Spring注解事务@Transactional

举报
object 发表于 2022/10/24 10:07:58 2022/10/24
【摘要】 事务是逻辑上的一组最小的操作,要么都执行,要么都不执行。 现实中最多的例子就是转账,A给B转账,包含了两步操作。 ​ 1.A扣款 ​ 2.B收款 这两步是不可拆分的一个事务组成。A扣款后,B必须收款,若因为系统故障导致A扣款后,未执行B收款操作。则事务要保证A扣款/B收款都不执行,即事务回滚。

Spring事务

什么是事务

事务是逻辑上的一组最小的操作,要么都执行,要么都不执行。

现实中最多的例子就是转账,A给B转账,包含了两步操作。

​ 1.A扣款

​ 2.B收款

这两步是不可拆分的一个事务组成。A扣款后,B必须收款,若因为系统故障导致A扣款后,未执行B收款操作。则事务要保证A扣款/B收款都不执行,即事务回滚。

事务特性(ACID)

事务特性也是面试中问道比较多的四个特性。分别是:

  • **原子性(Atomicity):**主要体现为不可拆分,一个事务中的所有操作都不可拆分,要么都完成、要么都不完成。A扣款/B收款不可拆分。
  • **隔离性(Isolation):**事务与事务之间互不干扰。(不绝对,根据隔离级别)
  • **持久性(Durability):**事务结束,数据的修改是永久的,不可丢失。
  • **一致性(Consistency):**事务开始前与事务结束后,数据完整性没有被破坏,所有一切按照预期结果执行。

ps:虽然名字叫ACID,之所以把C放在最后,是因为C是事务的最终目的。AID都是手段。一致性没有其他三个那么明确的界限说明,可以简单理解为“质量守恒”,所有数据,都需要符合预期规则行动,这里减少了,则对应的就需要增加(或者总量也要跟着减少)。

Spring事务的必备条件

是否能支持事务,最主要是看数据库是否支持。

例如:最常用的mysql存在两种引擎。innodb和myisam引擎,innodb是支持事务的,myisam是不支持的。mysql5.5以后默认的引擎为innodb。

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);
          }
}

声明式实现事务

主要通过@Transactional注解实现,底层原理是基于AOP实现。

@Transactional代码示例:

// 定义异常回滚,发生指定异常时会自动回滚
@Transactional(rollbackFor = Exception.class)
public void transMethod {
  // ..... 业务代码
}

Spring事务主要接口

Spring事务管理相关最重要的几个接口:

  • PlatformTransactionManager: 事务管理器,spring事务策略核心
  • TransactionDefinitioin:事务详情,隔离级别、传播行为、只读、超时、回滚规则
  • TransactionStatus:事务状态

PlatformTransactionManager事务管理接口定义了3个函数:

package org.springframework.transaction;
import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
    //获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

TransactionDefinition事务属性,定义了5个函数以及常量

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时
package org.springframework.transaction;
import org.springframework.lang.Nullable;

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。
    int getPropagationBehavior();
    //返回事务的隔离级别,默认值是 DEFAULT
    int getIsolationLevel();
    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    int getTimeout();
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();
    @Nullable
    String getName();
}

TransactionStatus:事务状态,定义了4个函数和1个常量

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事务
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
}

事务属性介绍

实际开发,用的比较多的是声明式事务,代码嵌入少。就以@Transactional注解示例

事务传播级别

事务传播指的是,多个事务函数嵌套调用时,指定事务是使用同一个事务,还是各自自己的事务,便于回滚时的范围限制。

代码示例:

// 定义事务传播级别
@Transactional(propagation = Propagation.REQUIRED)
public void transMethod {
  // ..... 业务代码
}

所有的可选择枚举值:

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;
    }
}
  • Propagation.REQUIRED

    使用最多,也是默认值。如果已开启事务,则使用同一个事务,如果还未开启事务,则开启新的事务。

    举例:A方法里面有B、C两个方法,如果A方法上使用了该注解,则回滚时,所有ABC的操作都会回滚。

  • Propagation.SUPPORTS

    判断当前是否存在事务,如果有,则加入。如果没有,则不开启事务,已非事务状态继续运行。

    举例:A方法里面有B、C两个方法,BC上有该注解,如果A方法没有开启事务,则BC也会开启,A开启了,则BC也开启

  • Propagation.MANDATORY

    判断当前是否存在事务,如果有,则加入。如果没有,则抛出异常。

    举例:A方法里面有B、C两个方法,BC上有该注解,如果A方法没有开启事务,则执行BC时,则会直接抛出异常。

  • Propagation.REQUIRES_NEW

    无论当前是否存在事务,都开启一个新的事务。且两个事务互不干扰。

    举例:A方法里面有B、C两个方法,A、B、C有该注解。A、C都回滚但B不一定回滚。3个事务彼此隔离,互不影响,但是如果BC因为异常回滚,且该异常未被捕获,抛出到了A,则A也会回滚,因为A检查到了异常,不是因为BC回滚,而只是因为检查到了异常。

  • Propagation.NOT_SUPPORTED

    判断当前是否存在事务,如果有,则将该事务挂起,以非事务运行。如果没有,则继续以非事务运行。

    举例:A方法里面有B、C两个方法,BC上有该注解,如果A开启了事务,执行B时,检查到有事务,则将事务挂起,B方法执行期间,事务异常也不会回滚。执行完毕后,事务恢复运行。

  • Propagation.NEVER

    判断当前是否存在事务,如果有,则抛出异常。如果没有,则继续以非事务方式运行。

    举例:A方法里面有B、C两个方法,BC上有该注解,如果A开启了事务,执行B时,检查到有事务,则抛出异常。如果A未开启,则继续非事务执行

  • Propagation.NESTED

    判断当前是否存在事务,如果有,则开启一个新的嵌套事务,如果没有,则开启一个事务,类似Propagation.REQUIRED的事务。可进行加入事务。

    ps:这个很容易和Propagation.REQUIRES_NEW搞混,但实际是不同的,Propagation.REQUIRES_NEW更绝对,彼此隔离性更强。Propagation.NESTED有事务时,开启的是子事务,依赖外部事务,外部事务回滚,内部也需要回滚。则外部提交事务,内部才会一起提交事务。

    举例:A方法里面有B、C两个方法,BC上有该注解,如果A开启了事务,执行B时,检查到有事务,则开启新的嵌套事务,如果A抛出了异常,则AB都需要回滚,但是B异常B先回滚,A正常执行,A执行完毕后,事务才会整体提交。如果没有则开启一个新的事务。

事务隔离级别

// 定义事务隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transMethod {
  // ..... 业务代码
}

隔离级别枚举

package org.springframework.transaction.annotation;

public enum Isolation {
    DEFAULT(-1),
    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;
    }
}
  • Isolation.DEFAULT

    默认级别,不同数据库的默认级别不同,mysql是REPEATABLE_READ,oracle是READ_COMMITTED

  • Isolation.READ_UNCOMMITTED

    读未提交,事务运行所做的改动,可以被其他事务所读取。风险:脏读、不可重复读、幻读

    举例:如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据

  • Isolation.READ_COMMITTED

    读已提交,事务运行所做的改动,事务未提交前,其他事务不可读取。风险:不可重复读、幻读

    举例:如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

  • Isolation.REPEATABLE_READ

    可重复读,事务运行所做的改动,事务未提交前,其他事务不可读取,且事务运行过程中,多次读取的数据结果保持一致,不会被其他事务所影响。风险:幻读

    举例:可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

  • Isolation.SERIALIZABLE

    可串行化,强制事务排序,避免事务冲突。风险:性能低。

    举例:提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的

事务超时属性

// 定义事务超时,5s后事务回滚
@Transactional(timeout = 5)
public void transMethod {
  // ..... 业务代码
}

事务只读属性

// 定义事务只读,默认为false
@Transactional(readOnly = true)
public void transMethod {
  // ..... 业务代码
}

事务只读,只读事务不涉及数据修改。主要作用,保持数据一致性,例如数据统计,避免因个别表的改动而导致数据的异常。

事务回滚规则

// 定义事务回滚,运行时异常回滚,值为类
@Transactional(rollbackFor = RuntimeException.class)
public void transMethod {
  // ..... 业务代码
}

// 定义事务回滚,运行时异常回滚,值为字符串
@Transactional(rollbackForClassName = "java.lang.RuntimeException")
public void transMethod {
  // ..... 业务代码
}

// 定义事务不回滚,运行时异常不回滚,值为类
@Transactional(noRollbackFor = RuntimeException.class)
public void transMethod {
  // ..... 业务代码
}

// 定义事务不回滚,运行时异常不回滚,值为字符串
@Transactional(noRollbackForClassName = "java.lang.RuntimeException")
public void transMethod {
  // ..... 业务代码
}

共计四种设置方式,共计两种回滚规则,xxx异常回滚,xxx异常不回滚。默认RuntimeException异常回滚。

@Transactional注解使用

@Transactional的作用范围

  1. 方法:常用,表示该函数开启特定事务。需要注意:作用在方法上需要保证该方法为public方法,否则不生效
  2. :作用类上,表示类中所有public方法都开启特定事务。如果方法上注解已经存在,方法覆盖类的注解
  3. 接口:不推荐。若配置了spring AOP 使用cglib动态代理,会导致@Transactional失效

@Transactional常用属性设置

@Transactional源码

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

常用配置参数及其含义:

属性名 说明
propagation 事务的传播级别,默认REQUIRED。详见事务传播级别
isolation 事务的隔离级别,默认DEFAULT。详见事务隔离级别
timeout 事务的超时属性,默认-1(不超时)。详见事务超时属性
readOnly 事务只读属性,默认false。详见事务只读属性
rollbackFor 事务回滚规则,默认RuntimException。详见事务回滚规则

@Transactional事务注解原理

@Transactional的底层原理还是代码编程事务+spring AOP动态代理实现。如果一个类中的public方法被标注了@Transactionial注解,spring容器启动时就会为其创建一个代理类,在调用事务方法时,会执行代理类中的方法进行增强,也就是声明事务,并在完成后提交事务,异常时回滚事务。

直接看主要的核心代码:


public <T> T execute(TransactionCallback<T> action) throws TransactionException {
   if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
      return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
   }
   else {
      TransactionStatus status = this.transactionManager.getTransaction(this);
      T result;
      try {
         //执行你注解声明地方的代码
         result = action.doInTransaction(status);
      }
      catch (RuntimeException ex) {
         // Transactional code threw application exception -> rollback
         rollbackOnException(status, ex);
         throw ex;
      }
      catch (Error err) {
         // Transactional code threw error -> rollback
         rollbackOnException(status, err);
         throw err;
      }
      catch (Exception ex) {
         // Transactional code threw unexpected exception -> rollback
         rollbackOnException(status, ex);
         throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
      }
      //提交事物
      this.transactionManager.commit(status);
      return result;
   }
}

springAop自调用问题

上面已经介绍@Transactional是基于springAOP实现的,不过有种情况却不会无法触发AOP的增强方法,就是自调用,简单来说,就是事务方法是通过对象本身自己调用,而不是其他对象调用。AOP的增强是代理类持有被代理对象的实例,所以自调用时,是不会触发AOP,也就不会触发事务了。

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

@Service
public class MyService {

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

@Transactional失效情况

  • @Transactional注解只作用到public方法上事务才生效;springAOP相关
  • @Transactional自调用不生效;springAOP相关
  • @Transactional注解的函数实例不受spring管控不生效;springAOP相关
  • 数据库需要支持事务机制,否则不生效,如mysql的myisam不生效; 数据库相关底层原理
  • 捕获了异常并未抛出异常;spring事务底层原理相关
  • 错误的异常回滚机制或错误的事务传播级别;spring事务底层原理相关
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。