阅读更多
1 环境
IDEA
Maven3.5.3
Spring-Boot-2.0.4.RELEASE
2 Demo工程目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 . ├── pom.xml └── src ├── main │ ├── java │ │ └── org │ │ └── liuyehcf │ │ └── spring │ │ └── tx │ │ ├── Application.java │ │ ├── DalConfig.java │ │ ├── UserController.java │ │ ├── UserDAO.groovy │ │ ├── UserDO.java │ │ └── UserService.java │ └── resources │ ├── application.properties │ └── create.sql └── test ├── java │ └── org │ └── liuyehcf │ └── spring │ └── tx │ └── test │ ├── BaseConfig.java │ ├── TestApplication.java │ ├── TestController.java │ └── TestDalConfig.java └── resources └── db └── create_h2.sql
3 pom
Demo工程的依赖配置
mysql驱动
mybatis以及与spring集成
groovy
spring-jdbc
spring-boot
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <groupId > org.liuyehcf</groupId > <artifactId > spring-tx</artifactId > <version > 1.0-SNAPSHOT</version > <modelVersion > 4.0.0</modelVersion > <properties > <spring.boot.version > 2.0.4.RELEASE</spring.boot.version > </properties > <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.12</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.6</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.2</version > </dependency > <dependency > <groupId > org.codehaus.groovy</groupId > <artifactId > groovy-all</artifactId > <version > 2.4.15</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > <version > 1.4.197</version > <scope > test</scope > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring.boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.6.0</version > <configuration > <source > 1.8</source > <target > 1.8</target > <compilerId > groovy-eclipse-compiler</compilerId > <fork > true</fork > <compilerArguments > <javaAgentClass > lombok.launch.Agent</javaAgentClass > </compilerArguments > </configuration > <dependencies > <dependency > <groupId > org.codehaus.groovy</groupId > <artifactId > groovy-eclipse-compiler</artifactId > <version > 2.9.2-01</version > </dependency > <dependency > <groupId > org.codehaus.groovy</groupId > <artifactId > groovy-eclipse-batch</artifactId > <version > 2.4.3-01</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.16.18</version > </dependency > </dependencies > </plugin > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > ${spring.boot.version}</version > <configuration > <fork > true</fork > <mainClass > org.liuyehcf.spring.tx.Application</mainClass > </configuration > <executions > <execution > <goals > <goal > repackage</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </project >
4 Java
4.1 Application
不多说,SpringBoot应用的启动方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.liuyehcf.spring.tx;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication(scanBasePackages = "org.liuyehcf.spring.tx") public class Application { public static void main (String[] args) { SpringApplication.run(Application.class, args); } }
4.2 DalConfig
数据层的SpringBoot方式的配置,包括如下注解
MapperScan
:Mapper
的扫描路径,通过sqlSessionFactoryRef
参数指定sql会话工厂
EnableTransactionManagement
:开启声明式事务
综上,一个数据层的完整配置包括如下几项
Mapper映射器 :通过MapperScan
注解实现
声明式事务 :通过EnableTransactionManagement
注解开启
数据源
事务管理器
会话工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package org.liuyehcf.spring.tx;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.jdbc.datasource.DriverManagerDataSource;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@MapperScan(basePackages = "org.liuyehcf.spring.tx", sqlSessionFactoryRef = "sqlSessionFactory") @EnableTransactionManagement @Configuration public class DalConfig { @Value("${db.url}") private String url; @Value("${db.username}") private String username; @Value("${db.password}") private String password; @Bean(name = "dataSource") public DataSource dataSource () { DriverManagerDataSource dataSource = new DriverManagerDataSource (); dataSource.setDriverClassName("com.mysql.jdbc.Driver" ); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManager () { DataSourceTransactionManager manager = new DataSourceTransactionManager (); manager.setDataSource(dataSource()); return manager; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory () throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean (); sqlSessionFactoryBean.setDataSource(dataSource()); return sqlSessionFactoryBean.getObject(); } }
4.3 UserController
一个非常普通的Controller
,包含一个健康检查接口以及一个业务接口(插入一个User)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package org.liuyehcf.spring.tx;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController @RequestMapping("/") public class UserController { @Resource private UserService userService; @RequestMapping("/insert") @ResponseBody public String insert (@RequestParam("name") String name, @RequestParam("age") Integer age, @RequestParam("ex") Boolean ex) { try { userService.insert(name, age, ex); return "SUCCESS" ; } catch (Throwable e) { e.printStackTrace(); return "FAILURE" ; } } @RequestMapping("/check_health") @ResponseBody public String checkHealth () { return "OK" ; } }
4.4 UserDAO
该类用groovy
来编写,需要引入org.codehaus.groovy:groovy-all
才能编译。使用groovy
的好处是可以写整段的sql语句,而不需要进行字符串拼接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package org.liuyehcf.spring.tximport org.apache.ibatis.annotations.Insertimport org.apache.ibatis.annotations.Mapperimport org.apache.ibatis.annotations.SelectKeyimport org.apache.ibatis.mapping.StatementType@Mapper interface UserDAO { @Insert (''' INSERT INTO user( name, age )VALUES( #{name}, #{age} ) ''' ) @SelectKey (before = false , keyProperty = "id" , resultType = Long.class , statementType = StatementType.STATEMENT, statement = "SELECT LAST_INSERT_ID()" ) int insert(UserDO userDO) }
4.5 UserDO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package org.liuyehcf.spring.tx;public class UserDO { private Long id; private String name; private Integer age; public Long getId () { return id; } public void setId (Long id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public String toString () { return "UserDO{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}' ; } }
4.6 UserService
这里通过一个参数ex
来控制是否抛出异常,便于校验@Transactional
是否进行了回滚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package org.liuyehcf.spring.tx;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;@Service public class UserService { @Resource private UserDAO userDAO; @Transactional public int insert (String name, Integer age, Boolean ex) { UserDO userDO = new UserDO (); userDO.setName(name); userDO.setAge(age); int res = userDAO.insert(userDO); if (ex) { throw new RuntimeException (); } return res; } }
5 Resource
5.1 application.properties
配置项
1 2 3 4 server.port=7001 db.url=jdbc:mysql://127.0.0.1:3306/mybatis?autoReconnect=true&useSSL=false db.username=root db.password=xxx
5.2 create.sql
上述工程用到的数据表的建表语句
1 2 3 4 5 6 7 8 9 10 11 12 DROP DATABASE mybatis;CREATE DATABASE mybatis;USE mybatis; CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR (20 ) NOT NULL , age INT UNSIGNED NOT NULL , PRIMARY KEY(id),UNIQUE KEY(name))ENGINE= InnoDB DEFAULT CHARSET= utf8 COLLATE = utf8_bin;
5.3 测试步骤
将应用启动后,访问http://localhost:7001/insert?name=liuyehcf&age=10&ex=true ,带上了ex=true
参数时,UserService.insert
方法将会抛出异常。@Transactional
注解在不指定rollbackFor
属性时,默认回滚RuntimeException
以及Error
,但是Exception
不会回滚
查看数据库,发现并没有插入数据,说明已经回滚了
6 集成测试
需要人肉访问url还是比较麻烦的,这里希望实现自动化的集成测试
6.1 BaseConfig
集成测试配置的基类,避免重复配置。集成测试类继承该基类即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.liuyehcf.spring.tx.test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = TestApplication.class) public class BaseConfig {}
6.2 TestDalConfig
集成测试数据源配置,这里选择的是h2-database
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package org.liuyehcf.spring.tx.test;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@MapperScan(basePackages = "org.liuyehcf.spring.tx", sqlSessionFactoryRef = "sqlSessionFactory") @EnableTransactionManagement @Configuration public class TestDalConfig { @Bean(name = "dataSource") public DataSource dataSource () { return new EmbeddedDatabaseBuilder () .setType(EmbeddedDatabaseType.H2) .addScript("db/create_h2.sql" ) .build(); } @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManager () { DataSourceTransactionManager manager = new DataSourceTransactionManager (); manager.setDataSource(dataSource()); return manager; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory () throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean (); sqlSessionFactoryBean.setDataSource(dataSource()); return sqlSessionFactoryBean.getObject(); } }
6.3 TestApplication
集成测试SpringBoot
启动类。这里用到了@ComponentScan
注解的excludeFilters属性,用于排除一些类,本例中排除了启动类Application
以及数据源配置类DalConfig
,这样一来,集成测试加载的数据源配置就只有TestDalConfig
了
为什么需要额外排除Application
?如果只排除DalConfig
的话,Application
仍然在TestApplication
配置的扫描路径下,且excludeFilters
属性只对TestApplication
有效,对Application
无效,因此Application
还是能够扫描到DalConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package org.liuyehcf.spring.tx.test;import org.liuyehcf.spring.tx.Application;import org.liuyehcf.spring.tx.DalConfig;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.FilterType;@SpringBootApplication(scanBasePackages = "org.liuyehcf.spring.tx") @ComponentScan(basePackages = "org.liuyehcf.spring.tx", excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Application.class, DalConfig.class}) ) public class TestApplication {}
6.4 TestController
普通测试类,继承BaseConfig
来获取集成测试的相关配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.liuyehcf.spring.tx.test;import org.junit.Test;import org.liuyehcf.spring.tx.UserController;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.transaction.annotation.Transactional;public class TestController extends BaseConfig { @Autowired private UserController userController; @Test @Transactional public void test () { userController.insert("liuye" , 10 , false ); } }
6.5 create_h2.sql
注意,千万别忘了SET mode MySQL;
这句
1 2 3 4 5 6 7 8 9 SET mode MySQL;CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR (20 ) NOT NULL , age INT UNSIGNED NOT NULL , PRIMARY KEY(id),UNIQUE KEY(name))ENGINE= InnoDB;
7 源码剖析
Spring-tx
利用了Spring-aop
,在目标方法上织入了一系列事务相关的逻辑。相关织入逻辑可以参考SourceAnalysis-Spring-AOP 。这里仅介绍事务相关的增强逻辑
分析的起点是TransactionInterceptor.invoke
,该类是事务对应的增强类,或者说拦截器(Spring AOP的本质就是一系列的拦截器)
1 2 3 4 5 6 7 8 9 10 11 @Override @Nullable public Object invoke (MethodInvocation invocation) throws Throwable { Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null ); return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
沿着调用链往下走,下面是TransactionAspectSupport.invokeWithinTransaction
,主要关注三个方法调用
createTransactionIfNecessary
方法 :在必要时,创建一个事务。与事务传播方式等等有关系
completeTransactionAfterThrowing
方法 :拦截到异常时,根据异常类型选择是否进行回滚操作
commitTransactionAfterReturning
方法 :未拦截到异常时,提交本次事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 @Nullable protected Object invokeWithinTransaction (Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null ); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null ; try { retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder (); try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceedWithInvocation(); } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException (ex); } } else { throwableHolder.throwable = ex; return null ; } } finally { cleanupTransactionInfo(txInfo); } }); if (throwableHolder.throwable != null ) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null ) { logger.error("Application exception overridden by commit exception" , throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null ) { logger.error("Application exception overridden by commit exception" , throwableHolder.throwable); } throw ex2; } } }
7.1 createTransactionIfNecessary
我们先来看一下createTransactionIfNecessary
方法,该方法的核心逻辑就是
getTransaction
:获取TransactionStatus
对象(该对象包含了当前事务的一些状态信息,包括是否是新事务
、是否为rollback-only模式
、是否有savepoint
),基本Spring事务的核心概念都在这个方法中有所体现,包括事务的传播方式等等
prepareTransactionInfo
:创建一个TransactionInfo
对象(该对象持有了一系列事务相关的对象,包括PlatformTransactionManager
、TransactionAttribute
、TransactionStatus
等对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected TransactionInfo createTransactionIfNecessary (@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { if (txAttr != null && txAttr.getName() == null ) { txAttr = new DelegatingTransactionAttribute (txAttr) { @Override public String getName () { return joinpointIdentification; } }; } TransactionStatus status = null ; if (txAttr != null ) { if (tm != null ) { status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured" ); } } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }
7.2 completeTransactionAfterThrowing
继续跟踪TransactionAspectSupport.completeTransactionAfterThrowing
方法。首先会根据异常类型以及事务配置的属性值来判断,本次是否进行回滚操作。主要的判断逻辑在txInfo.transactionAttribute.rollbackOn
方法中,本Demo对应的TransactionAttribute
接口的实现类是
RuleBasedTransactionAttribute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 protected void completeTransactionAfterThrowing (@Nullable TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.getTransactionStatus() != null ) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception" , ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by rollback exception" , ex); throw ex2; } } else { try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception" , ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by commit exception" , ex); throw ex2; } } } }
我们接着来看一下RuleBasedTransactionAttribute.rollbackOn
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public boolean rollbackOn (Throwable ex) { if (logger.isTraceEnabled()) { logger.trace("Applying rules to determine whether transaction should rollback on " + ex); } RollbackRuleAttribute winner = null ; int deepest = Integer.MAX_VALUE; if (this .rollbackRules != null ) { for (RollbackRuleAttribute rule : this .rollbackRules) { int depth = rule.getDepth(ex); if (depth >= 0 && depth < deepest) { deepest = depth; winner = rule; } } } if (logger.isTraceEnabled()) { logger.trace("Winning rollback rule is: " + winner); } if (winner == null ) { logger.trace("No relevant rollback rule found: applying default rules" ); return super .rollbackOn(ex); } return !(winner instanceof NoRollbackRuleAttribute); }
RuleBasedTransactionAttribute
的父类DefaultTransactionAttribute
的rollbackOn
方法如下,可以看到,默认的回滚异常类型就是RuntimeException
以及Error
1 2 3 public boolean rollbackOn (Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
8 小插曲
8.1 忘记mysql密码
以Mac OS(10.13.6)
、mysql-5.7.22
为例
1 2 3 4 5 6 7 8 9 10 11 12 13 mysql.server stop mysqld_safe --skip-grant-tables & mysql -u root update mysql.user set authentication_string=PASSWORD("xxx" ) where User='root' ; flush privileges;
9 todo
何时注入事务上下文
1 2 3 4 org.springframework.transaction.interceptor.TransactionInterceptor.invokeWithinTransaction * createTransactionIfNecessary * prepareTransactionInfo org.springframework.transaction.interceptor.TransactionAspectSupport#transactionInfoHolder
@Transactional with @Test
@Transactional默认会在执行完测试方法后回滚
1 2 3 4 5 org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks org.springframework.test.context.transaction.TransactionalTestExecutionListener org.springframework.test.context.transaction.TransactionContext
调用堆栈的时序图
<tx:annotation-driven transaction-manager="transactionManager" order="0"/>
好像无法改变实际的order值
Establishing SSL connection without server's identity verification is not recommended.
10 参考