Spring事务传播机制

目录

1.回顾事务的隔离级别

数据库的事务隔离级别

spring事务隔离级别

2.什么是Spring事务传播机制?

3.设置事务传播级别

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW 

PROPAGATION_NESTED 


1.回顾事务的隔离级别

数据库的事务隔离级别

数据库提供了四种隔离级别供用户选择,包括 READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)、SERIALIZABLE(串行化)。

spring事务隔离级别

Spring 框架的事务隔离级别一共有五个,包括:

  1. DEFAULT:表示使用底层数据源的默认隔离级别,一般为数据库的 READ_COMMITTED 隔离级别。

  2. READ_UNCOMMITTED:表示读未提交,最低的隔离级别,不能保证事务的可靠性。

  3. READ_COMMITTED:表示读已提交,能够保证一个事务只会读取到已经提交的数据。

  4. REPEATABLE_READ:表示可重复读,保证在同一个事务中多次读取同一数据时,得到的结果是一致的。

  5. SERIALIZABLE:表示串行化,所有的事务依次顺序执行,可以避免以上三种并发问题。

使用 @Transactional 注解时,可以通过设置 isolation 属性来指定事务的隔离级别,示例如下:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount(int userId, double money) {
    // 更新账户余额
}

2.什么是Spring事务传播机制?

Spring 框架中的事务传播机制是为了解决多个事务方法之间相互协作的问题,以确保事务操作的一致性和完整性。

在实际的开发过程中,不同的业务逻辑可能会涉及到多个方法的调用,而这些方法有可能会出现嵌套调用的情况。如果不对这些嵌套调用的方法进行事务处理,就有可能会导致数据的不一致或者错误。

假设我们有一个电商系统,涉及到两个业务逻辑:创建订单和扣减库存。其中,创建订单的方法为 createOrder(),扣减库存的方法为 reduceStock()。现在,我们需要在一个事务中同时执行这两个方法,以确保订单和库存的一致性。这时就可以使用 Spring 框架中的事务传播机制来管理事务。

假设这两个方法都在 Service 层中实现,且使用注解方式开启事务。我们可以对 createOrder() 方法和 reduceStock() 方法分别设置不同的传播级别,例如:

@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {
  
  @Autowired
  private OrderDao orderDao;

  @Autowired
  private StockDao stockDao;

  // REQUIRED 传播级别
  @Transactional(propagation = Propagation.REQUIRED)
  public void createOrder(Order order) throws Exception {
    // 创建订单
    orderDao.insert(order);
    
    // 扣减库存
    reduceStock(order.getProductId(), order.getCount());
  }

  // REQUIRES_NEW 传播级别
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void reduceStock(Long productId, int count) throws Exception {
    // 扣减库存
    stockDao.reduce(productId, count);
  }
}

createOrder() 方法使用了 REQUIRED 传播级别,表示如果当前存在事务,则加入该事务进行处理;如果不存在事务,则开启一个新的事务。而 reduceStock() 方法则使用了 REQUIRES_NEW 传播级别,表示每次都开启一个新的事务进行处理。

假设在 createOrder() 方法中出现异常,导致事务回滚,那么 reduceStock() 方法执行的事务也会被回滚;而如果 reduceStock() 方法出现异常,只会回滚该方法中的事务,不会影响到 createOrder() 方法的事务。这样,就可以确保订单和库存的一致性。

Spring 框架中的事务传播机制可以帮助我们管理多个业务逻辑之间的事务,并根据需求设置不同的传播级别,从而确保事务操作的正确性和完整性。

Spring 框架中提供了多种事务传播级别,包括:

  1. PROPAGATION_REQUIRED 默认状态,如果当前没有事务,就新开启一个事务;否则使用当前事务。

  2. PROPAGATION_SUPPORTS 如果当前有事务,就使用该事务;否则不使用事务。

  3. PROPAGATION_MANDATORY 使用当前事务,如果当前不存在事务,就会抛出异常。

  4. PROPAGATION_REQUIRES_NEW 每次都新开启一个事务,且中断当前已经存在的事务。

  5. PROPAGATION_NOT_SUPPORTED 不支持事务,每次都在非事务状态下执行。

  6. PROPAGATION_NEVER 禁止事务,如果当前存在事务,就会抛出异常。

  7. PROPAGATION_NESTED 嵌套事务,如果当前存在事务,则在嵌套事务内执行。如果当前不存在事务,则开启一个新事务。

3.设置事务传播级别

PROPAGATION_REQUIRED

再看一个关于PROPAGATION_REQUIRED的示例:
当用户表中新增了用户信息时,创建一个日志表记录新增用户的id,time,message

日志表: 

mysql> CREATE TABLE `log` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    ->   `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    -> ;
ERROR 1050 (42S01): Table 'log' already exists
mysql> select*from log;
Empty set (0.00 sec)

mysql> desc log;
+-----------+-----------+------+-----+-------------------+----------------+
| Field     | Type      | Null | Key | Default           | Extra          |
+-----------+-----------+------+-----+-------------------+----------------+
| id        | int(11)   | NO   | PRI | NULL              | auto_increment |
| timestamp | timestamp | NO   |     | CURRENT_TIMESTAMP |                |
| message   | text      | NO   |     | NULL              |                |
+-----------+-----------+------+-----+-------------------+----------------+
3 rows in set (0.01 sec)

用户表:

mysql> select *from userinfo
    -> ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.01 sec)

接下来用到的方法会进行嵌套调用,默认情况下时PROPAGATION_REQUIRED对这些嵌套调用的方法进行事务处理

Spring 事务传播机制的默认值是 REQUIRED。

加入事务:如果当前没有事务,那么被调用的事务方法会开启一个新的事务;如果当前已经有了事务,那么被调用的事务方法会加入到当前事务中,与当前事务同步提交或回滚。

@Data
public class Log {
    private int id;
    private LocalDateTime timestamp;
    private String message;
}
@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}

@RestController
@RequestMapping("/user")
public class Controller3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username,String password){
        if(null == username || null == password
            || username.equals("") || password.equals("")) return 0;
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);
        return result;
    }
}


@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public  int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: "+ result);
        int num = 100/0;
        return result;
    }

}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LogService logService;

    public int del(Integer id) {
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
        //给用户添加信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: "+ addUserResult);
        //给用户添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return  addUserResult;
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.LogMapper">

    <insert id="add">
        insert into log(`message`) values(#{message})
    </insert>
</mapper>


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <delete id="del">
        delete from userinfo where id = #{id}
    </delete>

    <insert id="add">
        insert into userinfo(username,password) values
        (#{username},#{password})
    </insert>
</mapper>

 执行:

 sql执行日志:

数据库中结果:


 

分析一下原因:

整个执行过程的方法调用链:

 异常在logservice出现后进行了事物的回滚。由于使用的是PROPAGATION_REQUIRED对这些嵌套调用的方法进行事务处理

调用链上的方法都加入到了同一个事务中,因此一处出现了异常。整个调用链上的所有方法都会进行回滚。

从而即使添加用户行为没发生异常,但由于其调用了出现异常的日志添加方法,他也跟着回滚了

我们的预期执行结果:在一个调用链上的事务,各自的执行结果相互不干扰

PROPAGATION_REQUIRES_NEW 

也即在上述例子中,用户添加事务和日志添加事务相互不影响,一个出现异常了,另一个不会跟随着回滚

使用到的传播机制:REQUIRES_NEW

表示当前方法必须开启一个新的事务运行,如果当前已经有事务,则挂起当前事务

改动代码:

对日志方法手动回滚,否则出现异常代码,不处理异常,整个调用链都会报错,感知到后都进行回滚了

将其他方法事务传播机制修改为:
@Transactional(propagation = Propagation.REQUIRES_NEW)

对日志方法手动回滚
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public  int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: "+ result);
        //回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        //int num = 100/0;
        return result;
    }

}

 

 用户添加成功,日志进行回滚了

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
| 20 | zhaoliu  | 123456   |       | 2023-05-31 11:31:51 | 2023-05-31 11:31:51 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
4 rows in set (0.00 sec)

mysql> select*from log;
Empty set (0.00 sec)

将REQUIRES_NEW换为REQUIRED 默认状态执行又会出现报错且全部回滚的情况

报错: Transaction rolled back because it has been marked as rollback-only

内部事务回滚,外部事务也会回滚,但会报异常

如果外部事务回滚,内部事务会跟着回滚,不会报异常

 数据库表还是没有任何改变,全部回滚

PROPAGATION_NESTED 

嵌套事务:PROPAGATION_NESTED ,如果当前存在事务,则在嵌套事务内执行。如果当前不存在事务,则开启一个新事务。

@Transactional(propagation = Propagation.NESTED)

将日志手动回滚

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi3    | 456789   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
| 20 | zhaoliu  | 123456   |       | 2023-05-31 11:31:51 | 2023-05-31 11:31:51 |     1 |
| 23 | zhaoliu2 | 123456   |       | 2023-05-31 12:33:36 | 2023-05-31 12:33:36 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
5 rows in set (0.00 sec)

mysql> select*from log;
Empty set (0.00 sec)

结果:只回滚日志事务,用户添加事务没有回滚

嵌套事务和加入事务的区别

两种方式的区别主要在于事务的隔离级别和提交方式:

  • 隔离级别:在嵌套事务中,子事务具有独立的隔离级别,并且可以根据需要进行回滚或提交;而在加入事务中,所有事务共享同一个隔离级别,且只有一个总事务进行提交或回滚。
  • 提交方式:在嵌套事务中,子事务的提交或回滚操作不会对外部事务产生影响;而在加入事务中,所有事务共享同一个提交或回滚操作,即只有总事务提交或回滚。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoLo♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值