springboot事务处理

1. 快速使用

  • 事务支持

备注:使用事务的时候,一定要首先确保当前数据库的引擎是否支持事务,如果数据库引擎不支持事务,则任何配置都是徒劳的。

例如:MySQL 数据库 InnoDB 支持事务,而 MyISAM 不支持事务。

  • 引入依赖

备注:已经引入了 mybatis-plus-boot-starter 则无需再次引入 spring-boot-starter-jdbc。
原因:mybatis-plus-boot-starter 内部已经引入了 spring-boot-starter-jdbc。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>${mybatis.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
  <version>${jdbc.version}</version>
</dependency>
  • 添加注解

备注:注解一般添加在自己的业务方法上面 (一般来说都是添加在 service 层的方法上面)

package com.dme.modules.service.transactional.impl;

/**
 * @Author: 喜欢编程的代先生
 * @Date: 2022-06-12 12:39
 * @Description: 拒绝侵权
 */
@Service
public class TransactionServiceImpl extends ServiceImpl<TransactionDemoDao, TransactionOne> implements TransactionService {

    private Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class);

    private TransactionDemoDao transactionDemoDao;

    @Autowired
    public void setTransactionDemoDao(TransactionDemoDao transactionDemoDao) {
        this.transactionDemoDao = transactionDemoDao;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void testDelete() {
        logger.info("测试删除........");
        int i = transactionDemoDao.deleteById("1539393513600253961");
        logger.info("受影响行数 :" + i);
        int a = 1 / 0;
    }
}

2. 详细介绍

在 Spring 中,事务的实现方式有两种,分别是编程式事务和声明式事务管理两种方式。

  • 编程式事务管理

编程式事务管理使用 TransactionTemplate或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring 推荐使用 TransactionTemplate。

  • 声明式事务管理

声明式事务管理建立在 AOP 之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行目标方法之后根据执行的情况提交或者回滚事务。声明式事务管理不需要入侵代码,通过 @Transactional 就可以进行事务的操作,推荐使用。

1. 事务注解

备注:@Transactional 只能放在 public 修饰的方法上。默认情况下,该注解只对 RuntimeException 及其子类异常执行事务回滚。

参数名称功能描述readOnly该属性用于设置当前事务是否为只读模式,设置为 true 表示只读,false 表示可读可写,默认情况下是 false。

例如: @Transactional(readOnly = true)rollbackFor该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。

例如:

指定单一异常类:@Transactional(rollbackFor = Exception.class)

指定多个异常类:@Transactional(rollbackFor = {RuntimeException.class,Exception.class})rollbackForClassName该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。

例如:

指定单一异常类名:@Transactional(rollbackForClassName = “RuntimeException”)

指定多个异常类名:@Transactional(rollbackForClassName = {“RuntimeException”,”Exception”})noRollbackFor该属性用于设置不需要进行回滚的异常类数组。使用方法同 rollbackFornoRollbackForClassName该属性用于设置不需要进行回滚的异常类名称数组。使用方法同 rollbackForClassNamepropagation该属性用于设置事务的传播行为。isolation该属性用于设置事务的隔离级别。timeout该属性用于设置事务的超时秒数,默认值为 -1 表示永不超时。

2. 隔离级别

  • 隔离级别

数据库标准提出了四种事务隔离级别,分别为:读未提交、读已提交、可重复读、串行化。

  • 枚举映射
package org.springframework.transaction.annotation;

public enum Isolation {
    DEFAULT(-1), // springboot 默认隔离级别,可以通过配置文件进行配置
    READ_UNCOMMITTED(1), // 读未提交
    READ_COMMITTED(2), // 读已提交
    REPEATABLE_READ(4), // 可重复读
    SERIALIZABLE(8); // 串行化

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}
  • 使用方法
@Transactional(isolation = Isolation.DEFAULT)
public void testAdd(){
    logger.info("测试默认隔离级别......");
}

3. 传播行为

  • 7 种传播行为

springboot 事务机制种对数据库存在七种传播行为,常用的传播行为主要有三种:REQUIRED、REQUIRES_NEW、NESTED
REQUIRED:需要事务,它是默认的传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。
SUPPORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在事务,则继续采用无事务的方式运行子方法
MANDATORY:必须使用事务,如果当前没有事务,则会抛出异常,如果当前存在事务,就沿用当前的事务。
REQUIRES_NEW:无论当前事务是否存在,都会创建新的事务去运行子方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。
NOT_SUPPORTED:不支持事务,如果当前存在事务,则将事务挂起,运行子方法。
NEVER:不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制继续运行。
NESTED:当前方法调用子方法时,如果子方法发生异常,则回滚子方法执行过的 SQL,而不回滚当前方法的事务。

  • 枚举映射
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;
    }
}
  • 使用方法
@Transactional(propagation = Propagation.NESTED)
public void testPropagation(){
  logger.info("测试事务传播行为......");
}

4. 事务超时

事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制事务还未完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。默认设置为底层数据库事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是 none,即没有超时限制。

5. 回滚机制

  • 自动回滚

备注:对可能出现异常的地方不要进行 try catch 操作。

@Transactional(rollbackFor = Exception.class)
public void testRollback(){
  logger.info("测试异常自动回滚......");
}
  • 手动回滚

备注:对可能出现异常的地方进行 try catch,并在 捕获异常的同时进行回滚的操作。

@Transactional(rollbackFor = Exception.class)
public void testRollback(){
  logger.info("测试异常自动回滚......");
  try {
    int i = 1 / 0;
 }catch (Exception e){
    logger.error("异常信息:" + e.getMessage());
    TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
    transactionStatus.setRollbackOnly();
 }
}
  • 设置回滚点

备注:将当前操作回滚到设置的回滚点的位置。

@Transactional(rollbackFor = Exception.class)
public void testRollback(){
  logger.info("业务逻辑一...........");
  logger.info("业务逻辑二...........");
  /**设置回滚点*/
  Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
  logger.info("业务逻辑三...........");
  logger.info("测试异常自动回滚......");
  try {
    int i = 1 / 0;
 }catch (Exception e){
    logger.error("异常信息:" + e.getMessage());
    TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
    /**回滚到指定位置,也就是说,业务逻辑一和业务逻辑二的逻辑不会进行回滚*/
    transactionStatus.rollbackToSavepoint(savepoint);
 }
}

3. 常见问题

1. @EnableTransactionManagement

问题:springboot 项目中是否需要在 启动类上添加 @EnableTransactionManagement 注解?
答案:不需要。因为 springboot 自动装配已经帮助我们处理了,springboot 项目默认支持了事务。

2. @Transactional 失效

问题:方法上添加了 @Transactional 注解,为什么没有进行回滚?
排查一:是否使用了 try catch 进行了异常捕获,并且捕获之后,没有通过 throw new RuntimeException(); 进行异常抛出。因为对于 spring aop 异常捕获原理,被拦截的方法需要显示的抛出异常,并不能进行任何处理,这样 aop 代理才能捕获到方法的异常,才能进行事务的回滚操作;默认清空下,aop 只捕获 RuntimeException 的异常,但是可以通过配置来捕获特定的异常并回滚。
排查二:是否使用了 try catch 进行了异常信息捕获,如果是可以在 catch 语句中添加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
排查三:排查是不是产生了自调用的问题。在 spring 的 aop 代理下,只有目标方法被外部调用,目标方法才由 spring 生成的代理对象来管理;若统一类中的其他没有用 @Transactional 注解进行修饰的方法内部调用了 用 @Transactional 注解进行修饰的方法,有 @Transactional 注解的方法的事务将会被忽略,不发生回滚。

备注:如果确实需要这样操作,只需要把 @Transactional 注解加在当前的类名上就可以了 或者使用 AspectJ 取代 spring aop 进行代理。
排查四:@Transactional 注解只被应用到 public 修饰的方法上;如果在 protected、private等修饰的方法上,@Transactional 注解不会报错,但是这个注解的将不会生效。
排查五:@Transactional 注解不会对当前修饰的方法的子方法生效。比如:我们在方法 A 中声明了 @Transactional 注解,但是 A 方法的内部调用的 方法 B 和 方法 C,其中方法 B 进行了 数据库的操作,但是改部分的异常被方法 B 进行了处理并且没有进行 抛出,这样的话事务是不会生效的。反之,如果 方法 B 声明了 @Transactional,但是方法 A 没有声明 @Transactional,A 方法内部调用 B 方法,事务也是不会生效的。如果想要事务生效,需要将子方法的事务控制交给调用的方法,在子方法中使用 @Transactional注解并通过 rollbackFor 指定定回滚的异常 或者直接将 异常 抛出。

备注:在使用事务的时候,最好把子方法的异常进行抛出,交给调用的方法进行处理。
参考:www.bilibili.com/h5/note-app/view?...

本作品采用《CC 协议》,转载必须注明作者和本文链接
zhaozhangxiao
讨论数量: 3

感谢分享 最近做的项目 正好涉及到了事务。

1周前 评论

分布式事务如何实现了

1周前 评论
zhaozhangxiao (楼主) 1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!