1.事务失效的7种情况
1)未启用spring事务管理功能
2)方法不是public类型的
3)数据源未配置事务管理器
4)自身调用问题
5)异常类型错误
6)异常被捕获
7)业务和spring事务代码必须在一个线程中
1.1未启用spring事务管理功能
@EnableTransactionManagement 注解用于启用spring事务自动管理功能
如果引入了autoconfigure,TransactionAutoConfiguration.class会自动启用事务管理功能
1.2方法不是public类型
@Transactional 注解可以用到类上、接口上和public方法上,如果是非public方法,事务将不会生效,主要是spring代理机制导致
1.3数据源未配置事务管理器
spring是通过事务管理器来管理事务的,一定要配置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
如果引入了autoconfigure,DataSourceTransactionManagerAutoConfiguration.class会自动创建事务管理器
1.4自身调用问题
spring是通过aop的方式实现的事务管理,为每个需要事务管理的bean创建代理对象, 然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。
@Service public class TransactionDemoService { public void m1(){ this.m2();; } @Transactional(rollbackFor = Exception.class) public void m2(){ //db操作 } }
以上代码中,调用m1方法,m2方法中的事务是不会生效的。因为m1中调用m2是通过this调用的,不是通过代理对象。 this.m2()不会被拦截,所以事务是无效的,如果外部直接调用通过TransactionDemoService这个bean来调用m2方法,事务是有效的,上面代码可以做一下调整,如下,在TransactionDemoService中注入了自己,此时m1中的m2事务是生效的 。
@Service public class TransactionDemoService { @Autowired private TransactionDemoService self; public void m1(){ self.m2();; } @Transactional(rollbackFor = Exception.class) public void m2(){ //db操作 } }
或者,使用AopContext也能解决这个问题,如下:
@Service public class TransactionDemoService { public void m1(){ ((TransactionDemoService)AopContext.currentProxy()).m2(); } @Transactional(rollbackFor = Exception.class) public void m2(){ //db操作 } }
注意,当使用AopContext时, 需要使用@EnableAspectJAutoProxy(exposeProxy = true)来暴露AOP的Proxy对象才行,否则会报异常。
1.5异常类型错误
spring的事务回滚机制为,对代理的方法进行try catch 当捕获到有指定的异常时,spring自动对事务进行回滚。但是, 并不是任何异常情况下,spring都会回滚事务,默认情况下,RuntimeException和Error的情况下,spring事务才会回滚。
同时,也可以自定义回滚得异常类型:
@Transactional(rollbackFor = AccessException.class) public void m2(){ //db操作 }
1.6异常被捕获
当业务代码抛出异常,spring感知到异常的时候,才会进行回滚,如果在业务方法里面捕获了异常,spring无法感知到异常,就不会进行回滚,如下:
@Transactional(rollbackFor = Exception.class) public void m2(){ try{ //db操作 }catch (Exception e){ logger.error("方法执行出现异常",e); } }
1.7业务和spring事务代码必须在一个线程中
spring事务实现中使用了ThreadLocal,ThreadLocal可以实现同一个线程中数据共享,必须是同一个线程的时候,数据才可以共享,这就要求业务代码必须和spring事务的源码执行过程必须在一个线程中,才会受spring事务的控制,比如下面代码,方法内部的子线程内部执行的事务操作将不受m1方法上spring事务的控制。
@Transactional(rollbackFor = Exception.class) public void m1() { new Thread() { //db操作 }.start(); }