目录
事务示例
示例一
示例二
示例三
示例四
示例五
示例六
事务原理
@EnableTransactionManagement
执行代理对象目标方法
事务示例
示例一
在test()方法直接调用abc()方法,并在test()方法添加@Transactional,test()和abc()方法分别会更新id=1和id=2的数据name字段,abc()方法中会抛出异常。
@Component
public class TransactionService {
@Autowired
private TransactionService service;
@Resource
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public String test(String name, String target) throws Exception {
User user = new User();
user.setId(1l);
user.setName(name);
userMapper.updateById(user);
abc(target);
return "ok";
}
public void abc(String target) throws Exception {
User user = new User();
user.setId(2l);
user.setName(target);
userMapper.updateById(user);
throw new Exception("error");
}
}
使用TransactionController对外提供接口做测试
@RestController
public class TransactionController {
@Autowired
private TransactionService service;
@GetMapping("testT")
public String test(@RequestBody Map<String, Object> map) throws Exception {
return service.test(map.get("name").toString(), map.get("target").toString());
}
}
测试之前先看数据库的值:
执行 http://localhost:8080/testT 测试
不难看出,两条更新操作都会被回滚。
示例二
在abc()方法中添加@Transactional(rollbackFor = Exception.class)注解
@Transactional(rollbackFor = Exception.class)
public void abc(String target) throws Exception {
User user = new User();
user.setId(2l);
user.setName(target);
userMapper.updateById(user);
throw new Exception("error");
}
结果还是一样,都会被回滚。因为test()方法是调用的TransactionService原始对象的abc()方法。
示例三
在TransactionService中新增TransactionService 类型的成员属性,通过@Autowired注入spring bean对象,并在test()方法中使用service属性调用abc(),其用意是调用spring创建的代理对象的abc()方法。
最终结果还是一样都会回滚,因为这个线程只会创建一个事务,同一个事务要么全部回滚,要么全部提交。
示例四
在abc()方法的注解改为@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW),其用意是同一个线程在执行test()方法时创建一个事务,在调用service.abc()方法时又会创建一个事务。
最终两个方法执行的数据库更新操作还是会回滚,因为线程在执行abc()方法后抛出的异常,在test()方法中并没有处理这个异常,所以两个事务都会回滚。
示例五
在test()方法中将service.abc()方法用try catch捕获异常。
再次执行,就会发现test()方法事务会提交,而abc()方法事务会回滚。如图,id=1的name属性修改james,id=2的name属性没有发生变化。
示例六
在test()方法中将service.abc()方法try catch捕获,并且abc()方法使用的事务和test()方法使用同一个事务,此时事务会提交还是回滚呢?还会出现和示例五中abc()方法更新操作回滚,test()方法更新操作提交的效果呢?
思考一会。。。
首先这两个方法使用的是同一个事务,故它们要么全部提交,要么全部回滚。那到底是回滚还是提交,将由跟spring事务管理器中的一个属性相关,该属性为globalRollbackOnParticipationFailure,它默认等于true,表示如果参与的事务有失败的,则标记为为回滚。如果将他设置为false,表示局部执行失败的事务,也会标记为提交。所以这个事务还是会回滚。
如果将事务管理器的globalRollbackOnParticipationFailure设置为false,那么该事务就会提交。
小结:在a()方法调用b()方法时,如果他们使用同一个事务,他们的数据库更新操作要么全部提交,要么全部回滚;如果b()方法事务传播属性为REQUIRES_NEW,表示在执行b()方法时创建新事物,此时a()方法事务的提交和回滚可以不受b()方法的事务控制。
事务原理
@EnableTransactionManagement
spring boot启动添加@EnableTransactionManagement注解,表示开启事务功能。spring解析@EnableTransactionManagement注解流程如下图。
a、导入TransactionManagementConfigurationSelector类,执行selectImports()方法,加载AutoProxyRegistrar和ProxyTransactionManagementConfiguration类。
b、spring在解析AutoProxyRegistrar时执行registerBeanDefinitions()方法,它会将InfrastructureAdvisorAutoProxyCreator注册到spring容器中,InfrastructureAdvisorAutoProxyCreator是一个BeanPostProccessor,在其父类中实现了postProcessBeforeInstantiation()方法,在实例化前会给目标对象创建代理对象。
c、spring在解析ProxyTransactionManagementConfiguration时分别会创建3个bean对象,BeanFactoryTransactionAttributeSourceAdvisor、TransactionInterceptor和AnnotationTransactionAttributeSource,分别是advisor、advice和pointcut,advice是代理逻辑,pointcut是切入点,advisor可以理解为是advice+pointcut组成的。
执行代理对象目标方法
a、当调用被代理对象目标方法时,会进入TransactionInterceptor#invoke()方法,执行invokeWithinTransaction()方法
b、getTransaction()中创建事务对象,如果事务传播属性为REQUIRED,REQUIRES_NEW,NESTED时,执行startTransaction()开启并设置事务属性。
c、首先从事务对象中获取连接对象,将连接对象设置为不自动提交。
d、执行prepareTransactionInfo()方法创建TransactionInfo对象,并将getTransaction()返回的TransactionStatus对象设置到TransactionInfo对象,然后调用bindToThread()方法获取当前线程ThreadLocal对象中的TransactionInfo对象,将其赋值给oldTransactionInfo缓存起来,将新创建的TransactionInfo对象设置到ThreadLocal对象中去。
d、执行目标方法
f、提交或者回滚,如果目标方法执行失败,抛出异常,则会执行completeTransactionAfterThrowing()进行回滚,回滚之后执行finally块的cleanupTransactionInfo()方法,将缓存在oldTransactionInfo属性的TransactionInfo对象重新设置到当前线程的ThreadLocal对象中去;如果目标方法执行成功,则先执行cleanupTransactionInfo()方法,再执行commitTransactionAfterReturning()方法提交事务。
流程图