【事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在,出现RuntimeException或Error的时候。通俗一点就是:代码中出现的空指针等异常,会被回滚。而文件读写、网络超时问题等,spring就没法回滚了。】
一、数据库引擎不支持事务
以 MySQL为例,MyISAM引擎是不支持事务操作的,一般要支持事务都会使用InnoDB引擎
从MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAM,所以这一点要值得注意,如果底层引擎不支持事务,那么再怎么设置也没有用。
二、没有被 Spring 管理
此时把@Service注解注释掉,那么这个类就不会被加载成一个Bean,这个类就不会Spring管理了,事务自然就失效了。
public class OrderServiceImpl implements OrderService{
@Transactional
public void updateOrder(Order order){
//update order
}
}
因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,service类都不是spring管理的
三、方法不是 public 的或者被final 、static关键字修饰
@Transactional注解只能用于public 的方法上,否则事多不会生效,如果要用在非public的方法上,则可以开启基于 AspcetJ 框架的静态代理模式。
Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了。
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
//spring事务方法addTianLuo的访问权限不是public
@Transactional
private void addTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
事务方法被final 、static关键字修饰
@Service
public class MyServiceImpl{
@Autowired
private MyMapper myMapper;
@Autowired
private MyFlowMapper myFlowMapper;
//如果一个方法被声明为final或者static,则该方法不能被子类重写,
//也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
@Transactional
public final void addSomething(Things things){
myMapper.save(things);
myFlowMapper.saveFlow(buildFlowByMy(things));
}
}
四、发生自身调用
事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
public void addTianLuo(TianLuo tianluo){
// 调用内部的事务方法
//调用目标executeAddTianLuo方法不是通过代理类进行的,因此事务不生效
this.executeAddTianLuo(tianluo);
}
@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中。如下:
@Service
public class TianLuoExecuteServiceImpl implements TianLuoExecuteService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
@Service
public class TianLuoAddServiceImpl implements TianLuoAddService {
@Autowired
private TianLuoExecuteService tianLuoExecuteService;
public void addTianLuo(User user){
tianLuoExecuteService.executeAddTianLuo(user);
}
}
有时候也可以在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象。
五、没有配置事务管理器
如果没有配置以下DataSourceTransactionManager数据源事务管理器,那么事务也不会生效 :
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
但在 Spring Boot 中只要引入了 spring-boot-starter-data-jdbc 启动器依赖就会自动配置DataSourceTransactionManager数据源事务管理器,所以 Spring Boot框架不存在这个问题,但在传统的 Spring 框架中需要注意。
解决方案:(如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。)
@Configuration
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
六、设置了不支持事务
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
//update order
}
}
这里的Propagation.NOT_SUPPORTED表示当前方法不以事务方式运行,当前若存在事务则挂起,这就是主动不支持以事务方式运行了
七、异常没有被抛出
这个方法把异常给捕获了,但没有抛出来,所以事务不会回滚,只有捕捉到异常事务才会生效。
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) {
try {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo flow数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
}
}
}
解决方案:在spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志什么的,一定要重新把异常抛出来,正例如下:
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional(rollbackFor = Exception.class)
public void addTianLuo(TianLuo tianluo) {
try {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
throw e;
}
}
}
手动抛了别的异常
Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置
解决方案:添加属性配置@Transactional(rollbackFor = Exception.class)
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(tianluo);
throw new Exception(); //手动抛了Exception异常,但是是不会回滚的
}
}
八、异常类型不匹配
其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效。
@Service
public class TianLuoServiceImpl implements TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
//其实rollbackFor属性指定的异常
@Transactional(rollbackFor = Error.class)
public void addTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(tianluo);
//模拟异常抛出
throw new Exception();
}
}
九、配置错误的 @Transactional 注解
虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在更新User实体时会抛出异常。
@Transactional(readOnly = true)
public void updateUser(User user){
userDao.updateUser(user);
}
解决方案:将readOnly属性设置为false,或者移除了@Transactional注解中的readOnly属性。
十、事务超时时间设置过短
timeout属性被设置为1秒,这意味着如果事务在1 秒内无法完成,则报事务超时了。
@Transactional(timeout = 1)
public void doSomething(){
}
十一、使用了错误的事务传播机制
@Service
public class TianLouServiceImpl{
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
//Propagation.NOT_SUPPORTED传播特性不支持事务
@Transactional(propagation = Propagation.NOT_SUPPPRTED)
public void doInsertIianluo(TianLuo tianluo)throws Exception{
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
选择正确的事务传播机制:Spring提供了七种事务传播机制
- REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
- SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
- MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
- REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
- NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
- NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。
十二、事务注解被覆盖导致事务失效
这将导致数据不一致的问题,因为在MyRepository的save()方法中进行的数据库操作将不会回滚。
public interface MyRepository {
@Transactional
void save(String data);
}
public class MyRepositoryImpl implements MyRepository {
@Override
public void save(String data) {
// 数据库操作
}
}
public class MyService {
@Autowired
private MyRepository myRepository;
//由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务。
@Transactional
public void doSomething(String data) {
myRepository.save(data);
}
}
public class MyTianluoService extends MyService {
//传播行为(REQUIRES_NEW),子类方法中的注解覆盖了父类的注解
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething(String data) {
super.doSomething(data);
}
}
十三、嵌套事务
@Service
public class TianLuoServiceInOutService {
@Autowired
private TianLuoFlowService tianLuoFlowService;
@Autowired
private TianLuoMapper tianLuoMapper;
//导致tianLuoMapper.save也会回滚啦。
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowService.saveFlow(tianluo);
}
}
@Service
public class TianLuoFlowService {
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
//如果saveFlow出现运行时异常,会继续往上抛,到外层addTianLuo的方法
@Transactional(propagation = Propagation.NESTED)
public void saveFlow(TianLuo tianLuo) {
tianLuoFlowMapper.save(tianLuo);
throw new RuntimeException();
}
}
解决方案:try-catch包
@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
try {
tianLuoFlowService.saveFlow(tianluo);
} catch (Exception e) {
log.error("save tian luo flow fail,message:{}",e.getMessage());
}
}
十四、事务多线程调用
Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。
Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。
@Service
public class TianLuoService {
@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowService tianLuoFlowService;
@Transactional
public void addTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
//多线程调用
new Thread(() -> {
tianLuoFlowService.saveFlow(tianluo);
}).start();
}
}
@Service
public class TianLuoFlowService {
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;
@Transactional
public void save(TianLuo tianLuo) {
tianLuoFlowMapper.saveFlow(tianLuo);
}
}
在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。