Spring的事务详解

2023-10-16 10:18:11 浏览数 (1)

事务在Spring中是如何运作的

在了解嵌套事务之前,可以先看下单个事务在Spring中的处理流程,以便后面可以更清晰地认识嵌套事务的逻辑。

Spring事务使用AOP的机制实现,会在@Transactional注解修饰的方法前后分别织入开启事务的逻辑,以及提交或回滚的逻辑。@Transactional可以修饰在方法或者类上,区别就在于修饰于类上的,会对该类下符合条件的方法(例如private修饰的方法就不符合条件)前后都织入事务的逻辑。

spring事务传播机制

声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。 当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。 声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。

声明式

在配置文件中设置以下6项

required

如果客户端没有事务 在bean中新起一个事务

如果客户端有事务bean 中就加进去

子事务

主事务

结果

异常

正常,并try-catch异常

均回滚

正常

异常

均回滚

正常

异常,并try-catch异常

不回滚

requiresNew

不管客户端有没有事务服务器段都新起一个事务

如果客户端有事务就将事务挂起

子事务

主事务

结果

异常

正常,并try-catch异常

子回滚,主不回滚

正常

异常

子不回滚,主回滚

异常

正常

均回滚

supports

如果客户端没有事务服务端也没有事务

如果客户端有事务服务端就加一个事务

mandatcry

如果客户端没有事务服务端就会报错

如果客户端有事务服务端就加事务

notSupported

不管客户端有没有事务服务端都没有事务

如果客户端有事务服务端就挂起

never

不管客户端有没有事务服务端都没有事务

如果客户端有事务就报错

NESTED

如果当前存在事务,则在嵌套事务内执行。

如果当前没有事务,则进行与REQUIRED类似的操作

子事务

主事务

结果

异常

正常,并try-catch异常

子回滚,主不回滚

正常

异常

均回滚

异常

正常

均回滚

编程式事务

Javax.transaction.UserTranscation

通常情况下只需要一个 @Transactional 就搞定了(代码侵入性降到了最低),就像这样:

代码语言:javascript复制
/**
 * 模拟转账
 */
@Transactional
public void handle() {
 // 转账
 transfer(double money);
 // 减自己的钱
  Reduce(double money);
}

编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。 你比如说,使用 TransactionTemplate 来管理事务:

代码语言:javascript复制
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}

再比如说,使用 TransactionManager 来管理事务:

代码语言:javascript复制
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  业务代码
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}

就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。 在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。

事务隔离级别

前面我们已经了解了数据库的事务隔离级别,再来理解 Spring 的事务隔离级别就容易多了。 TransactionDefinition 中一共定义了 5 种事务隔离级别:

ISOLATION_DEFAULT,使用数据库默认的隔离级别,MySql 默认采用的是 REPEATABLE_READ,也就是可重复读。 ISOLATION_READ_UNCOMMITTED,最低的隔离级别,可能会出现脏读、幻读或者不可重复读。 ISOLATION_READ_COMMITTED,允许读取并发事务提交的数据,可以防止脏读,但幻读和不可重复读仍然有可能发生。 ISOLATION_REPEATABLE_READ,对同一字段的多次读取结果都是一致的,除非数据是被自身事务所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。 ISOLATION_SERIALIZABLE,最高的隔离级别,虽然可以阻止脏读、幻读和不可重复读,但会严重影响程序性能。 通常情况下,我们采用默认的隔离级别 ISOLATION_DEFAULT 就可以了,也就是交给数据库来决定。

事务的超时时间

事务超时 timeout ,也就是指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。 假如事务的执行时间格外的长,由于事务涉及到对数据库的锁定,就会导致长时间运行的事务占用数据库资源。

事务的只读属性

事务的只读属性readOnly, 如果一个事务只是对数据库执行读操作,那么该数据库就可以利用事务的只读属性,采取优化措施,适用于多条数据库查询操作中。 为什么一个查询操作还要启用事务支持呢? 这是因为 MySql(innodb)默认对每一个连接都启用了 autocommit 模式,在该模式下,每一个发送到 MySql 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。 那如果我们给方法加上了 @Transactional 注解,那这个方法中所有的 SQL 都会放在一个事务里。否则,每条 SQL 都会单独开启一个事务,中间被其他事务修改了数据,都会实时读取到。 有些情况下,当一次执行多条查询语句时,需要保证数据一致性时,就需要启用事务支持。否则上一条 SQL 查询后,被其他用户改变了数据,那么下一个 SQL 查询可能就会出现不一致的状态。

事务的回滚策略

回滚策略rollbackFor,用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。默认情况下,事务只在出现运行时异常(Runtime Exception)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。

如果你想要回滚特定的异常类型的话,可以这样设置:

代码语言:javascript复制
@Transactional(rollbackFor= MyException.class)

事务的不回滚策略

不回滚策略noRollbackFor,用于指定不触发事务回滚的异常类型,可以指定多个异常类型。

@Transaction失效场景

  • 作用于非public方法上,之所以会失效是因为在Spring AOP 代理时,如下图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute

方法,获取Transactional 注解的事务配置信息。 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

注意:protected、private修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

  • propagation设置问题,会导致事务不生效,也就事务不会回滚
  • rollbackFor指定事务回滚的异常类型
  • 同个类中的调用被@transaction修饰的方法,会失效,因为只有当事务方法被当前类以外的代码调用,才会由spring生成的代理对象来管理。
  • try catch导致失效
  • 数据库不支持事务

0 人点赞