首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >有必要再重温Spring事务的传播机制了

有必要再重温Spring事务的传播机制了

原创
作者头像
半月无霜
发布2025-02-28 16:08:02
发布2025-02-28 16:08:02
2310
举报
文章被收录于专栏:半月无霜半月无霜

一、前言

在公司的项目上,有个多数据源的功能,它通过注解完成实现,在执行一个Dao方法的时候,去走指定的数据库。

这个大家应该应该都不陌生,后面可以看看分享一波。

我项目中,多数据源主要是用于读写分离,尤其是一些读取的SQL全部都走到了读库。

但今天这个问题,有点诡异为什么明明加了@Ds注解,但还是走到了主库,注解没有生效?

这实际上很简单,因为这个注解是在非事务的条件下使用的,一但所处的环境是事务包裹着的话,就会失效

所以,只需要在Ds注解上,再加个@Transactional(propagation = Propagation.NOT_SUPPORTED)即可,这样就可以解决这个问题

这涉及到Spring事务的传播机制,我们来重温一下

二、事务的传播机制

事务的传播机制,简单的来说,就是一个方法,调用另一个方法。原本就有的事务,在遇到一个新的事务后会发生什么机制。这就是我们要讲得事务传播机制。

通过@Transactionalpropagation属性我们可以进行配置事务的传播机制,我们先看看这个枚举里面都有些什么

代码语言:javascript
复制
 package org.springframework.transaction.annotation;
 ​
 public enum Propagation {
     REQUIRED(0),
     SUPPORTS(1),
     MANDATORY(2),
     REQUIRES_NEW(3),
     NOT_SUPPORTED(4),
     NEVER(5),
     NESTED(6);
 ​
     private final int value;
 ​
     private Propagation(int value) {
         this.value = value;
     }
 ​
     public int value() {
         return this.value;
     }
 }

简简单单,没有说明,一个一个来看吧。先写出两个Dao,一会测试用

代码语言:javascript
复制
 package com.banmoon.test.dao;
 ​
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Repository;
 ​
 @Repository
 public class UserDao {
 ​
     @Autowired
     private JdbcTemplate jdbcTemplate;
 ​
     @Transactional
     public void insert(){
         jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')");
     }
 ​
 }
代码语言:javascript
复制
 package com.banmoon.test.dao;
 ​
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Repository;
 ​
 @Repository
 public class PlatformDao {
 ​
     @Autowired
     private JdbcTemplate jdbcTemplate;
 ​
     @Transactional
     public void insert(){
         jdbcTemplate.execute("INSERT INTO `platform`(`name`) VALUES ('博客平台')");
     }
 ​
 }

1)REQUIRED(默认)

这是spring默认的事务传播机制。

  • 如果外部方法已经有事务了,那么本方法将加入这个事务
  • 如果外部方法没有事务,那么本方法就自己建事务

我们写一个Service去调用上面两个Dao

代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     // 如果外部方法已经有事务了,那么本方法将加入这个事务
     public void insert(){
         // 此处将创建事务
         userDao.insert();
         // 此处将创建事务,如果发生异常,只会回滚自己的事务
         platformDao.insert();
     }
 ​
 }
代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
      * 如果发生异常,导致外部方法中整个事务的回滚
      */
     @Transactional
     public void insert(){
         // 不创建事务了,加入到外部事务
         userDao.insert();
         // 不创建事务了,加入到外部事务
         platformDao.insert();
     }
 ​
 }

2)SUPPORTS

  • 如果外部方法已经有事务了,那么本方法将加入这个事务
  • 如果外部方法没有事务,那么本方法就以非事务的方式执行,不再创建事务
代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
      * 如果发生异常,导致外部方法中整个事务的回滚
      */
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
         platformDao.insert();
     }
     
     /**
      * 外部方法没有事务,里面的两个方法不再会创建事务,直接以非事务的方式运行
      */
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
         platformDao.insert();
     }
 ​
 }

3)MANDATORY

  • 如果外部方法已经有事务了,那么本方法将加入这个事务
  • 如果外部方法没有事务,将抛出异常
代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
      * 如果发生异常,导致外部方法中整个事务的回滚
      */
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
         platformDao.insert();
     }
     
     /**
      * 外部方法没有事务,里面直接会抛出异常
      */
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
         platformDao.insert();
     }
 ​
 }

来看看异常吧

image-20220307210837620
image-20220307210837620

4)REQUIRES_NEW

  • 如果外部方法已经有事务了,重新创建一个新的事务
  • 如果外部方法没有事务,重新创建一个新的事务

这也就是说,无论外部有无事务,内部方法都将会创建新的事务

代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 外部方法已经有事务了,内部方法还是会重新创建一个新的事务
      * 如果发生异常,只会导致自己所在的方法事务回滚
      * 这里创建了三个事务
      */
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
         platformDao.insert();
     }
     
     /**
      * 外部方法没有事务,内部方法会创建一个新的事务
      * 如果发生异常,只会导致自己所在的方法事务回滚
      * 这里创建了两个事务
      */
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
         platformDao.insert();
     }
 ​
 }

5)NOT_SUPPORTED

  • 如果外部方法已经有事务了,以非事务的方式运行
  • 如果外部方法没有事务,以非事务的方式运行

也就是说,无论外部方法有无事务,里面的方法执行都是没有事务的。

但我有点疑惑,如果里面方法抛出异常了,外部方法会怎么样。简单测试一下吧

代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 无论外部方法有无事务,里面的方法执行都是没有事务的
      */
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.NOT_SUPPORTED)
         userDao.insert();
         // 外部方法的执行
         jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
         // 这里的修饰是@Transactional(propagation = Propagation.NOT_SUPPORTED)
         platformDao.insert();
     }
 ​
 }

我执行了,结果是只有26行的数据插入被回退。

24行和28行是非事务运行的,就算异常了也不会回退

6)NEVER

  • 如果外部方法已经有事务了,抛出异常
  • 如果外部方法没有事务,以非事务的方式运行

SUPPORTS相反的一个传播机制

代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     /**
      * 外部方法已经有事务了,将抛出异常
      */
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.NEVER)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.NEVER)
         platformDao.insert();
     }
     
     /**
      * 外部方法没有事务,内部方法也不再创建,以非事务的方式运行
      */
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.NEVER)
         userDao.insert();
         // 这里的修饰是@Transactional(propagation = Propagation.NEVER)
         platformDao.insert();
     }
 ​
 }

7)NESTED

  • 如果外部方法已经有事务了,内部方法将创建事务,和外部事务组成嵌套事务
  • 如果外部方法没有事务,内部方法将创建事务

组成嵌套事务是什么意思呢? 我们将外部方法的事务称为父事务,内部方法创建的事务为子事务

  • 当子事务回滚时,不影响父事务
  • 当父事务回滚时,子事务一起回滚

这里同样,我们来进行测试一下

代码语言:javascript
复制
 package com.banmoon.test.service;
 ​
 import com.banmoon.test.dao.PlatformDao;
 import com.banmoon.test.dao.UserDao;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 @Service
 public class UserService {
 ​
     @Autowired
     private UserDao userDao;
 ​
     @Autowired
     private PlatformDao platformDao;
 ​
     @Autowired
     private JdbcTemplate jdbcTemplate;
 ​
 ​
     @Transactional
     public void insert(){
         // 这里的修饰是@Transactional(propagation = Propagation.NESTED)
         userDao.insert();
         // 外部方法的执行
         jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
         try {
             // 这里的修饰是@Transactional(propagation = Propagation.NESTED)
             platformDao.insert();// 此处将抛出空指针
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
     
     @Transactional
     public void insert1(){
         // 这里的修饰是@Transactional(propagation = Propagation.NESTED)
         userDao.insert();
         // 外部方法的执行
         jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
         // 这里的修饰是@Transactional(propagation = Propagation.NESTED)
         platformDao.insert();
         throw new NullPointException();// 此处将抛出空指针
     }
 ​
 }

执行insert(),这是子事务中抛出的异常。结果是只有30行的插入数据回滚。注意的就是,内部方法的异常要自己捕获,别被父事务发现了。如果发现了,大家就一起回滚吧。

执行insert1(),这是父事务中抛出的异常。结果发现所有插入的数据都回滚了。

三、最后

那么,为什么多数据源遇到了事务就会失效了呢,这还是因为dynamic-datasource切换数据源的原理就是实现了DataSource接口,实现了getConnection方法,只要处于事务中,@Ds标记的方法对其他数据源操作只会使用开启事务的数据源,因为开启事务数据源会被缓存下来,可以在DataSourceTransactionManagerdoBegin()方法中看见那个txObject,如果在一个事务内,就会复用Connection,所以切换不了数据源

image-20250228160237597
image-20250228160237597

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、事务的传播机制
    • 1)REQUIRED(默认)
    • 2)SUPPORTS
    • 3)MANDATORY
    • 4)REQUIRES_NEW
    • 5)NOT_SUPPORTED
    • 6)NEVER
    • 7)NESTED
  • 三、最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档