Spring 事务失效的场景

Spring 事务失效指在使用 Spring 进行事务管理时,事务没有被正确地提交或回滚所致的问题。可能是因为代码中未正确地配置事务管理器或者事务注解的使用不正确所导致的。如果事务失效,则数据库操作可能未能正确地更新或回滚,可能会造成不可预知的结果,从而影响系统的稳定性和一致性。

1. 访问权限问题

Java 的访问权限有4种:privatedefaultprotectedpublic,它们的权限从左到右,以此变大。如果在开发中,将事务方法定义了错误的访问权限,则事务功能会失效。

java 复制代码
@Service
public class EmpService {
 
    @Transactional
    private void add(UserModel userModel){
        saveData(userModel);
    }
}

如上:add 方法的权限被定义成了 private,这样会导致事务失效,Spring 要求被代理方法必须是**public**的。

在Spring源码中,如果目标方法不是 public,则 TransactionAttribute 返回 null,不支持事务。

2. 方法被 final 修饰

方法被**final修饰时,也会导致事务失效,如下的add方法使用了final**修饰,造成事务失效。

java 复制代码
@Service
public class EmpService {
 
    @Transactional
    public final void add(UserModel userModel){
        saveData(userModel);
    }
}

因为 Spring 事务底层是用了 AOP ,用了JDK的动态代理或者CGLB的动态代理,会帮我们生成代理类,在代理类中实现事务功能。如果某个方法被final修饰了,那么在代理类中,就无法重新该方法,而添加事务功能。

注意:如果某个方法被static修饰,同样也无法通过动态代理,变成事务方法。

3. 直接调用内部方法

java 复制代码
@Service
public class EmpService {
 
    public void add(UserModel userModel){
        saveData(userModel);
        updateSataus(userModel);
    }
 
 
    @Transactional
    public void updateSataus(UserModel userModel){
        doSomething();
    }
}

在事务方法add可知,它直接调用了 updateStatus 方法,方法拥有事务的能力是因为 Spring AOP 中生成了代理对象,但是直接调用 updateStatus 方法不会直接生成事务。但是可以直接将该类直接注入进来,比如:

java 复制代码
@Service
public class EmpService {
 
    private EmpService empService;
 
    @Transactional
    public void add(UserModel userModel){
        saveData(userModel);
        empService.updateSataus(userModel);
    }
 
 
    @Transactional(rollbackFor = Exception.class)
    public void updateSataus(UserModel userModel){
        doSomething();
    }
}

这样事务就生效了,也不会穿生循环依赖的问题。

4. 未被 Spring 管理

如下所示:EmpService 类没有交给 Spring 进行管理(没添加**@Service**等注解),导致事务失效。

java 复制代码
public class EmpService {
    @Transactional
    public void add(UserModel userModel){
        saveData(userModel);
        updateSataus(userModel);
    }
}

5. 多线程调用

由以下代码可知:在 add 事务方法里面,调用了 updateStatus 事务方法,但是 updateStatus 事务方法是在另外一个线程中调用的。这样就导致了两个方法不在同一个线程中,获取到了数据库连接不一样,从而是两个不同的事务,如果 updateStatus 方法中抛出了异常,add 方法是不会回滚的。

java 复制代码
@Service
public class EmpService {
 
    @Autowired
    private OrderService orderService;
 
 
    @Transactional
    public void add(UserModel userModel){
 
        new Thread(()->{
            orderService.updateSataus();
        }).start();
    }
 
}
 
@Service
public class OrderService{
 
    @Transactional
    public void updateSataus(){
        System.out.println("======================");
    }
 
}

Spring的事务是通过数据库的连接来实现的:

当前线程中保存了一个 map,key 是数据源,value 是数据库连接。同一个事务,指同一个数据库连接,只有拥有同一个事务连接才能保证同时提交和回滚。如果是不同的线程,拿到的数据库连接肯定是不同的。

6. 数据库不支持事务

如果数据库的引擎是**myisam,那么它是不支持事务的,要想支持事务,改成innodb**引擎

7. 事务没有开启

如果是 Spring Boot 项目,那么是事务默认是开启的,但如果是 Spring 项目,需要 xml 配置

8. 事务的传播特性

如果事务的传播特性设置错了,事务也会失效

如下:**Propagation.NEVER**这种类型的传播特性不支持事务,如果有事务会抛出异常。

java 复制代码
@Service
public class EmpService {
 	//Propagation.NEVER传播特性不支持事务
    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel){
        saveData(userModel);
        updateSataus(userModel);
    }
}

目前只有这三种传播特性才会创建新事物:REQUIREDREQUIRES_NEWNESTED

9. 自己捕获了异常

事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常

java 复制代码
@Service
public class EmpService {
    @Transactional
    public void add(UserModel userModel){
        try {//开发者手动捕获了异常,导致事务失效
            saveData(userModel);
            updateSataus(userModel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

10. 手动抛了别的异常

如果抛的异常不正确,事务也不会回滚

java 复制代码
@Service
public class EmpService {
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
            saveData(userModel);
            updateSataus(userModel);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
}

因为 Spring 事务,默认情况下只会回滚**RuntimeException(运行时异常)Error(错误)**,对于普通的非运行时异常,它不会回滚。

11. 自定义回滚异常

如果在使用**@Transactional注解声明事务时,有时想自定义回滚异常,Spring 也是支持的。可以通过设置rollbackFor**参数,来完成这个功能。如下:

java 复制代码
@Service
public class EmpService {
    @Transactional(rollbackFor = BusinessException.class)
    public void add(UserModel userModel) {
            saveData(userModel);
            updateSataus(userModel);
    }
}

但是如果在程序执行过程中,出现了 SQL 异常,但是 SQL 异常并不属于我们定义的 BusinessException 异常,所以事务也不会回滚

相关推荐
为将者,自当识天晓地。17 分钟前
c++多线程
java·开发语言
daqinzl25 分钟前
java获取机器ip、mac
java·mac·ip
激流丶40 分钟前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue44 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式1 小时前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink2 小时前
HTTP动词与状态码
java
ktkiko112 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田2 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue