热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Spring声明式事务不回滚问题

疑问,确实像往常一样在service上添加了注解@Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码

疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。下面总结一下经验教训:

注:转自http://www.jianshu.com/p/f5fc14bde8a0

Spring事务的管理操作方法

  • 编程式的事务管理
    • 实际应用中很少使用
    • 通过使用TransactionTemplate 手动管理事务
  • 声明式的事务管理
    • 开发中推荐使用(代码侵入最少)
    • Spring的声明式事务是通过AOP实现的

主要掌握声明式的事务管理。

spring事务不回滚的两个原因

总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。

1. Service类内部方法调用

大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:

@Service
public class RabbitServiceImpl implements RabbitService {

    @Autowired
    private RabbitDao rabbitDao;
    @Autowired
    private TortoiseDao tortoiseDao;

    @Override
    public Rabbit methodA(String name){
        return methodB(name);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public boolean methodB(String name){
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);
        return true;
    }

}

单元测试代码如下:

public class RabbitServiceImplTest {

    @Autowired
    private RabbitService rabbitService;

    // 事务未开启
    @Test
    public void testA(){
        rabbitService.methodA("rabbit");
    }

    // 事务开启
    @Test
    public void testB(){
        rabbitService.methodB("rabbit");
    }
}

从上一节中可以看到,声明式事务是通通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。

对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图:

事务的调用原理

总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。

2. try...catch异常

在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:

@Transactional(propagation = Propagation.REQUIRED)
public boolean methodB(String name) throws BizException {
    try {
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);
    } catch (Exception e) {
        throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg);
    }
    return true;
}

BizException的定义如下:

public class BizException extends Exception {
    // 自定义异常
}

上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了Spring的官方文档,找到了答案。下面是翻译自Spring官网。

17.5.3 声明式事务的回滚

上一节中介绍了如何设置开启Spring事务,一般在你的应用的Service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。

在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。

  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常时RuntimeException或者是其子类,这样事务才会回滚(默认情况下Error也会导致事务回滚)。在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。

  注:Unchecked Exception包括Error与RuntimeException. RuntimeException的所有子类也都属于此类。另一类就是checked Exception。

  • 你可以精确的配置异常类型,指定此异常类事务回滚,包括 checked 异常。下面的xml代码片段展示了如何配置checked异常引起事务回滚,应用自定义异常类型:

  
  for="Exception"/>
  
  

与其有同等作用的注解形式如下:

@Transactional(rollbackForClassName={"Exception"})
或者
@Transactional(rollbackFor={Exception.class})
  • 在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException 异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。

  
  for="InstrumentNotFoundException"/>
  
  

  与其有同样作用的注解形式如下:   

@Transactional(noRollbackForClassName={"InstrumentNotFoundException"})
或者
@Transactional(noRollbackFor={InstrumentNotFoundException.class})
  • 还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException 之外的任何异常都会导致事务回滚。

  
  for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
  
  • 你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和Spring FrameWork 的事务框架代码紧密的绑定在一起,示例代码如下:
public void resolvePosition() {
  try {
      // some business logic...
  } catch (NoProductInStockException ex) {
      // trigger rollback programmatically
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
}

  如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean POJO-based architecture.(没懂...)

看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException。我的解决办法是,在注解@Transactional中添加 rollbackFor={BizException.class}。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。

 


推荐阅读
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文由编程笔记#小编整理,主要介绍了关于数论相关的知识,包括数论的算法和百度百科的链接。文章还介绍了欧几里得算法、辗转相除法、gcd、lcm和扩展欧几里得算法的使用方法。此外,文章还提到了数论在求解不定方程、模线性方程和乘法逆元方面的应用。摘要长度:184字。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
author-avatar
用户7kxpkjs2ol
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有