首页 > 编程知识 正文

Spring 事务失效的场景,spring事务失效的原因

时间:2023-05-04 15:22:19 阅读:223346 作者:2968

文章目录 1. 数据库事务1.1 事务的ACID特性1.2 多事务的并发进行造成的问题1.3 MySQL事务的隔离级别1.4 Spring事务的传播特性1.5 Spring事务的失效场景1.5.1 本类中没有事务的方法调用含有事务的方法1.5.2 rollbackFor属性使用错误1.5.3 出现异常后被捕获处理了

1. 数据库事务 1.1 事务的ACID特性

事务的概念:事务是由N步数据库的操作序列组成的逻辑单元,这系列操作要么全部执行,要么全部放弃;

① 原子性:事务开始执行,要么全部执行成功,要么全部执行失败,只要中间有一个指令执行失败,所有的指令都执行失败,数据需要回滚到指令执行前的状态。

② 一致性:事务执行前后,事务的完整性应该保持一致。

③ 隔离性:隔离性是当多个用户并发访问数据库时,一个用户的事务不跟被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。

④ 持久性:一个事务一旦被提交,他对数据库中的数据的改变是永久性的,即使数据库发生故障也不应该对其有任何影响。

1.2 多事务的并发进行造成的问题

服务器程序本身就是多线程的环境,每一个浏览器(用户)访问服务器的时候,服务器都会创建一个新的线程来处理用户的请求,在这次请求的过程中,如果需要访问数据库就会有事务的操作,也就是说服务器时多线程的环境多事务并发的场景,如果多个用户同时访问同一个网站的一个功能,同时访问数据库中的同一条数据,多个事务同时访问同一个事务的情况就会出现,如果不做事务的隔离性处理,就可能会出现一些问题:

① 第一类丢失更新:某一个事务的回滚,导致另一个事务已经更新的数据丢失了

② 第二类丢失更新:某一个事务的提交,导致另一个事务已经提交的数据丢失了

③ 脏读:某一个事务,读取了另一个事务未提交的数据。

④ 不可重复读:某一个事务,对同一个数据前后读取的结果不一致。

⑤ 幻读:某一个事务,对同一个表前后查询到的行数不一致。

不可重复读和幻读不要搞混:不可重复读针对一行数据的修改,幻读针对向一张表中插入一条数据或删除一条数据

1.3 MySQL事务的隔离级别

① Read Uncommitted :一个事务可以读取另一个事务未提交的数据,安全级别最低,问题都会产生(读未提交)

② Read Committed :一个事务可以读取另一个事务已提交的数据,可以解决第一类丢失更新和脏读(读已提交)

③ Repeatable Read :事务开启后,其他事务不能对数据再进行修改(行锁),直到事务结束(可重复读)

④ Serializable :解决所有问题。 需要对数据表加锁,加锁会降低数据处理的性能,效率最低。(序列化)

MySQL的默认隔离级别为可重复读,但是互联网项目建议使用读已提交。

1.4 Spring事务的传播特性

Spring 事务的7种传播特性:

指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?

两个方法都加了 @Transactional,调用我的方法加了事务,我自己也加了事务,那我使用谁的事务?

① propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

② propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

③ propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

④ propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

⑤ propagation_required:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。

⑥ propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

⑦ propagation_nseted:如果当前存在事务,则在嵌套事务内执行。如果没有,就新建一个。

1.5 Spring事务的失效场景 1.5.1 本类中没有事务的方法调用含有事务的方法 public class AccountServiceImpl implements AccountService { //不带有事务的方法调动本类中有事务的方法,事务失效 public void transfer(String out, String in, Double money) { //调用add()方法 add(in,money); reduce(out,money); } @Transactional public void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0; } public void reduce(String out, double money) { accountDao.outMoney(out, money); }} @Testpublic void demo1(){accountService.transfer("aaa", "bbb", 200d);}

结果:事务失效了,add()执行时方法出现了异常,但是事务失效了

如图,Spring事务底层使用的是AOP 思想,当Spring容器启动的时候会去解析和加载相关的类,为带有@Transactional方法的类生成代理类,在代理类中实现目标方法的增强,进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部方式使用this调用方式时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。其主要原因在于同一个类中出现了没有加事务的方法调用了加事务的方法,导致不走Aop代理,事务失效。

public void transfer(String out, String in, Double money) { this.add(in,money); reduce(out,money);}/** 因为加了这个注解,因此Spring会为其生成一个代理类,由代理类实现事务管理,通过环绕通知来增强。 但是这个方法的调用者并不是代理类而是this,既然调用者变成了this,因此就无法开启事务管理*/@Transactionalpublic void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0;}

解决方法 1:调用者也加上@Transaction注解,如果这两个方法都加上注解,那么就相当于将add()方法的事务加入到transfer()方法中,因此都会在事务中执行,这是应为Spring默认的传播机制为required(如果当前没有事务就新建一个事务,如果已经存在事务了,就加入该事务)

@Transactionalpublic void transfer(String out, String in, Double money) { this.add(in,money); reduce(out,money);}@Transactionalpublic void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0;}

解决方法 2:将调用者和被调用者这两个方法,放在两个不同的类中

public class AccountServiceImpl implements AccountService { /** * 在当前类的没有事务的方法中调用另一个类中有事务的方法,出现事务管理 * 在add方法抛出异常时,事务回滚了,并没有往数据库中加钱 */ public void transfer(String out, String in, Double money) { addService.add(in,money); reduce(out,money); } public void reduce(String out, double money) { accountDao.outMoney(out, money); }}public class AddService {//使用事务,如果进行了事务管理,事务应该回滚 @Transactional public void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0; }} 1.5.2 rollbackFor属性使用错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务;其他异常不会触发回滚事务。

如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

//如果事务中抛出Exception异常,使用rollbackFor指定捕获的异常后,事务将回滚@Transactional(rollbackFor = Exception.class)public void transfer(String out, String in, Double money) throws Exception { try { add(in,money); } catch (Exception e) { //抛出Exception异常 throw new Exception(); } reduce(out,money);}@Transactionalpublic void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0;} 1.5.3 出现异常后被捕获处理了

如果add()方法内部抛了异常,而transfer()方法此时try catch了add()方法的异常,那这个事务还能正常回滚吗?不能!!!

@Transactionalpublic void transfer(String out, String in, Double money) { try { add(in,money); } catch (Exception e) { e.printStackTrace(); } reduce(out,money);}@Transactionalpublic void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0;}

因为当add()中抛出了一个异常以后,当前事务需要回滚。但是transfer()中由于手动的捕获这个异常并进行处理,transfer()认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

解决方法:可以catch,但是一定要抛出RuntimeException异常,这样事务才能回滚,否则会提交

Spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出RuntimeException。如果抛出RuntimeException并在你的业务方法中没有catch到的话,事务会回滚。因为Spring事务只能捕捉到Error和RuntimeException异常,而对于Exception异常是捕获不到的。在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException()

@Transactionalpublic void transfer(String out, String in, Double money) { try { add(in,money); } catch (Exception e) { /** * 如果抛出的是RuntimeException,调用者会捕捉到这个异常将事务rollback * 如果抛出的是Exception,调用者不会捕捉到这个异常,就会将事务commit */ throw new RuntimeException(); } reduce(out,money);}@Transactionalpublic void add(String in,double money){ accountDao.inMoney(in,money); //出现异常 int i=1/0;}

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。