添加@Transactional注解后,Spring会将每个方法执行封装为AOP的执行过程,每次执行到注解的方法时,会判断是否需要开启事务,如果事务创建成功,则执行业务逻辑,业务逻辑执行完成后,提交事务或异常后回滚事务。
在阿里巴巴java规范文档中,不建议使用@Transactional开启事务,而是通过手动的方式,是因为@Transactional 有很多失效的场景,或者是在复杂的场景下,如果不注意的情况下,容易形成长事务,占用了过多的数据库连接,导致数据库连接用完,或者是导致数据库锁表导致异常。
扩展知识
手动添加事务方式
@Service
public class UserService {
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
public void updateUser(){
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
try {
// 执行业务逻辑
// 提交事务
platformTransactionManager.commit(transaction);
} catch (Exception e) {
// 回滚事务
platformTransactionManager.rollback(transaction);
}
}
}
有一种更简单的写法,内部自动开启事务,提交事务,或者回滚
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void updateUser(){
transactionTemplate.execute((TransactionCallback<Void>) status -> {
// 执行业务逻辑
return null;
});
}
}
@Transactional 原理
首选清楚mysql事务,首先开启事务,执行业务逻辑,然后提交或回滚事务。而Spring AOP是将这个过程封装了一层。
源码调用过程
在Spring中操作
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class)
public void updateUser(){
this.updateById();
this.deptService.updateById();
}
}
上面对updateUser方法添加了@Transactional类,在初始化Bean的时候,UserService会被代理,在代理调用过程如下
-
CglibAopProxy.DynamicAdvisedInterceptor#intercept 方法拦截,此处为动态代理执行出
-
ReflectiveMethodInvocation#proceed()方法,该方法是链式调用实现了MethodInterceptor的相关类的invoke方法
-
调用TransactionInterceptor#invoke方法,当前方法添加了@Transactional注解
在invokeWithinTransaction的关键代码
根据createTransactionIfNecessary内部信息,调用过程是
AbstractPlatformTransactionManager#getTransaction 获取事务信息
–> startTransaction() 开启事务
–> doBegin() 此处为最终Spring设置事务的方法
此时明白在调用代理对象下的updateUser()方法时,获取到mysql的连接,并将自动提交事务设置关闭
失效场景:
1、非public修饰的方法;Spring动态代理只能代理公共方法,私有或者静态方法都无法被代理
2、代码中使用try/catch处理的异常;在源码中只有抛出异常时才会被spring的代理执行的方法捕获,然后回滚事务
3、调用类内部的@Transactional方法;因为@Transactional是基于代理,调用内部方法时,直接通过this的方式调用,绕过了代理去执行方法
上述失效场景是由Spring AOP机制决定的
总结
在简单的场景下,直接使用@Transactional注解来进行编码,注意上述失效场景。但是在复杂的场景下,就容易产生长事务,因为我们知道,在调用方法的时候,就先获取到了mysql的连接,并且开启了事务,需要特别注意。