最近在项目组的业务技术分析会上,有同事遇到事务的失效的场景导致线上业务不可用。如果对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