SpringTransaction

Spring事务

Spring的两种事务管理方式

编程式事务管理

通过TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用。

使用 TransactionTemplate 进行编程式事务管理:

@Autowired
private TransactionTemplate transactionTemplate;

public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}

使用 TransactionManager 进行编程式事务管理:

@Autowired
private PlatformTransactionManager transactionManager;

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

声明式事务管理

推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。

Spring事务管理接口

  1. PlatformTransactionManager:(平台)事务管理器,Spring事务策略的核心;

    public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
    }

    通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager),Hibernate(HibernateTransactionManager),JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

  2. TransactionDefinition:事务定义信息(事务隔离级别,传播行为,超时,只读,回滚规则);

    public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    // 返回事务的传播特性,默认值为REQUIRED
    default int getPropagationBehavior() {
    return 0;
    }
    // 返回事务的隔离级别,默认值是DEFAULT
    default int getIsolationLevel() {
    return -1;
    }
    // 返回事务的超时时间,默认值为-1。
    // 如果超过该时间限制但事务还没有完成,则自动回滚事务。
    default int getTimeout() {
    return -1;
    }
    // 返回是否为只读事务,默认值为 false
    default boolean isReadOnly() {
    return false;
    }

    @Nullable
    default String getName() {
    return null;
    }

    static TransactionDefinition withDefaults() {
    return StaticTransactionDefinition.INSTANCE;
    }
    }
  3. TransactionStatus:事务运行状态;

@Transactional 属性详解

事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题。

示例:在A类的aMethod()方法中调用了B类的bMethod()方法。这时就涉及到业务层方法之间互相调用的事务问题。如果bMethod()发生异常需要回滚,如何配置事务传播行为才能让aMethod()也跟着回滚呢?

Class A {
@Transactional(propagation=propagation.xxx)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}

Class B {
@Transactional(propagation=propagation.xxx)
public void bMethod {
//do something
}
}
  1. TransactionDefinition.PROPAGATION_REQUIRED

    使用的最多的一个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

    • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
    • 如果外部方法开启事务并且被Propagation.REQUIRED修饰,则所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
  2. TransactionDefinition.PROPAGATION_REQUIRES_NEW

    创建一个新的事务,如果当前存在事务,则把当前事务挂起。即不论外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会开启自己的事务,且开启的事务相互独立,互不干扰。

    示例:上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则,aMethod()同样也会回滚,因为这个异常被aMethod()的事务管理机制检测到了。

    Class A {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
    //do something
    B b = new B();
    b.bMethod();
    }
    }

    Class B {
    @Transactional(propagation=propagation.REQUIRES_NEW)
    public void bMethod {
    //do something
    }
    }
  3. TransactionDefinition.PROPAGATION_NESTED

    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等价于TransactionDefinition.PROPAGATION_REQUIRED。也就是说:

    • 在外部方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰;
    • 如果外部方法开启事务,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务;
    Class A {
    // 如果aMethod回滚,bMethod和bMethod2都要回滚;
    // 如果bMethod回滚,不会造成aMethod和bMethod2回滚;
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
    //do something
    B b = new B();
    b.bMethod();
    b.bMethod2();
    }
    }

    Class B {
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void bMethod {
    //do something
    }
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void bMethod2 {
    //do something
    }
    }
  4. TransactionDefinition.PROPAGATION_MANDATORY

    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  5. TransactionDefinition.PROPAGATION_SUPPORTS

    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  6. TransactionDefinition.PROPAGATION_NOT_SUPPORTED

    以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  7. TransactionDefinition.PROPAGATION_NEVER

    以非事务方式运行,如果当前存在事务,则抛出异常。

事务隔离级别

  • TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别;
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别;

MySQL InnoDB存储引擎默认支持的隔离级别是REPEATABLE-READ,在该隔离级别下InnoDB引擎使用的是Next-Key Lock锁算法,因为可以避免幻读的产生。

事务超时属性

指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以int的值来表示超时时间,单位是秒,默认值为-1。

事务的只读属性

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

为什么数据查询操作需要启用事务支持?

如果不加Transactional,每条SQL会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。

  • 如果一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL执行期间的读一致性;
  • 如果一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持;

事务的回滚规则

默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error也会导致事务回滚,但是在遇到检查型(Checked)异常时不会回滚。

// 回滚自定义的异常类型
@Transactional(rollbackFor= MyException.class)

@Transactional的使用

  1. @Transactional的作用范围

    方法:推荐将注解使用于方法上,注意,该注解只能应用到public方法上,否则不生效;

    类:如果这个注解使用在类上,表明该注解对该类中所有的public方法都生效;

    接口:不推荐在接口上使用;

  2. 参数配置

    属性名 说明
    propagation 事务的传播行为,默认值为 REQUIRED
    isolation 事务的隔离级别,默认值采用 DEFAULT
    timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    readOnly 指定事务是否为只读事务,默认值为 false。
    rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。
  3. @Transactional事务注解原理

    @Transactional的工作机制是基于AOP实现的,AOP是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现了接口,会使用CGLIB动态代理。

    public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    Class<?> targetClass = config.getTargetClass();
    if (targetClass == null) {
    throw new AopConfigException("TargetSource cannot determine target class: " +
    "Either an interface or a target is required for proxy creation.");
    }
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    return new JdkDynamicAopProxy(config);
    }
    return new ObjenesisCglibAopProxy(config);
    } else {
    return new JdkDynamicAopProxy(config);
    }
    }
    .......
    }

    如果一个类或者类中的public方法上被标注注解@Transactional,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional注解的public方法时,实际调用的是TransactionInterceptor类中的invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

  4. Spring AOP自调用的问题

    若同一类中的其他没有@Transactional注解的方法内部调用有 @Transactional注解的方法,有@Transactional注解的方法的事务会失效。

    这是由于Spring AOP代理造成的,因为只有当@Transactional注解的方法在类以外被调用的时候,Spring事务管理才生效。

    示例:MyService类中的method1()调用method2()就会导致method2()的事务失效。

    @Service
    public class MyService {

    private void method1() {
    method2();
    //......
    }
    @Transactional
    public void method2() {
    //......
    }
    }

    解决办法:避免同一类中自调用,或者使用AspectJ取代Spring AOP代理。

Author: Jiayi Yang
Link: https://jiayiy.github.io/2020/06/11/SpringTransaction/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.