文档章节

Spring事务传播属性有那么难吗?看这一篇就够了

 不学无数的程序员
发布于 10/16 18:23
字数 3435
阅读 3641
收藏 128

Spring事务传播属性有那么难吗?看这一篇就够了

笔者文笔功力尚浅,如有不妥,请慷慨指出,必定感激不尽

学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性。

传播属性

传播属性定义的是当一个事务方法碰到另一个事务方法时的处理行为,一共有七种行为,定义如下

传播性 描述
PROPAGATION_REQUIRED 0 支持当前事务,如果没有就新建事务
PROPAGATION_SUPPORTS 1 支持当前事务,如果没有就不以事务的方式运行
PROPAGATION_MANDATORY 2 支持当前事务,如果当前没事务就抛异常
PROPAGATION_REQUIRES_NEW 3 无论当前是否有事务,都会新起一个事务
PROPAGATION_NOT_SUPPORTED 4 不支持事务,如果当前存在事务,就将此事务挂起不以事务方式运行
PROPAGATION_NEVER 5 不支持事务,如果有事务就抛异常
PROPAGATION_NESTED 6 如果当前存在事务,在当前事务中再新起一个事务

其实只看概念的话已经很直截了当了说明了每个传播性的作用,此时我们再用具体的例子演示一下每个传播性属性下的行为。

此次演示我们使用的是H2数据库,这个数据库是作用在内存里面的,所以对于我们演示事务效果来说正好,无需我们在进行其他的配置了,我们新建一个表。将下面语句放在schema.sql文件里面即可,SpringBoot程序在启动的时候就会自动为我们在内存里面建立这样的一个表。

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

演示之前我们会定义两个类FooServiceBarService。我们使用BarService 里面的方法进行调用FooService 中的方法。

环境准备

在进行事务演示之前,其实可以分为以下几种情况,根据排列组合,我们可以得出以下八种情况

  • 调用者:有无事务
  • 调用者:是否有异常
  • 被调用者:有无事务**(这个是通过传播属性进行控制的)**所以并不在排列组合中
  • 被调用者:是否有异常
调用者是否有事务 调用者是否有异常 被调用者是否有异常

异常类

其中的RollbackException是我们自己定义的一个异常类

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;

 	// PROPAGATION_REQUIRED演示 无事务
    @Override
    public void testRequiredNoTransactional() throws RollbackException {
        fooService.testRequiredTransactional();
    }
}

调用者

BarService中定义两个方法,一个是带着事务的,一个是不带事务的

// 有事务
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {

}

// 无事务
@Override
public void noTransactional() throws RollbackException {
    
}

接下来我们就根据俄上面定义的八种情况进行事务传播属性的学习。

PROPAGATION_REQUIRED

在此传播属性下,被调用方是否新建事务取决去调用者是否带着事务。

想要了解这个传播属性的特性,其实我们演示上面八种情况的两个例子就够了

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况我们在被调用者抛出异常的情况下,如果查询不到插入的数据,那么就说明被调用者在调用者没有事务的情况下自己新建了事务。
  • 第二种情况我们在调用者抛出异常的情况下,如果查询不到插入的数据,那么就说明被调用者在调用者有事务的情况下就加入当前事务了。

我们先来看一下被调用者的类的方法例子。

@Service
public class FooServiceImpl implements FooService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // REQUIRED传播属性-被调用者有异常抛出
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredHasException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")");
        throw new RollbackException();
    }

    // REQUIRED传播属性-被调用者无异常抛出
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredNoException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
    }
}

接下来我们看一下调用者方法的例子

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;

    // 有事务
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hasTransactional() throws RollbackException {
        // 调用者有事务,抛异常  被调用者无异常
        fooService.testRequiredNoException();
        throw new RollbackException();
    }

    // 无事务
    @Override
    public void noTransactional() throws RollbackException {
        // 调用者无事务,不抛异常  被调用者有异常
        fooService.testRequiredHasException();
    }
}

此时我们在程序调用时进行查询

String noException = Global.REQUIRED_NO_EXCEPTION;
String hasException = Global.REQUIRED_HAS_EXCEPTION;

try {
    barService.noTransactional();
}catch (Exception e){
    log.info("第一种情况 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
}

try {
    barService.hasTransactional();
}catch (Exception e){
    log.info("第二种情况 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
}

查看打印出来的日志

2019-10-16 13:02:04.142  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 13:02:04.143  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

我们看到我们都没有查到相应的数据,说明数据都回滚了。此时我们应该就理解了那句话支持当前事务,如果没有就新建事务

PROPAGATION_SUPPORTS

被调用者是否有事务,完全依赖于调用者,调用者有事务则有事务,调用者没事务则没事务。

接下来我们还是用上面的两个例子进行演示

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况:被调用者抛出异常的情况下,如果仍能查询到数据,说明事务没有回滚,说明被调用者没有事务
  • 第二种情况:调用者抛出异常情况下,如果查不到数据,说明两个方法在一个事务中

接下来仍然是例子演示

被调用者,只是将@Transactional 注解中的propagation 属性更换为了Propagation.SUPPORTS

// SUPPORTS传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}

// SUPPORTS传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}

调用者和上面的例子调用一样,我们直接查看执行效果

2019-10-16 13:50:27.738  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 1
2019-10-16 13:50:27.741  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

我们看到了在第一种情况下查到了数据,说明在第一种情况下被调用者是没有事务的。此时我们应该就理解了这句话 支持当前事务,如果没有就不以事务的方式运行

PROPAGATION_MANDATORY

依然是这两个例子进行演示

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况:因为调用者没有事务,所以此传播属性下应该是抛异常的
  • 第二种情况:被调用者的事务和调用者事务是同样的

接下来是被调用者的代码例子

// MANDATORY传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}

// MANDATORY传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}

调用者和上面的例子调用一样,我们直接查看执行效果

2019-10-16 13:58:39.178 ERROR 12317 --- [           main] c.e.t.t.TransactionApplication           : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
2019-10-16 13:58:39.276  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 13:58:39.281  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

我们发现和我们推测一样,说明被调用者是不会自己新建事务的,此时我们应该就理解了这句话支持当前事务,如果当前没事务就抛异常

PROPAGATION_REQUIRES_NEW

此传播属性下,无论调用者是否有事务,被调用者都会新建一个事务

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况:调用者无事务,被调用者会新建事务,所以查不到数据
  • 第二种情况:调用者有事务,被调用者会新建一个事务,所以调用者抛异常影响不到被调用者,所以能查到数据

接下来我们演示代码。

被调用者

// REQUIRES_NEW传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')");
    throw new RollbackException();
}

// REQUIRES_NEW传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')");
}

调用者的例子和上面的相同,我们直接来看执行情况

2019-10-16 16:29:20.296  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 16:29:20.298  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 1

我们发现和我们的推论是一样的,说明调用者的事务和被调用者的事务完全无关。此时我们应该就理解这句话了无论当前是否有事务,都会新起一个事务

PROPAGATION_NOT_SUPPORTED

无论调用者是否有事务,被调用者都不以事务的方法运行

同样是这两个例子

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况:被调用者都不会有事务,那么在抛异常之后就能查到相应的数据
  • 第二种情况:在调用者有事务的情况下,被调用者也会在无事务环境下运行,所以我们依然能查到数据

接下来验证我们的猜测

// NOT_SUPPORTED传播属性-被调用者有异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}

// NOT_SUPPORTED传播属性-被调用者无异常抛出
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')");
}

然后查看执行结果

2019-10-16 16:38:35.065  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 1
2019-10-16 16:38:35.067  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 1

我们可以看到在最后两种情况都查到了数据,根据演示效果应该可以理解这句话了,不支持事务,如果当前存在事务,就将此事务挂起不以事务方式运行

PROPAGATION_NEVER

调用者有事务,被调用者就会抛出异常

调用者是否有事务 调用者是否有异常 被调用者是否有异常

这个就不演示,相信大家看到这里应该都会明白在第一种情况下我们是能够查到数据的。在第二种情况下由于调用者带着事务,所以会抛异常。

PROPAGATION_NESTED

此传播属性下,被调用者的事务是调用者的事务的子集。

我们重点说一下NESTED的传播属性的特性

调用者是否有事务 说明
被调用者会新起一个事务,此事务和调用者事务是一个嵌套的关系
被调用者会自己新起一个事务

关于什么是嵌套事务的关系,我们用下面三个例子能够进行演示。

调用者是否有事务 调用者是否有异常 被调用者是否有异常
  • 第一种情况:如果查不到数据,则说明在调用者无事务情况下,被调用者会新起一个事务
  • 第二种情况:如果查不到数据,说明外层事务能够影响内层事务
  • 第三种情况:如果查到数据,说明内层事务不影响外层事务

接下来我们编写具体的代码

// NESTED传播属性-回滚事务
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')");
   // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    throw new RollbackException();
}

// NESTED传播属性-不回滚事务
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
}

然后接下来的调用者也会有点区别

@Override
@Transactional()
public void hasTransactionalNoException() throws RollbackException {
    // NESTED传播属性 - 调用者有事务,不抛异常  被调用者有异常
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
    fooService.testNestedHasException();
}

然后执行效果

2019-10-16 18:01:06.387  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 18:01:06.389  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0
2019-10-16 18:01:06.390  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第三种情况 1

可以看出来嵌套事务的本质就是外层会影响内层,内层不影响外层。而REQUIRES_NEW则是互不影响

总结

到现在我们已经全部分析完了七种传播属性,从写这篇文章开始到结束其中也碰到过一些坑,有些是不自己实践一遍是根本不知道的,所以我还是建议读者看完这篇文章以后自己进行实践,演示各种情况,只有这样才能够烂熟于心。

本文代码地址

© 著作权归作者所有

不学无数的程序员

粉丝 43
博文 48
码字总数 89211
作品 0
私信 提问
加载中

评论(5)

风一样的美男子
风一样的美男子
写的能走心点吗?
小破孩9527
小破孩9527
哪里有问题吗?❓
阿黑呀
阿黑呀
挺详细的
金贞花
金贞花
没有精髓
小破孩9527
小破孩9527
所以事务传播属性的精髓应该是什么?
脱离 Spring 实现复杂嵌套事务,之五(SUPPORTS - 跟随环境)

本文是<实现 Spring 的事务控制>系列文章中一篇。本文假设读者已经阅读并理解《实现 Spring 的事务控制,之一(必要的概念)》文中所涉及的概念(当前连接、引用计数),以及数据库连接的(n...

哈库纳
2014/02/17
3K
1
Hasor JDBC 的难关,嵌套事务处理思路

本文存属提醒我自己不要忘记的事情。也是向大家展示 Hasor 对于 JDBC 方面即将的又一个重大的进步。目前该方案还在实施中。 前段时间闲着没事分析了下 Spring JDBC ,觉得 Spring JDBC 的设计...

哈库纳
2013/12/29
1K
9
Spring 中常用的两种事务配置方式以及事务的传播性、隔离级别

一、注解式事务 1、注解式事务在平时的开发中使用的挺多,工作的两个公司中看到很多项目使用了这种方式,下面看看具体的配置demo。 2、事务配置实例 (1)、spring+mybatis 事务配置 (2)、...

哲别0
2018/04/20
156
0
Spring AOP 日志拦截器的事务管理

如果要在方法执行前或后或抛出异常后加上一个自己的拦截器,或者一个环绕拦截器,在拦截器中执行一些操作,比如执行一些数据库操作,记录一些信 息,这些操作通过调用一个服务类的方法来执行...

哲别0
2018/05/18
520
0
Spring 深入理解事务原理

IT虾米的博客 一、事务的基本原理 Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步...

hgqxjj
2018/06/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Docker 安装 Redis

1、查看可用的 Redis 版本 docker search redis 2、取最新版的 Redis 镜像 这里我们拉取官方的最新版本的镜像: $ docker pull redis:latest 3、查看本地镜像 使用以下命令来查看是否已安装了...

你好夜故事
23分钟前
9
0
关于spring boot自动注入出现Consider defining a bean of type 'xxx' in your configuration问题解决方案

根据英文的提示是在配置中找不到一个指定自动注入类型的bean,经过多方排查得出结论:   正常情况下加上@Component注解的类会自动被Spring扫描到生成Bean注册到spring容器中,既然他说没找...

观海562
36分钟前
7
0
将List> 转成对象

public class MapToBeanUtil<T> { public static <T> List<T> convertMap2Bean(List<Map<String, Object>> map, Class<T> T) { List<T> result = null; if (CollectionUtils.isEmpty......

简小姐
39分钟前
10
0
大数据HDFS的相关运维题

1.在 HDFS 文件系统的根目录下创建递归目录“1daoyun/file”,将附件中的BigDataSkills.txt 文件,上传到 1daoyun/file 目录中,使用相关命令查看文件系统中 1daoyun/file 目录的文件列表信息...

洲小洲
52分钟前
7
0
了解如何使用 Jenkins-X UpdateBot

本文首发于:Jenkins 中文社区 原文链接 作者:Ryan Dawson 译者:wenjunzhangp Jenkins-X UpdateBot 是用于在项目源代码中自动更新依赖项版本的工具。假设您正在构建两个项目 A 和 B,B 使用...

Jenkins中文社区
53分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部