SpringBoot事务管理


在日常开发过程中,只要涉及数据操作,都不可避免地会涉及事务管理相关内容,而Spring提供了强大的事务管理机制,能够帮助开发者更轻松地处理数据一致性和事务的问题。

一、什么是事务管理

事务(Transaction)是数据库操作的一个执行单元,它要么完全执行成功,要么完全不执行。事务管理就是确保在数据库操作过程中,要么所有操作都成功提交,要么所有操作都不提交,从而保证数据的一致性和完整性。事务管理通常涉及以下几个关键概念:ACID特性、事务管理器、事务传播行为和事务隔离级别等。

  1. ACID特性
    ACID 是事务处理的四个基本特性,它们分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这些特性保证了事务在数据库中的可靠性和稳定性。
    • 原子性(Atomicity):事务是一个不可分割的工作单位,要么全部成功执行,要么全部失败回滚。
    • 一致性(Consistency):事务执行后,数据库从一个一致性状态转换到另一个一致性状态,不会破坏数据库的完整性约束。
    • 隔离性(Isolation):事务之间相互隔离,一个事务的执行不会影响其他事务的执行结果。
    • 持久性(Durability):事务一旦提交,其结果将永久保存在数据库中,即使系统发生故障也不会丢失。
  2. 事务管理器(Transaction Manager)
    事务管理器是负责管理事务的组件,它可以处理事务的开始、提交、回滚和管理事务的隔离级别等。在Java开发中,常见的事务管理器有 PlatformTransactionManager 和 DataSourceTransactionManager 等。
    • PlatformTransactionManager:Spring框架中定义的事务管理器接口,可以用于管理各种类型的事务。
    • DataSourceTransactionManager:Spring提供的用于管理数据库事务的事务管理器,通常与JDBC、Hibernate等数据访问技术一起使用。
  3. 事务传播行为(Transaction Propagation Behavior)
    事务传播行为定义了在方法调用链中如何处理事务的传播。在Spring中,可以使用 @Transactional 注解来指定事务的传播行为,常见的传播行为包括:
    • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
    • REQUIRES_NEW:每次都创建一个新事务,并且暂停当前事务(如果存在的话)。
    • NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新事务。
  4. 事务隔离级别(Isolation Level)
    事务隔离级别定义了多个事务并发执行时的相互影响程度。在数据库中,常见的事务隔离级别包括:
    • READ_UNCOMMITTED:允许事务读取未提交的数据,可能导致脏读、不可重复读和幻读等问题。
    • READ_COMMITTED:确保一个事务只能读取已提交的数据,避免了脏读问题,但可能存在不可重复读和幻读问题。
    • REPEATABLE_READ:确保一个事务在多次读取同一数据时,得到的结果是一致的,避免了不可重复读问题,但可能存在幻读问题。
    • SERIALIZABLE:最高的隔离级别,确保多个事务并发执行时不会产生任何问题,但可能导致性能下降。
  5. 事务的控制
    • 事务的开始:事务开始时,数据库记录当前状态,将事务标记为进行中。
    • 事务的执行:执行事务中的一组操作,包括读取数据、修改数据等。
    • 事务的提交:如果事务执行成功,将事务中的所有操作结果提交给数据库并标记事务为成功。
    • 事务的回滚:如果事务执行失败或出现异常,将事务中的操作全部撤销,并回滚到事务开始前的状态。

二、Spring事务管理

在了解事务及事务管理的基本概念之后,需要熟悉如何在代码实现事务的管理。在 Spring中主要有两种方式来管理事务:声明式事务管理和编程式事务管理。
编程式事务管理
编程式事务管理是通过在代码中显式地管理事务的开始、提交和回滚。这种方式提供了更细粒度的控制,但相对来说更繁琐。

  • 使用 Spring 的TransactionTemplate或PlatformTransactionManager接口,可以在代码中手动控制事务的边界。

声明式事务管理
声明式事务管理是通过在 Spring 的配置文件或注解中定义事务边界和属性,让 Spring 自动管理事务。这是一种更简洁、方便的方式,通常推荐使用。

  • XML 配置文件:通过在<tx>元素中配置事务管理器和事务属性,可以在 Spring 上下文中启用事务管理。
  • Java 注解:使用@Transactional注解在类或方法上,可以指定事务的传播行为、隔离级别、回滚规则等,在SpringBoot中,主要用注解的方式更加简洁、方便。
    对于大多数简单的场景,推荐使用声明式事务管理,因为它更简洁和易于维护。而在特殊情况下,比如业务逻辑很复杂,需要更细粒度的控制时,则可以考虑使用编程式事务管理。

1. 编程式事务管理

1.1 通过PlatformTransactionManger控制事务

举例:现在要给南京公司的员工涨薪,为了防止执行涨薪因为异常导致数据前后不一致,我们需要来对”涨薪“这个事务进行管理

  1. 首先需要获取事务管理器,指定数据源
  2. 定义事务属性(隔离级别、传播特性、超时时间、是否只读等)
  3. 开启事务,返回事务状态
  4. 编写业务逻辑代码
  5. 提交事务
  6. 若出现异常,则回滚事务
@Service
@Slf4j
public class TransactionPractice {
    // 1.获取事务管理器,指定数据源
    @Autowired
    private PlatformTransactionManager transactionManager;
    // 2.定义事务属性(隔离级别、传播特性、超时时间、是否只读等)
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private EmployeeService employeeService;

    public void ProgrammaticTransactionManagementFirst() {
        // 3.开启事务,返回事务状态
        TransactionStatus transactionStatus = null;
        try {
            List<Employee> employeesOri = getCompanyEmployee("南京公司");
            System.out.println("--------涨薪前-------");
            employeesOri.forEach(System.out::println);
            employeesOri.forEach(o -> o.setSalary(o.getSalary() + 1000));
            transactionStatus = transactionManager.getTransaction(transactionDefinition);
            employeeService.updateBatchById(employeesOri);
            // 4.提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            log.error("涨薪异常:(");
            // 5.回滚事务
            if (transactionStatus != null) {
                log.info("涨薪事务回滚!");
                transactionManager.rollback(transactionStatus);
            }
        }
        List<Employee> employeesLater = getCompanyEmployee("南京公司");
        System.out.println("--------涨薪后-------");
        employeesLater.forEach(System.out::println);
    }

    public List<Employee> getCompanyEmployee(String company) {
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getSubCompany, "南京公司");
        List<Employee> employees = employeeService.list(queryWrapper);
        return employees;
    }
}

正常情况输出如下:

--------涨薪前-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=3000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=4000)
--------涨薪后-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)

如果执行事务过程中出现异常,例如在更新数据之后,插入一条编码为空的数据(DDL限制code不能为空),执行插入操作时报异常
在这里插入图片描述
此时执行结果如下:可以看到,由于更新过程中出现异常,事务发生回滚,数据未发生变化

--------涨薪前-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)
2024-03-31 16:17:38.589 ERROR 14324 --- [           main] o.practice.service.TransactionPractice   : 涨薪异常:(
2024-03-31 16:17:38.589  INFO 14324 --- [           main] o.practice.service.TransactionPractice   : 涨薪事务回滚!
--------涨薪后-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)
1.2 通过TransactionTemplate控制事务

这种方式通过重写TransactionTemplate.execute()方法,实现对事务的控制。

@Service
@Slf4j
public class TransactionPractice {
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private EmployeeService employeeService;

    public void ProgrammaticTransactionManagementSecond() {
        List<Employee> employeesOri = getCompanyEmployee("南京公司");
        System.out.println("--------涨薪前-------");
        employeesOri.forEach(System.out::println);
        employeesOri.forEach(o -> o.setSalary(o.getSalary() + 1000));
        // 使用TransactionTemplate执行事务操作
        transactionTemplate.execute(transactionStatus -> {
            try {
                // 更新数据
                employeeService.updateBatchById(employeesOri);
                // 新增一条员工数据
                //Employee emptyEmployee = new Employee();
                //emptyEmployee.setName("小刚").setAge(23).setSubCompany("南京公司").setJob("测试").setDepartment("测试一部").setSalary(4000);
                //employeeService.save(emptyEmployee);
            } catch (Exception e) {
                log.error("涨薪异常:( 涨薪事务回滚!");
                transactionStatus.setRollbackOnly();
            }
            return null;
        });
        List<Employee> employeesLater = getCompanyEmployee("南京公司");
        System.out.println("--------涨薪后-------");
        employeesLater.forEach(System.out::println);
    }

    public List<Employee> getCompanyEmployee(String company) {
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getSubCompany, "南京公司");
        List<Employee> employees = employeeService.list(queryWrapper);
        return employees;
    }
}

正常情况下执行结果:

--------涨薪前-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=3000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=4000)
--------涨薪后-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)

同样让代码执行报异常,结果如下:

--------涨薪前-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)
2024-03-31 16:38:22.098 ERROR 8520 --- [           main] o.practice.service.TransactionPractice   : 涨薪异常:( 涨薪事务回滚!
--------涨薪后-------
Employee(id=4, name=翠花, age=27, subCompany=南京公司, department=测试一部, salary=4000)
Employee(id=5, name=玲玲, age=31, subCompany=南京公司, department=测试二部, salary=5000)

查看TransactionTemplate源码,可以看到TransactionTemplate其实是对PlatformTransactionManager的进一步封装,底层逻辑是一致的

@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
    } else {
        TransactionStatus status = this.transactionManager.getTransaction(this);

        Object result;
        try {
            result = action.doInTransaction(status);
        } catch (Error | RuntimeException var5) {
            this.rollbackOnException(status, var5);
            throw var5;
        } catch (Throwable var6) {
            this.rollbackOnException(status, var6);
            throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception");
        }

        this.transactionManager.commit(status);
        return result;
    }
}

2. 声明式事务管理

2.1 基本使用

使用@Transactional实现事务管理前,需要在启用类上添加@EnableTransactionManagement。在添加这个注解后,在项目启动时,Spring在扫描Bean时,检查是否有@Transactional注解(类、父类、接口或方法),如果存在,则会通过AOP给对应的Bean生成代理对象,并拦截public方法(只对),完成事务相关操作(开启、提交或回滚事务)。需要注意:

  • 该注解只对public方法有效
  • 如果放在接口上,接口所有实现类的public方法都会加上事务
  • 如果放在类上,则当前类及子类的public方法都会加上事务
  • 如果放在public方法上,只会对该方法加上事务

还是上面涨薪的例子,改用@Transactional实现如下

@Service
@Slf4j
public class TransactionPractice {
    @Autowired
    private EmployeeService employeeService;

    @Transactional(rollbackFor = Exception.class)
    public void DeclarativeTransactionManagement() {
        List<Employee> employeesOri = getCompanyEmployee("南京公司");
        System.out.println("--------涨薪前-------");
        employeesOri.forEach(System.out::println);
        employeesOri.forEach(o -> o.setSalary(o.getSalary() + 1000));
        // 更新数据
        employeeService.updateBatchById(employeesOri);
        // 新增一条员工数据    
        Employee emptyEmployee = new Employee();
        emptyEmployee.setName("小刚").setAge(23).setSubCompany("南京公司").setJob("测试").setDepartment("测试一部").setSalary(4000);
        employeeService.save(emptyEmployee);
        List<Employee> employeesLater = getCompanyEmployee("南京公司");
        System.out.println("--------涨薪后-------");
        employeesLater.forEach(System.out::println);
    }

    public List<Employee> getCompanyEmployee(String company) {
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getSubCompany, "南京公司");
        List<Employee> employees = employeeService.list(queryWrapper);
        return employees;
    }
}
2.2 参数

可以看到,使用@Transactional进行事务管理的方式非常简洁、方便。@Transactional提供了多个参数来针对不同的使用场景进行配置

  1. propagation
    • 作用:指定事务的传播行为,即方法被调用时,当前方法如何处理事务。
    • 取值:
      • Propagation.REQUIRED:如果当前存在事务,则加入当前事务;如果当前没有事务,则创建一个新的事务。
      • Propagation.REQUIRES_NEW:每次都创建一个新的事务,如果当前存在事务,则将当前事务挂起。
      • Propagation.SUPPORTS:支持当前事务,如果当前没有事务,则以非事务方式执行。
      • Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
      • Propagation.MANDATORY:支持当前事务,如果当前没有事务,则抛出异常。
      • Propagation.NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
      • Propagation.NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。
  2. isolation
    • 作用:指定事务的隔离级别,即事务并发时对数据的读取、写入等操作的隔离程度。
    • 取值:
      • Isolation.DEFAULT:默认的隔离级别,使用底层数据源的默认隔离级别。
      • Isolation.READ_UNCOMMITTED:允许读取未提交的数据,最低的隔离级别,可能会导致脏读、不可重复读和幻读。
      • Isolation.READ_COMMITTED:只能读取已提交的数据,避免了脏读,但可能会出现不可重复读和幻读。
      • Isolation.REPEATABLE_READ:保证在同一个事务中多次读取同一数据时结果一致,避免了脏读和不可重复读,但可能会出现幻读。
      • Isolation.SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读,但性能较低。
  3. timeout
    • 作用:指定事务的超时时间,即事务执行的最大时间限制,超过时间会自动回滚事务。
    • 取值:以秒为单位的整数值,例如 timeout = 30 表示事务执行时间不超过 30 秒。
  4. readOnly
    • 作用:指定事务是否为只读事务,即方法中是否允许进行写操作。
    • 取值:true 表示只读事务,false 表示可读写事务,默认为 false。
  5. rollbackFor 和 noRollbackFor
    • 作用:指定哪些异常会触发事务回滚,哪些异常不会触发事务回滚。
    • 取值:可以指定异常类的数组,例如 rollbackFor = {RuntimeException.class, SQLException.class} 表示遇到 RuntimeException 或 SQLException 异常时会触发事务回滚。
  6. value
    • 作用:用于指定应该应用事务管理的方法,可以指定多个方法名或通配符。
    • 取值:字符串数组,例如 value = {“save*”, “update*”} 表示应用于所有以 save 或 update 开头的方法。
  • 50
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值