Spring使用事务的两种方式

1. 为什么需要事务?

前面的博客 对MySQL事务作讲解,事务就是将⼀组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败。

比如,现在要实现转账操作:

第一步:张三账户 -500

第二步:李四账户+500

如果没有事务,第一步执行成功,然而第二步执行失败,那么张三的账户的500元就凭空消失了。而事务的引入就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

2.Spring中事务的实现

Spring中的事务操作分为两类:

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)。

之前MySQL博客中,使用事务可以分为3个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:

-- 开启业务  (⼀组操作前开启事务)
start transaction;
-- 相关业务执行


-- 提交事务  (这组操作全部成功, 提交事务)
commit;

-- 回滚事务 (这组操作中间任何⼀个操作出现异常, 回滚事务)
rollback;

2.1 Spring编程式事务(手动)

Spring ⼿动操作事务和上⾯ MySQL 操作事务类似,它也是有 3 个重要操作步骤:
  1. 开启事务(获取)
  2. 提交事务
  3. 回滚事务
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus。 如图所示:

使用编程式事务代码如下:
代码结构:

controller:

@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    // JDBC 事务管理器
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;
    // 定义事务属性
    @Resource
    private TransactionDefinition transactionDefinition;

    @PostMapping("/add")
    public String addUser(@RequestBody Userinfo user) {
        // 开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

        userService.addUser(user);

        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        //回滚事务
        //dataSourceTransactionManager.rollback(transactionStatus);
        return "User added successfully!";
    }
}

当执行提交事务,观察结果:

数据库:

发现我们程序执行成功并且事务提交完成,持久化到数据库中。

那么如果我们使用的是回滚代码,来添加用户tom,再来观察执行结果:

然而,数据却没有同步到数据库中,因为事务被我们回滚了:

以上代码虽然可以实现事务, 但操作也很繁琐, 有没有更简单的实现⽅法呢? --- 声明式事务

2.2 声明式事务@Transactional

声明式事务的实现很简单, 只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了⽆需⼿动开启事务和提交事务, 进⼊⽅法时⾃动开启事务, ⽅法执⾏完会⾃动提交事务, 如果中途发⽣ 没有处理的异常会⾃动回滚事务.
代码实现:
注意:
我们⼀般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, ⼀个业务功能可能会包含多个数据访问的操作. 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在⼀个事务范围内. 下面代码在Controller中书写, 只是为了⽅便演示学习.
@Transactional
@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;
    

    @PostMapping("/add")
    public String addUser(@RequestBody Userinfo user) {

        userService.addUser(user);
        return "User added successfully!";
    }
}

现在代码中是没有异常的,也就是实行玩添加业务后就提交了。添加 tom,观察执行结果:

入预期所料,事务被提交,数据持久化到数据库。

现在我们手动来写一个异常,然后添加alice,通过触发异常来实现事务回滚:

 观察结果,数据被回滚:

@Transactional作用

@Transactional 可以⽤来修饰⽅法或类:

  • 修饰⽅法时: 只有修饰public ⽅法时才⽣效(修饰其他⽅法时不会报错, 也不⽣效)[推荐]
  • 修饰类时: 对 @Transactional 修饰的类中所有的 public ⽅法都⽣效.
⽅法/类被 @Transactional 注解修饰时, 在⽬标⽅法执⾏开始之前, 会⾃动开启事务, ⽅法执⾏结束之后, ⾃动提交事务.
注意:
  • 如果在⽅法执⾏过程中, 出现异常, 且异常未被捕获, 就进⾏事务回滚操作.
  • 如果异常被程序捕获, ⽅法就被认为是成功执⾏, 依然会提交事务.
修改上述代码, 对异常进⾏捕获:

 运⾏程序, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交.

如果需要事务进⾏回滚, 有以下两种方式:

1. 重新抛出异常:

2. 手动回滚事故

使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并使用 setRollbackOnly 设置 setRollbackOnly :

3. @Transactional详解

了解  @Transactional 注解当中的三个常⻅属性:

1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

3.1 rollbackFor

@Transactional 默认只在遇到运⾏时异常Error时才会回滚, ⾮运⾏时异常不回滚. 即Exception的⼦类中, 除了RuntimeException及其⼦类.:
上⾯为了演⽰事务回滚, ⼿动设置了程序异常: int a = 10 / 0 ;这属于运行时异常,会发生回滚。
如果我们代码中有一个IOException就不会触发回滚吗?接下来我们把异常改为如下代码检验一下

观察结果:

 发现虽然程序抛出了异常, 但是事务依然进⾏了提交:

如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通过 rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚.

 再次执行程序,发现虽然程序抛出了IOException异常,但是事务也回滚了,因为我们修改了rollbackFor 的属性。

结论:

  • 在Spring的事务管理中,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
  • 如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定. 

 写累了.事务的隔离级别和传播机制单独开一节博客来回估吧。

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小小小小关同学

你的支持就是我的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值