Spring 事务和事务传播机制

x33g5p2x  于2022-06-27 转载在 Spring  
字(4.4k)|赞(0)|评价(0)|浏览(758)

1. Spring 事务的实现

Spring 中事务的实现有两种方法:

  1. 手动操作事务
  2. 声明式自动提交事务

1.1 Spring 手动操作事务

Spring 手动操作事务, 一共三个重要步骤: ①开启事务,②提交事务,③回滚事务

@RestController
public class UserController2 {
    @Resource
    private UserService userService;
    // JDBC 事务管理器
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;
    // 定义事务属性
    @Resource
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/save")
    public Object save(User user) {
        // 1. 开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        // 插入数据库
        int result = userService.save(user);
        // 2. 提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        // 3. 回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        return result;
    }
}

1.2 Spring 声明式事务

在方法上添加 @Transactional 注解就可以实现了
无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务

@RestController
public class UserController2 {
    @Resource
    private UserService userService;
    
    @RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        int result = userService.save(user);
        return result;
    }
}

1.2.1 @Transactional 作用范围

@Transactional 可以用来修饰方法或类:

  • 修饰方法的时候, 需要注意只能应用到 public 方法上, 否则不生效.
  • 修饰类时, 表明该注解对该类中所有的 public 方法都生效.

1.2.2 @Transactional 参数说明

参数作用
value当配置了多个事务管理器时, 可以使用该属性指定选择哪个事务管理器
transactionManager当配置了多个事务管理器时, 可以使用该属性指定选择哪个事务管理器.
propagation事务的传播行为. 默认为 Propagation.REQUIRED
isolation事务的隔离级别. 默认为 Isolation.DAEFAULT
timeout事务的超时事件. 默认值为-1, 如果超过该时间限制但事务还没有完成, 则自动回滚事务.
readOnly指定事务是否只读事务. 默认为 false. 为了忽略那些不需要事务的方法, 比如读取数据, 可以设置为 read-only 为 true
rollbackFor用于指定能够触发事务回滚的异常类型, 可以指定多个异常类型
rollbackForClassName用于指定能够触发事务回滚的异常类型, 可以指定多个异常类型
noRollbackFor抛出指定的异常类型, 不回滚事务, 也可以指定多个异常类型
noRollbackForClassName抛出指定的异常类型, 不回滚事务, 也可以指定多个异常类型

1.2.3 异常被捕获, 不会事务回滚

当前的代码:

@RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        int result = userService.save(user);
        try {
            int a = 10/0;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

当前数据库表中的内容:

运行之后表中的内容

此时发现, 出现异常的情况, 不会发生事务回滚

① 解决办法 — 将异常重新抛出
@RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        int result = userService.save(user);
        try {
            int a = 10/0;
        }catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        return result;
    }

此时数据库中也不会有数据

② 解决办法 — 手动回滚事务

使用 TransctionAspectSupport.currentTransactionStatus() 可以得到当前事务
然后使用 setRollbackOnly 就可以实现回滚.

@RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        int result = userService.save(user);
        try {
            int a = 10/0;
        }catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

2. Spring 事务的隔离级别

2.1 Spring事务的隔离级别有 5 种

  1. Isolation.DEFAULT: 以连接的数据库的事务隔离级别为主
  2. Isolation.READ_UNCOMMITTED: 读未提交, 可以读取到未提交的事务, 存在脏读
  3. Isolation.READ_COMMITTED: 读已提交, 只能读取到已经提交的事务, 解决了脏读, 但存在不可重复读
  4. Isolation.REPEATABLE_READ: 可重复读, 解决了不可重复读, 但存在幻读
  5. Isolation.SERIALIZABLE: 串行化, 可以解决所有并发问题, 但性能低

2.2 Spring 中设置事务隔离级别

只需要通过 @Transactional 中的 isolation 属性进行设置即可

3. Spring 事务的传播机制

Spring 事务传播机制定义了多个包含了事务的方法, 相互调用时, 事务是如何在这些方法间进行传递的
事务隔离级别 保证多个并发事务执行的可控性
 
事务传播机制 保证一个事务在多个调用方法间的可控性

3.1 事务传播机制有哪些?

Spring 事务的传播机制包含以下 7 种 :

  1. Propagation.REQUIRED: 默认的事务传播级别, 它表示如果当前存在事务, 则加入该事务; 如果当前没有事务, 则创建一个新的事务.
  2. Propagation.SUPPORTS: 如果当前存在事务, 则加入该事务; 如果当前没有事务, 则以非事务的方式继续运行.
  3. Propagation.MANDATORY: 如果当前存在事务, 则加入该事务; 如果当前没有事务, 则抛出异常.
  4. Propagation.REQUIRES_NEW: 表示创建一个新的事务, 如果当前存在事务, 则把当前事务挂起.
  5. Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起.
  6. Propagation.NEVER: 以非事务方式运行, 如果当前存在事务, 则抛出异常.
  7. Propagation.NESTED: 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行; 如果不存在事务, 则创建一个新的事务.

3.2 代码演示

3.2.1 支持当前事务

@RestController
public class UserController2 {
    @Resource
    private UserService userService;
    @Resource
    private LogService logService;

    @RequestMapping("/save")
    @Transactional(propagation = Propagation.REQUIRED)
    public Object save(User user) {
        int result = userService.save(user);
        logService.saveLog("用户插入: " + user.getUsername());
        return result;
    }
}

UserService 代码 和 LogService 代码

运行结果:

发现 LogSerivice 出错, 导致 LogService操作回滚, 也导致 UserService 报错

3.2.2 不支持当前事务

修改 UserServiceLogServicePropagation.REQUIRES_NEW

运行结果:

User表中插入成功, Log表中插入失败.

3.2.3 不支持当前事务, NEVER 抛异常

修改 LogServicePropagation=Propagation.NEVER

将 log的代码移到前面

@RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        logService.saveLog("用户插入: " + user.getUsername());
        int result = userService.save(user);
        return result;
    }

运行结果:
执行到 logService.saveLog 这段就报错. 就不会继续往下走了.

3.2.4 嵌套事务

修改 LogServiceUserService 代码为 Propagation.NESTED

运行结果:

用户表和日志表都没有添加任何的数据

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

3.3.1 嵌套事务

将嵌套事务中的 LogService 中进行当前事务的回滚操作.

运行结果:
User表操作成功, Log表操作失败.

3.3.2 加入事务

还是在 LogService 中 执行回滚操作.

运行结果:

此时 User表插入数据失败, Log表插入数据也失败.

3.3.3 总结

嵌套事务 (NESTED)加入事务 (REQUIRED) 的区别

  • 整个事务如果全部执行成功, 二者的结果是一样的.
  • 如果事务执行到一半失败了, 那么加入事务整个会全部回滚; 而嵌套事务会局部回滚, 不会影响上一个方法中执行的结果.

相关文章