0401事务-spring6

文章目录

    • 1 spring事务管理概述
      • 1.1 事务概述
      • 1.2 spring事务管理
    • 2 事务传播行为
    • 3 事务的隔离级别
    • 4 应用spring事务开发注意事项
      • 4.1 事务超时时间
      • 4.2 只读事务
      • 4.3 异常回滚
      • 4.4 设置全局异常处理后@Transactonal不生效
    • 结语

1 spring事务管理概述

1.1 事务概述

在计算机科学中,事务是一组相关的操作,被视为一个单独的不可分割的逻辑单元,或者说是一个原子操作。事务通常用于确保数据库操作的一致性和完整性,以及保证数据的可靠性。

事务的四个基本属性(ACID)包括:

  1. 原子性(Atomicity):一个事务应该被视为一个不可分割的单元,要么全部执行,要么全部不执行。
  2. 一致性(Consistency):一个事务执行后,应该使数据库从一个一致性状态转换到另一个一致性状态。这意味着事务应该遵循所有预定义的规则和约束。
  3. 隔离性(Isolation):在事务执行过程中,对其他事务应该是隔离的,即每个事务都应该认为它是唯一正在运行的事务,并且在没有提交之前,对其他事务不可见。
  4. 持久性(Durability):一旦事务被提交,它的结果应该持久保存在数据库中,即使系统崩溃也应该能够恢复。

通过使用事务,我们可以确保在一组操作中的任何操作失败时,所有的更改都能够回滚,从而保持数据的一致性和完整性。在数据库中,事务由一组SQL语句组成,这些SQL语句可以是插入、更新或删除操作。事务可以通过编程接口或SQL语句来控制和管理,以确保数据的完整性和一致性。

1.2 spring事务管理

Spring框架提供了对事务管理的支持,以便于开发人员可以轻松地实现数据库事务控制。Spring事务管理通过与底层事务管理器交互来管理事务,支持多种事务管理机制,包括JDBC事务、Hibernate事务和JTA(Java Transaction API)事务。

Spring事务管理的核心接口是PlatformTransactionManager,它定义了事务管理器的通用行为。Spring框架提供了多种实现PlatformTransactionManager接口的类,如DataSourceTransactionManagerHibernateTransactionManagerJtaTransactionManager等,可以根据需要选择适合的实现。

  • DataSourceTransactionManager:支持JdbcTemplate,Mybatis,Hibernate等事务管理;
  • JtaTransactionManager:支持分布式事务管理。

继承关系如下图1.2-1所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jRP0ekss-1681004468456)(/Users/gaogzhen/Documents/20230408-spring6-tx-DataSourceTransactionManager.png)]

在Spring中,我们可以使用@Transactional注解来将一个方法标记为事务性方法。在使用@Transactional注解时,可以指定事务的传播行为、隔离级别、超时时间和只读属性等。

例如,下面是一个使用@Transactional注解实现事务控制的示例:

javaCopy code
@Transactional
public void transferMoney(Account fromAccount, Account toAccount, double amount) {
    // 转账逻辑
}

在上面的示例中,transferMoney()方法被标记为事务性方法,即使在方法执行期间发生异常,也会回滚整个事务,确保转账操作的原子性。

在Transactioanl属性中,我们主要了解下事务传播行为和事务隔离级别。我们不做测试程序,如果以后有应用场景,我们在讲解。

2 事务传播行为

spring中传播行为为Propagation枚举类型,源代码2-1如下所示:

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

/**
 * 传播行为
 */
public enum Propagation {

   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

   NEVER(TransactionDefinition.PROPAGATION_NEVER),

   NESTED(TransactionDefinition.PROPAGATION_NESTED);

   private final int value;

   Propagation(int value) {
      this.value = value;
   }

   public int value() {
      return this.value;
   }

}

在Spring事务管理中,事务的传播行为是指当一个事务方法调用另一个事务方法时,当前事务如何传播到被调用的方法。Spring框架定义了七种事务传播行为:

  1. REQUIRED:如果当前存在事务,则加入该事务;否则新建一个事务。
  2. SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务的方式执行。
  3. MANDATORY:当前必须存在事务,否则抛出异常。
  4. REQUIRES_NEW:无论当前是否存在事务,都新建一个事务。
  5. NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则挂起该事务。
  6. NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
  7. NESTED:如果当前存在事务,则在该事务内嵌一个子事务;否则新建一个事务。

以上的每种传播行为都适用于不同的场景和需求,例如:

  • REQUIRED:应用于正常的业务逻辑,需要在一个事务中完成,而且需要保证事务的原子性。
  • SUPPORTS:适用于只读操作,即不需要进行修改,只需要读取数据的情况。
  • MANDATORY:适用于需要事务支持的操作,例如修改或删除操作。
  • REQUIRES_NEW:适用于需要独立的事务支持的操作,例如一些与业务逻辑无关的后台任务。
  • NOT_SUPPORTED:适用于只读操作或与事务无关的操作,例如查询操作。
  • NEVER:适用于不能在事务中执行的操作,例如一些文件操作或网络操作。
  • NESTED:适用于需要嵌套事务的场景,例如一些需要在某个业务逻辑完成后,再进行一些额外的操作的情况。

事务传播行为是Spring事务管理的一个重要概念,了解和使用不同的传播行为可以帮助开发人员更好地控制和管理事务。

3 事务的隔离级别

在并发环境下,多个事务可能同时访问数据库中的同一数据,这时需要保证事务的隔离性,以防止脏读、不可重复读、幻读等问题。事务的隔离级别定义了一个事务对另一个事务的影响程度,Spring框架支持四种事务隔离级别:

  1. READ_UNCOMMITTED(读未提交):最低的隔离级别,允许一个事务读取另一个事务未提交的数据,可能导致脏读、不可重复读、幻读等问题。
  2. READ_COMMITTED(读已提交):一个事务只能读取另一个事务已提交的数据,可以避免脏读问题,但不可避免不可重复读和幻读问题。Oracle默认隔离级别。
  3. REPEATABLE_READ(可重复读):一个事务在执行期间多次读取同一数据时,保证读取到的是同一个数据,可以避免脏读、不可重复读问题,但不可避免幻读问题。MySQL默认隔离级别。
  4. SERIALIZABLE(串行化):最高的隔离级别,通过强制事务串行执行来避免脏读、不可重复读和幻读问题。

隔离级别越高,对性能的影响越大,因为需要对数据库进行更严格的锁定。因此,在选择隔离级别时,需要根据实际情况进行权衡,以获得最合适的性能和数据一致性。

在Spring事务管理中,可以通过@Transactional注解的isolation属性来指定事务隔离级别,默认为DEFAULT,表示使用数据库的默认隔离级别。例如,可以使用以下注解来指定事务的隔离级别为可重复读:

@Transactional(isolation = Isolation.REPEATABLE_READ)

4 应用spring事务开发注意事项

4.1 事务超时时间

在Spring事务管理中,可以通过@Transactional注解的timeout属性来设置事务的超时时间,单位为秒,默认值为-1,表示不设置超时时间。

事务超级时间计算规则:

  • 只计算程序最后一条DML语句及其之前的程序执行时间;即它不计算最后一条DML语句之后的用时。

示例:

@Override
    @Transactional(rollbackFor = RuntimeException.class, timeout = 3)
    public void transfer(Integer fromId, Integer toId, BigDecimal mount) {
        // 查询账户
        Account from = accountDao.getAccount(fromId);
        if (from == null) {
            throw new IllegalArgumentException("账户不存在:" + fromId);
        }
        Account to = accountDao.getAccount(toId);
        if (to == null) {
            throw new IllegalArgumentException("账户不存在:" + toId);
        }
        // 模拟用时
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException("异常处理");
        }
        // 校验
        if (from.getBalance().compareTo(mount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 转账
        from.setBalance(from.getBalance().subtract(mount));
        to.setBalance(to.getBalance().add(mount));
        int count = accountDao.updateAccount(from);
        if (count != 1) {
            throw new RuntimeException("转出失败");
        }
        count = accountDao.updateAccount(to);
        if (count != 1) {
            throw new RuntimeException("转转入失败");
        }

        // try {
        //     TimeUnit.SECONDS.sleep(5);
        // } catch (InterruptedException e) {
        //     throw new RuntimeException("异常处理");
        // }
    }

在最后一条D ML语句count = accountDao.updateAccount(to);之前会报错;之后模拟用时则不会报错。报错如下:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out:

4.2 只读事务

@Ttransactional(readonly=true),设置只读事务的目的:

Spring会对只读事务进行了优化,这种优化称为“只读优化”,主要是针对只读事务的特点,采用了以下几种优化措施:

  1. 数据库层面的优化:在MySQL等数据库中,只读事务可以使用“快照读”(snapshot read)的方式来访问数据,即使用MVCC(多版本并发控制)技术,在读取数据时不会对数据进行加锁,从而不会影响到其他事务对该数据的读取和写入,提高了读取数据的效率。
  2. Spring缓存的优化:对于只读事务,Spring会将查询结果缓存起来,当下次有相同的查询请求时,直接返回缓存中的结果,而不用再次查询数据库,从而提高了查询效率。
  3. 事务管理器的优化:对于只读事务,Spring使用了与读写事务不同的事务管理器,以达到更好的性能优化效果。只读事务使用的是ReadOnlyTransactionManager,该事务管理器不会创建数据库连接,而是从连接池中获取已有的连接,从而减少了连接池中连接的创建和销毁开销,提高了性能。

通过以上优化措施,Spring对只读事务的执行效率得到了很大的提升,使得只读事务在查询方面的性能表现更加优秀。但需要注意的是,只读事务的优化可能会对数据的一致性产生一定的影响,因此在使用只读事务时需要慎重考虑,并结合业务场景进行选择。

4.3 异常回滚

rollbackFor是Spring中用于设置事务回滚的异常类的属性,表示当抛出指定的异常时,事务将回滚,即撤销已经提交的数据库操作,将数据恢复到之前的状态。

rollbackFor属性接受一个异常类或多个异常类的数组作为参数,例如:

javaCopy code
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void updateUser(User user) throws SQLException, IOException {
    // ...
}

在这个例子中,当在updateUser方法中抛出SQLExceptionIOException异常时,Spring将会触发事务回滚,撤销之前的数据库操作。

如果不设置rollbackFor属性,Spring默认只有在抛出RuntimeException及其子类异常时,才会触发事务回滚。如果要回滚非运行时异常,可以在rollbackFor属性中设置需要回滚的异常类。

需要注意的是,设置rollbackFor属性时,指定的异常类应当是受检查异常(checked exception),即需要显式地在方法声明中指定抛出该异常,否则在方法中抛出该异常时,编译器会提示“未捕获异常”的错误。

4.4 设置全局异常处理后@Transactonal不生效

在Spring中,当一个方法使用了@Transactional注解进行事务管理,如果这个方法中发生了受检查异常(checked exception),那么Spring会将该异常转换成RuntimeException类型的异常,并抛出该异常,从而触发事务回滚操作。

如果同时在应用中设置了全局异常处理器(例如@ControllerAdvice注解的异常处理类),并在该处理器中处理了该异常类型,那么在异常被处理后,异常已经被成功捕获并处理,不会再被抛出到上层调用链中,从而不会触发事务回滚操作。

为了解决这个问题,可以通过设置rollbackFor属性或rollbackForClassName属性,在异常被处理时,仍然将其转换为RuntimeException类型的异常并抛出,从而触发事务回滚。例如:

@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
    // ...
}

这个例子中,@Transactional注解中设置了rollbackFor属性,将所有异常都转换为RuntimeException类型的异常,并触发事务回滚操作。当全局异常处理器捕获到异常并处理后,仍然会将异常转换为RuntimeException类型并抛出,从而触发事务回滚。

另外,需要注意的是,如果在全局异常处理器中使用了@Transactional注解,那么在处理异常时,Spring会自动启动一个新的事务进行处理。在这种情况下,如果全局异常处理器中的操作失败,那么该事务将会回滚,但不会影响原来的事务。因此,在使用全局异常处理器时需要慎重考虑是否需要进行事务管理。

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/spring6-study

参考:

[1]Spring框架视频教程[CP/OL].P118-135.

[2]ChatGPT

你可能感兴趣的:(#,spring全家桶,事务,spring,Java)