最近在项目组的业务技术分析会上,有同事遇到事务的失效的场景导致线上业务不可用。如果对Spring事务的@Transactional理解有限的话,确实很容易在开发中忽视一些细节问题,导致业务不可用的Bug。既然发生了问题,那么必然是要总结和反省的,然后我今天这里有时间总结一下各种事务失效的问题。
@Transactional属性介绍propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下: Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。Propagation.NESTED :和 Propagation.REQUIRED 效果一样。isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。 Isolation.DEFAULT:使用底层数据库默认的隔离级别。Isolation.READ_UNCOMMITTED:读未提交Isolation.READ_COMMITTED:读已提交Isolation.REPEATABLE_READ:可重复读Isolation.SERIALIZABLE:可串行化timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。为什么这么说,因为现在大多数的企业开发当中选择数据库来说,MySQL可谓是如火如荼,MySQL5.5之前默认的存储引擎是MyISAM,这个存储引擎是不支持事务的,只有Innodb存储引擎支持事务。
@Transactional采用的是AOP的方式去处理事务的问题,如果Bean本身没有被IOC容器注册的话,那么事务肯定也是不生效的。例如:
/**
* @author zhanbo
* @version 1.0
* @date 2020/11/5 16:51
* @describe
*/
//@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 测试事务失效1
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void testTransactional1() {
UserEntity userEntity = new UserEntity();
userEntity.setName("测试事务失效1");
userEntity.setMoney(1000);
testMapper.insert(userEntity);
int i = 10/0;
}
}
TestService没有@Service注解,此时testTransactional1()方法的事务是会失效的。当然这种问题显然是容易发现的,一般大家都不会犯这种错误。
以下来自 Spring 官方文档:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

之所以会失效是因为在Spring AOP 代理时,如上图所示
TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或JdkDynamicAopProxy的 invoke 方法会间接调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错。
/**
* @author zhanbo
* @version 1.0
* @date 2020/11/5 16:51
* @describe
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 自调用事务失效
*/
@Override
public void testTransactional1() {
testTransactional();
}
/**
*
*/
@Transactional(rollbackFor = Exception.class)
public void testTransactional(){
UserEntity userEntity = new UserEntity();
userEntity.setName("测试事务失效1");
userEntity.setMoney(1000);
testMapper.insert(userEntity);
int i = 10/0;
}
}testTransactional()方法中抛出了异常,但是事务并没有回滚。这是一种常见的自调用事务失效问题。解决方案,在testTransactional1()方法上加上@Transactional注解,因为Transactional当中的传播机制是新建一个事务所以加上这个注解即可解决问题。
/**
* @author zhanbo
* @version 1.0
* @date 2020/11/5 16:51
* @describe
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 异常被吞
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void testTransactional1() {
UserEntity userEntity = new UserEntity();
userEntity.setName("测试事务失效1");
userEntity.setMoney(1000);
testMapper.insert(userEntity);
try{
int i = 1/0;
}catch (Exception e){
System.out.println(e);
}
}
}rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

/**
* @author zhanbo
* @version 1.0
* @date 2020/11/5 16:51
* @describe
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 异常类型不一致问题
*/
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void testTransactional1() throws Exception {
UserEntity userEntity = new UserEntity();
userEntity.setName("测试事务失效1");
userEntity.setMoney(1000);
testMapper.insert(userEntity);
try{
int i = 1/0;
}catch (Exception e){
throw new Exception("发生异常");
}
}
}这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。/**
* @author zhanbo
* @version 1.0
* @date 2020/11/5 16:51
* @describe
*/
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 不支持事务
*/
@Override
@Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NOT_SUPPORTED)
public void testTransactional1() throws Exception {
testTransactionalApi();
}
}在业务开发中,@Transactional使用的还是比较多的,大家要注意的点就是贴切自己的业务场景去使用事务。
注意点:
public方法不可用rollbackFor默认是RuntimeException,如果是其他继承Exception的异常需要修改rollbackFor