vlambda博客
学习文章列表

导致spring事务失效的几种场景

分享架构师全套视频,50G资料,涵盖所有框架知识点,栈!

西0104

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();
}

·END·