最近写spring事务时用到REQUIRES_NEW遇到一些不回滚的问题,所以就记录一下。

场景1:在一个服务层里面方法1和方法2都加上事务,其中方法二设置上propagation=Propagation.REQUIRES_NEW,方法1调用方法2并且在执行完方法2后抛出一个异常,如下代码

 @Service
public class BookServiceImpl implements BookService { @Autowired
private JdbcTemplate jdbcTemplate; @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖 扣除库存数量
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql 将赚到的钱添加到account表中的balance
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params); testUpdate(); jdbcTemplate.update(addRmbSql, params); throw new RuntimeException("故意的一个异常");
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void testUpdate() {
//这个业务没什么意义,只是用来测试REQUIRES_NEW的 当执行后SpringMVC这本书库存-1
String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
Object []params = {1,"SpringMVC"};
jdbcTemplate.update(sql, params); }

三张表分别是对应account表,book表,book_stock表

 private static  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/*.xml");

     @Test
public void testREQUIRES_NEW() { BookService bean = ac.getBean(BookService.class); bean.update();
}

结果是无论是方法1还是方法2都回滚了,那么REQUIRES_NEW就不起作用了,为了探索原因我修改了一下代码

在第5行的地方打印出对象的类型是什么

 @Test
public void testREQUIRES_NEW() { BookService bean = ac.getBean(BookService.class);
System.out.println("update的调用者:"+bean.getClass());
bean.update();
}

在第11行的地方打印对象类型

 @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params);
System.out.println("testUpdate的调用者:"+this.getClass());
testUpdate(); jdbcTemplate.update(addRmbSql, params); throw new RuntimeException("故意的一个异常");
}

运行结果是

显然调用update的对象是一个代理对象,调用testUpdate的对象不是一个代理对象,这就是为什么添加REQUIRES_NEW不起作用,想要让注解生效就要用代理对象的方法,不能用原生对象的.

解决方法:在配置文件中添加标签<aop:aspectj-autoproxy   expose-proxy="true"></aop:aspectj-autoproxy>将代理暴露出来,使AopContext.currentProxy()获取当前代理

将代码修改为

        <!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 将代理暴露出来 -->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

  11 12行将this替换为((BookService)AopContext.currentProxy())

     @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params);
System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
((BookService)AopContext.currentProxy()).testUpdate(); jdbcTemplate.update(addRmbSql, params); throw new RuntimeException("故意的一个异常");
}

运行结果

调用的对象变成代理对象了  那么结果可想而知第一个事务被挂起,第二个事务执行完提交了 然后异常触发,事务一回滚  SpringMVC这本书库存-1,其他的不变

我还看到过另一种解决方法

在第7行加一个BookService类型的属性并且给个set方法,目的就是将代理对象传递过来...    看26 27行显然就是用代理对象去调用的方法   所以就解决问题了   不过还是用第一个方案好

 @Service
public class BookServiceImpl implements BookService { @Autowired
private JdbcTemplate jdbcTemplate; private BookService proxy; public void setProxy(BookService proxy) {
this.proxy = proxy;
} @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params);
/* System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
((BookService)AopContext.currentProxy()).testUpdate();*/ System.out.println(proxy.getClass());
proxy.testUpdate(); jdbcTemplate.update(addRmbSql, params); throw new RuntimeException("故意的一个异常");
}

OK这个问题解决那就下一个

场景2:在一个服务层里面方法1和方法2都加上事务,其中方法二设置上propagation=Propagation.REQUIRES_NEW,方法1调用方法2并且在执行方法2时抛出一个异常     没注意看是不是觉得两个场景是一样的,因为我是拷贝下来改的...   差别就是在哪里抛出异常  这次是在方法2里面抛出异常, 我将代码还原至场景1的第一个解决方案,然后在方法2里面抛出异常 代码如下

 @Service
public class BookServiceImpl implements BookService { @Autowired
private JdbcTemplate jdbcTemplate; @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params); System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
((BookService)AopContext.currentProxy()).testUpdate(); jdbcTemplate.update(addRmbSql, params); }
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void testUpdate() {
//这个业务没什么意义,只是用来测试REQUIRES_NEW的
String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
Object []params = {1,"SpringMVC"};
jdbcTemplate.update(sql, params); throw new RuntimeException("在方法二里面抛出一个异常");
}

预期结果是testUpdate这个事务是要回滚的,update这个方法的事务正常执行,所以数据库的变化是balance字段的钱要+60  Spring这本书的库存-1,但是结果是数据库完全没有变化

分析:在testUpdate方法内抛异常被spring aop捕获,捕获后异常又被抛出,那么异常抛出后,是不是update方法没有手动捕获,而是让spring aop自动捕获,所以在update方法内也捕获到了异常,因此都回滚了

这张图片的代码是我debug模式下  在testUpdate方法中执行到抛出异常的地方  再点step over 跳到的地方   显然spring aop捕获到了异常后,再次抛出,这就是为什么update方法会捕获到异常

OK问题很简单   解决方案也很简单   只需要手动捕获该异常,不让spring aop捕获就OK了

将update方法改为

 @Transactional(timeout=4)
public void update() {
// TODO Auto-generated method stub
//售卖
String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
//入账的sql
String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
Object []params = {1,"Spring"}; jdbcTemplate.update(sellSql, params); try {
System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
((BookService)AopContext.currentProxy()).testUpdate();
} catch (RuntimeException e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
e.printStackTrace();
} jdbcTemplate.update(addRmbSql, params); }

执行结果    update执行成功   testUpdate回滚

总结:同一个Service不同事务的嵌套会出现调用的对象不是代理对象的问题,如果是多个不同Service的不同事务嵌套就没有这个问题。场景2的要记得手动捕获异常,不然全回滚了.至于为什么调用testUpdate方法的对象不是代理对象,可能还要看源码,懂的人可以在评论区分享一下。

如果有错误,请评论区指正

spring事务传播行为之使用REQUIRES_NEW不回滚的更多相关文章

  1. spring事务传播机制实例讲解

    http://kingj.iteye.com/blog/1680350   spring事务传播机制实例讲解 博客分类:   spring java历险     天温习spring的事务处理机制,总结 ...

  2. spring 事务传播机制

    spring 事务 传播机制 描述的 事务方法直接相互调用,父子事物开启,挂起,回滚 等的处理方式. 绿色的 那几个 我认为比较重要. 1 , @Transactional(propagation=P ...

  3. Spring事务传播属性介绍(一).required 和 reuqires_new

    Mandatory.Never.Not_Support传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10260030.html Nested传播 ...

  4. spring 事务传播行为实例分析

    Spring事务传播行为: spring特有的事务传播行为,spring支持7种事务传播行为,确定客户端和被调用端的事务边界(说得通俗一点就是多个具有事务控制的service的相互调用时所形成的复杂的 ...

  5. Spring 事务传播行为的使用

                                                                                                        ...

  6. 理解 spring 事务传播行为与数据隔离级别

    事务,是为了保障逻辑处理的原子性.一致性.隔离性.永久性. 通过事务控制,可以避免因为逻辑处理失败而导致产生脏数据等等一系列的问题. 事务有两个重要特性: 事务的传播行为 数据隔离级别 1.事务传播行 ...

  7. spring事务传播行为的思考

    1.问题 @TransactionConfiguration(transactionManager = "txManager", defaultRollback = false) ...

  8. spring事务传播行为讲解转载

    https://segmentfault.com/a/1190000013341344 前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为.事务传播行为是 ...

  9. Spring事务传播行为详解

    前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为.事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为.这是Spring ...

随机推荐

  1. Ubuntu 14.04 LTS 初装成

    原先博客放弃使用,几篇文章搬运过来 Windows 7下使用win32diskimager 制作启动盘,安装Ubuntu OS安装完成后,安装DrclientLinux. 安装搜狗输入法 Linux下 ...

  2. C++基础笔记(int转string)

    int a = 23; stringstream ss; ss << a; string s1 = ss.str(); 头文件需添加#include "sstream"

  3. 添加自己的component

    在之前讲解esp-idf的文件结构时,曾经讲过component是esp-idf集成的功能块,这篇文章就来讲解下,如何在esp-idf 中添加自己的component. STEP1; 创建compon ...

  4. django自定义user认证系统

    第一种,彻底推翻django的user认证系统,重新定义我们想要的字段 from django.contrib.auth.models import AbstractBaseUser,Permissi ...

  5. RabbitMQ基本理论

    本节内容 一  RabbitMQ介绍 二  RabbitMQ安装配置 三  RabbitMQ的Python实现-pika 1. 生产者消费者 2. 工作队列 3. 持久化和公平分发 4. 发布与订阅 ...

  6. XE下显示托盘图标(TrayIcon)

    https://www.cnblogs.com/studypanp/p/4930619.html XE下显示托盘图标(TrayIcon)   1.拖一个TrayIcon控件 2.拖一个Applicat ...

  7. Hadoop 综合揭秘——MapReduce 基础编程(介绍 Combine、Partitioner、WritableComparable、WritableComparator 使用方式)

    前言 本文主要介绍 MapReduce 的原理及开发,讲解如何利用 Combine.Partitioner.WritableComparator等组件对数据进行排序筛选聚合分组的功能.由于文章是针对开 ...

  8. 【SP1811】 LCS - Longest Common Substring(SAM)

    传送门 洛谷 Solution 考虑他要求的是最长公共子串对吧,那么我们对于一个串建后缀自动机,另一个串在后缀自动机上面跑就是了. 复杂度\(O(n+m)\)的,很棒! 代码实现 代码戳这里

  9. 42_并发编程-JionableQueue

    一.生产者消费者模型   在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程.在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生 ...

  10. cad.net cad启动慢? cad2008启动慢? cad启动延迟? cad卡住? cad98%卡? 默认打印机!!

    默认打印机是不是联网打印机,如果cad找不到这个打印机将会很慢才打开cad的界面(它真的不是卡死了,而是找不到...) 奇妙的是桌子至今都没有利用新建线程的方式来控制这个打印机等待,而是直接在主程序上 ...