Spring的声明式事务@Transactional注解的6种失效场景
一.Spring事务管理的两种方式事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。 编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例: try {//TODO something transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);throw new InvoiceApplyException("异常失败");} 声明式事务:是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。 显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。 和编程式事务相比,声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。 声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二是基于@Transactional注解: @Transactional @GetMapping("/test") public String test() { int insert = cityInfoDictMapper.insert(cityInfoDict); } 二、@Transactional注解介绍1、@Transactional注解可以作用于哪些地方? @Transactional 可以作用在接口、类、类方法。
2、@Transactional注解有哪些属性? propagation属性 事务的传播行为,默认值为 Propagation.required,属性信息如下:
isolation 属性 事务的隔离级别,默认值为 Isolation.DEFAULT。
timeout 属性 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 readOnly 属性 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 rollbackFor 属性 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。 noRollbackFor属性 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。 三、@Transactional的6种失效场景1、@Transactional 应用在非 public 修饰的方法上 如果Transactional注解应用在非 public 修饰的方法上,Transactional将会失效。 是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法 或 JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。 protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;} 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。 注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容易犯错的一点。 2、@Transactional 注解属性 propagation 设置错误 这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
3、@Transactional 注解属性 rollbackFor 设置错误 rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务,其他异常不会触发回滚事务。 // 希望自定义的异常可以进行回滚@Transactional(propagation= Propagation.required,rollbackFor= MyException.class 若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。 4、同一个类中方法调用,导致@Transactional失效 开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。 那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 //@Transactional @GetMapping("/test") private Integer A() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); /** * B 插入字段为 3的数据 */ this.insertB();/** * A 插入字段为 2的数据 */int insert = cityInfoDictMapper.insert(cityInfoDict);return insert;}@Transactional()public Integer insertB() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("3");cityInfoDict.setParentCityId(3);return cityInfoDictMapper.insert(cityInfoDict);} 5、异常被catch捕获导致@Transactional失效 这种情况是最常见的一种@Transactional注解失效场景 @Transactionalprivate Integer A() throws Exception {int insert = 0;try {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("2");cityInfoDict.setParentCityId(2);/** * A 插入字段为 2的数据 */insert = cityInfoDictMapper.insert(cityInfoDict);/** * B 插入字段为 3的数据 */b.insertB();} catch (Exception e) {e.printStackTrace();}} 如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚了,会抛出异常: org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。 spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。 在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。 6、数据库引擎不支持事务 这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MysqL数据库默认使用支持事务的innodb引擎,一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。 参考博客: 一口气说出 6种 @Transactional 注解失效场景 @Transactional事务注解的实际应用(多数据源切换和动态事务) https://blog.csdn.net/j1231230/article/details/108602206 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |