Spring事务和事务传播机制

目录

一. Spring 中事务的实现

1. 编程式事务

2. 声明式事务

 3. @Transaction 参数的设置

4. @Transaction 的隔离级别

5. @Transaction 的工作原理 

二. Spring 事务传播机制

七种事务传播机制

 支持当前事务

 不支持当前事务

 嵌套事务


一. Spring 中事务的实现

1. 编程式事务

编程式事务,主要就是三个步骤:

1. 开启事务;

2. 提交事务;

3. 回滚事务;

在 SpringBoot 中内置了两个对象:DataSourceTransactionManager 用来开启事务,提交事务和回滚事务,TransactionDefinition 是事务的属性,在开启事务的时候,就需要传入此对象,从而获得一个事务 TransactionStatus;


@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

//    编程式事务     要引入两个内置的对象
//    整个事务的操作,是需要到这个事务管理器对象的:DataSourceTransactionManager
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
//    事务定义器,通过这个来设置事务的一些东西,要得到一个事务,就必须传递此参数
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public int del(Integer id) {
        if (id == null || id <= 0) {
            return 0;
        }
//          开启事务
        TransactionStatus transactionStatus = null;
        int result = 0;
        try {
//            开启事务
            transactionStatus = transactionManager.getTransaction(transactionDefinition);
            //            业务操作,删除用户
            result = userService.del(id);
            System.out.println("删除:" + result);
//           正常情况下,提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
//           如果出现异常了,回滚事务, 或者事务还没创建,就不用回滚
            if (transactionStatus != null) {
                transactionManager.rollback(transactionStatus);
            }
        }
        return result;
    }
}

2. 声明式事务

相比于编程式事务,声明式事务就方便得多了,只需要在方法或类上添加注解 @Transaction

不需要手动开启和提交事务,进入方法就自动开启事务,方法执行完就自动提交事务,如果中途发生没有处理的异常,就会自动回滚事务;

需要注意的是:

@Transactional 修饰方法的时候,只能修饰在 public 方法上;

修饰类的时候,表示该注解对类中的所有 public 方法生效; 

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;

    @RequestMapping("/del")
    @Transactional      // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
    public int del(Integer id){
        if (id == null || id <= 0){
            return 0;
        }
        int result = userService.del(id);
        System.out.println("删除:"+result);
        int num = 10 / 0;        // 制造算数异常                  
        return result;
    }

}

 此时因为在运行过程中,出现了算数异常,所以事务会进行回滚,页面会以 500 的形式返回;

但如果对于异常进行 try/catch 处理的时候,情况就不一样了,如果是我们自己去抓取了异常,没有让异常抛出,此时就相当于是我们自己把异常消化了,外部监测不到,就不会回滚事务了,而是正常提交事务; 

对此就有两种解决方法:

1. 将异常抛出,让框架感知到异常,当框架感知到异常之后,就会自动进行回滚;

2. 手动进行回滚事务;

       try {
            int num = 10 / 0;
        }catch (Exception e){
//            e.printStackTrace();          // 不回滚
//            throw e;                      // 回滚
//            手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

 3. @Transaction 参数的设置

4. @Transaction 的隔离级别

在前面的文章中,有说到事务的四大特性:原子性,一致性,持久性,隔离性。在隔离性中,也有五种隔离级别:

1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主;
2. Isolation.READ_UNCOMMITTED:读未提交;
3. Isolation.READ_COMMITTED:读已提交;
4. Isolation.REPEATABLE_READ:可重复读;
5. Isolation.SERIALIZABLE:串行化;
此处的细节内容可以见前面的文章。

5. @Transaction 的工作原理 

@Transaction 是基于 AOP 来实现的,AOP 又是使用动态代理来实现的,如果目标对象实现了接口, 默认情况下会采用 JDK 的动态代理,如果目标对象没有实现接口,会采用 CGLIB 动态代理;

@Transaction 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务,如果中途遇到异常,则回滚事务;

二. Spring 事务传播机制

Spring 事务传播机制定义了多个包含了事务的方法,相互调用的时候,事务是如何在这些方法间进行传递的。

事务的隔离级别是保证多个事务并发执行的稳定性,事务的传播机制是保证一个事务在多个调用方法间的稳定性; 

 

事务传播机制解决: 

七种事务传播机制

1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。

2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以非事务的方式继续运⾏。
3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不⼲扰。
5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

也可以进行整体划分:

此处写一个 添加用户和添加日志 的案例进行演示:

首先在 Controller 层中的 add 方法进行 @Transaction 注解添加,然后调用 Service 层的 add 方法,Service 层的 add 方法也添加了 @Transaction 注解,然后调用 logService.add() 方法进行日志打印,logService.add() 方法也添加了 @Transaction 注解;

( 此处为了方便分析,用 Controller 调用了 Service,后 Service 再次调用 Service 层的方法)

 支持当前事务

@RestController
@RequestMapping("/user3")
public class UserController3 {

    @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 user = new UserInfo();
        user.setUsername(username);
        user.setPassword(password);
        int result = userService.add(user);
//        外部事务的回滚,不会报错误
//        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
//        给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果:"+addUserResult);

//        添加日志信息
        LogInfo logInfo = new LogInfo();
        logInfo.setMessage("添加用户信息");
        logService.add(logInfo);

        return addUserResult;
    }
}
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(LogInfo log){
        int result = logMapper.add(log);
        System.out.println("添加日志:" + result);
//        设置回滚操作    内部事务的回滚会报错误
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}

此时的事务传播机制属于 支持当前事务 , 也就是可以将三个事务看为是一个事务,同时提交,同时回滚,在程序中,前两个事务都正常运行,然后最后一个事务进行回滚,此时整个事务都会进行回滚,也就是既没有添加用户,也没有添加日志。

此处需要注意:内部事务的回滚会以报错误的形式出现,显示 500 页面,外部事务的回滚则不会,上述是内部事务的回滚,所以显示的页面是:

 不支持当前事务

将 LogService 中的 add 方法,也就是日志添加的方法,隔离级别设为 REQUIRES_NEW 。

此时该事务就与前面两个事务没有任何联系,不在一个调用链上的事务,各自执行相互不干扰。独自进行提交事务,回滚事务。

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(LogInfo log){
        int result = logMapper.add(log);
        System.out.println("添加日志:" + result);

        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        return result;
    }
}

此时在该方法中进行回滚操作,外部事务都不回滚,看一下执行结果:日志记录没有添加,而用户信息添加了;

需要补充的是:在 不支持当前事务 的机制中,虽然两个事务不在一个调用链上,但如果内层事务出现了异常,而没有及时处理,让外层的 Controller 和 Service 都感知到了,会导致整个程序报错,整个调用链都是500,虽然都是独自的业务,但是感知到异常的时候,都是会进行回滚的;

按照上述代码,如果在 LogService 中 (或者 UserService )的 add 方法加入 int num = 100/0 语句形成算数异常,此时内层事务对异常的未处理会导致外层也受影响,页面显示 500,日志 和 用户信息 都无法添加成功; 

但如果是外层事务的异常未处理,也会造成 500 页面显示,但不会影响到内层事务的业务执行;

按照上述代码,如果在 UserController3 中的 add 方法加入 int num = 100/0 语句形成算数异常,此时日志可以正常添加,而用户信息无法添加,因为异常的出现而回滚事务了,页面显示为 500;

 嵌套事务

嵌套事务,在进行回滚的时候,就只会影响到自己的内层事务,外层事务是影响不到的,也就是不会回滚嵌套之前的事务;

在上述代码中,LogService 中的事务,就算是 UserService 的内层事务,所以 UserService 中的事务设置为 嵌套事务 的时候,进行回滚就只会影响到 LogService 中的事务,会一起回滚,而最外层的 UserController 事务不会被影响到。

得到的结果也就是:用户信息添加失败,日志信息添加失败;

如果是对最里层事务 LogService 设置为 嵌套事务,回滚发生在 LogService 中,那么此时用户信息是可以添加成功的,但日志信息添加失败;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;


    @Transactional(propagation = Propagation.NESTED)
    public int add(UserInfo userInfo){
//        给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果:"+addUserResult);

        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

//        添加日志信息
        LogInfo logInfo = new LogInfo();
        logInfo.setMessage("添加用户信息");
        logService.add(logInfo);

        return addUserResult;
    }
}

嵌套事务之所以能实现部分事务的回滚,是因为事务中有一个保存点的概念,嵌套事务进入之后就相当于新建了一个保存点,而回滚的时候,只会回滚到保存点,因此之前的事务是不受影响的。

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

1. 如果事务全部执行成功,二者的结果是一样的;

2. 如果事务执行到一半失败了,加入事务整个事务都会回滚,而嵌套事务会局部回滚,不会影响到上一个方法中执行的结果; 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

PlLI-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值