聊一聊Spring事务中的7种传播特性

原创 一安 一安未来 2023-06-13 08:01 发表于北京

收录于合集#干货分享集141个

图片

大家好,我是一安~

简介

事务的传播特性

假设我们有两个方法:methodAmethodB,如果methodA调用methodB,那么将采用什么样的事务形式,这就叫做事务的传播特性,传播特性是作用于内层方法

目前,SpringTransactionDefinition类中定义了以下7种传播特性,具体特性我们接下来会分析:

   /**
    * PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务
    * PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
    * PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务
    * PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务
    * PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
    * PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
    * PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
    */

案例

介绍比较常用的PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW,其他的了解即可。

新建测试表

CREATE TABLE `demo1` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `demo2` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `addr` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

引入依赖

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>
  <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.1</version>
  </dependency>
  <!--mysql-->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>

为了方便直接引入了lombokmybatis-plus,小编这里利用之前搭建SpringCloud Alibaba的代码生成器直接生成了代码

图片

增加配置

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&nullCatalogMeansCurrent=true
    username: root
    password: root

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
  mapper-locations: classpath:mapper/*.xml

测试

@SpringBootTest
public class TransactionTest {


    /**
    * PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务
    * PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
    * PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务
    * PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务
    * PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
    * PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
    * PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
    * */

    @Autowired
    private Demo1Service demo1Service;

    /**
     *  测试:
     *      1.demo1Service正常写入,不开启事务
     *      2.demo2Service 制造int a = 1/0;异常,不开启事务
     *
     *  结论:
     *      1.系统抛出java.lang.ArithmeticException: / by zero
     *      2.demo1 和 demo2 正常写入数据,没有回滚
     */
    @Test
    public void noTransaction() {
        Demo1 demo1 = new Demo1();
        demo1.setName("一安未来").setAge("20");
        demo1Service.insertDemo1(demo1);
    }


    /**
     *  测试:
     *      1.demo1Service正常写入,开启默认事务REQUIRED
     *      2.demo2Service 制造int a = 1/0;异常,不开启事务
     *       或
     *      1.demo1Service正制造int a = 1/0;异常,开启默认事务REQUIRED
     *      2.demo2Service 正常写入,不开启事务
     *
     *  结论:
     *      1.系统抛出java.lang.ArithmeticException: / by zero
     *      2.demo1 和 demo2 执行回滚
     *      3.如果存在外层事务,则使用外层事务;
     */
    @Test
    public void transaction_REQUIRED() {
        Demo1 demo1 = new Demo1();
        demo1.setName("一安未来").setAge("20");
        demo1Service.insertDemo1(demo1);
    }

    /**
     *  测试:
     *      1.demo1Service正常写入,不开启事务
     *      2.demo2Service 制造int a = 1/0;异常,开启默认事务REQUIRED
     *
     *  结论:
     *      1.系统抛出java.lang.ArithmeticException: / by zero
     *      2.demo1 未回滚,demo2 执行回滚
     *      3.如果不存在外层事务,就主动开启事务;
     */
    @Test
    public void transaction_REQUIRED2() {
        Demo1 demo1 = new Demo1();
        demo1.setName("一安未来").setAge("20");
        demo1Service.insertDemo1(demo1);
    }

    /**
     *  测试:
     *      1.demo1Service制造int a = 1/0;异常,开启默认事务REQUIRED
     *      2.demo2Service 正常写入,开启事务REQUIRES_NEW
     *
     *  结论:
     *      1.系统抛出java.lang.ArithmeticException: / by zero
     *      2.demo1执行回滚 , demo2未回滚
     *      3.总是主动开启事务;如果存在外层事务,就将外层事务挂起
     */
    @Test
    public void transaction_REQUIRED3() {
        Demo1 demo1 = new Demo1();
        demo1.setName("一安未来").setAge("20");
        demo1Service.insertDemo1(demo1);
    }
}

注意事项:

  • 禁止放在非public方法上

  • 不注意事务上下文环境,即事务的传播行为

  • @Transactional注解属性rollbackFor设置错误

  • 同一个类中方法调用,导致@Transactional失效

  • 异常catch捕捉导致@Transactional失效

  • 数据库引擎不支持事务

补充说明

Spring的事务是如何进行回滚的?

在使用Spring框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用户自己通过代码来控制事务的处理逻辑,还有一种是声明式事务,通过@Transactional注解来实现。

其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,Spring对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由Spring框架来帮助进行控制。

其实事务操作是AOP的一个核心体现,正常情况下要通过通知来完成核心功能,但事务不是通过通知来实现,而是通过TransactionInterceptorinvoke来实现的

  @Nullable
  public Object invoke(final MethodInvocation invocation) throws Throwable {
      Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
      return this.invokeWithinTransaction(invocation.getMethod(), targetClass, new TransactionAspectSupport.CoroutinesInvocationCallback() {
          @Nullable
          public Object proceedWithInvocation() throws Throwable {
              return invocation.proceed();
          }

          public Object getTarget() {
              return invocation.getThis();
          }

          public Object[] getArguments() {
              return invocation.getArguments();
          }
      });
  }

当一个方法添加@Transactional注解之后,Spring会基于这个类生成一个代理对象,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关闭,然后去执行具体的业务逻辑。

如果执行逻辑没有出现异常,那么代理逻辑就会直接提交:TransactionAspectSupportcommitTransactionAfterReturning()->commit()->processCommit()->doCommit()

    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");
        }

        try {
            con.commit();
        } catch (SQLException var5) {
            throw this.translateException("JDBC commit", var5);
        }
    }

如果出现任何异常情况,那么直接进行回滚操作:TransactionAspectSupportcompleteTransactionAfterThrowing()->rollback()->processRollback()->doRollback()

    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }

        try {
            con.rollback();
        } catch (SQLException var5) {
            throw this.translateException("JDBC rollback", var5);
        }
    }

当事务执行完毕需要清除相关事务:TransactionAspectSupportcleanupTransactionInfo()

如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!

图片

面试官:说说什么是本地缓存、分布式缓存以及多级缓存,它们各自的优缺点?


SpringBoot整合Prometheus+Pushgetway采集自定义指标数据(值得收藏)


基于Shrio的分布式微服务权限控制和会话管理的详细设计与实现

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值