【Spring专题】Spring之事务底层源码解析

Spring事务原理与源码解析
本文围绕Spring事务展开,先介绍@Transactional注解及Spring事务传播类型,通过多个示例分析其使用场景。接着深入课程内容,回顾Spring事务相关概念,按源码调用次序讲解核心方法,包括开启事务、回滚、提交等,最后总结学习了Spring事务底层原理。

特别声明

事务是具有原子性的,同一个事务之下的所有操作是一个整体,要么一起回滚,要么一起提交!

阅读导航

系列上一篇文章:《【Spring专题】Spring之Bean生命周期源码解析——阶段四(Bean销毁)(拓展,了解就好)

前置知识

@Transactional简单介绍

@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解。它可以作用在类上面,所有该类的public方法都配置相同的事务属性信息;也可以作用在方法上面,表示方法被纳入了事务管理钟。被事务管理的方法,能保证方法内多个数据库操作要么同时成功、要么同时失败。但是使用@Transactional注解时又需要注意一些细节,不然一个不小心就事务失效了。
下面@Transactional注解的源码,我们来看看它都有什么属性:

@Target({
   
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
   
   

    /**
     * 同下面的transactionManager,用来确定目标的事务管理器的名称
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * 同上面的value,用来确定目标的事务管理器的名称
     */
    String transactionManager() default "";
    
    /**
     * 事务传播类型/策略。默认值为 Propagation.REQUIRED
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * 事务的隔离级别,默认值为 Isolation.DEFAULT
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    /**
     * 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
     */
    boolean readOnly() default false;

    /**
     * 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型必须引起事务回滚。
     * 默认情况下,事务将在RuntimeException和Error上回滚,但不会在检查异常(业务异常)上回滚.
     * 这是构造回滚规则的首选方法(与rollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。
     */
    Class<? extends Throwable>[] rollbackFor() default {
   
   };

    /**
     * 定义零(0)个或多个异常类,这些异常类必须是Throwable的子类,指示哪些异常类型不能导致事务回滚。
     * 这是构造回滚规则的首选方法(与noRollbackForClassName相反),匹配异常类型、它的子类和它的嵌套类。
     */
    Class<? extends Throwable>[] noRollbackFor() default {
   
   };
}

上面贴的基本上是我们在业务代码中能使用到的关于@Transactional注解的了。通过属性我们可以看出,@Transactional注解具备以下作用:

  1. 指定目标事务管理器
  2. 指定事务的传播类型
  3. 指定事务的隔离级别
  4. 指定事务的超时时间
  5. 指定事务是否为只读事务
  6. 指示哪些异常类型能导致事务回滚
  7. 指示哪些异常类型不能导致事务回滚

看完之后是不是觉得内心毫无波澜,甚至有点不知所以然?诶,我为什么要把这些属性罗列出来呢?并且还说了一堆废话。
在这里插入图片描述
O,我的兄弟们呐,既然@Transactional注解上给出了这些属性并且支持我们修改他们的值来改变注解的行为策略,那它在源码里面肯定要支持的呀。我知道不少人看完我这句话会有这种想法:这…,听君一席话入听一席话呀!这个我也知道啊,还要你说吗。那我只能说:如果你已经悟了这一点,说明你不是我的目标提醒群体
我相信,肯定也有不少朋友一脸懵逼的进来,最后也没抓住这个源码解析的重点。知道了上述属性的作用,对我们阅读源码其实是有大大的帮助的。还是那句话:通过业务阅读源码,远比通过源码洞悉业务简单得多!

好啦,言归正传。在上面的属性中,其中尤为重要的,当是propagation属性,事务的传播类型/策略。默认为Propagation.REQUIRED,另外还有一些其他属性,分别表现为不一样的策略。下面我们再进一步简单介绍下

*Spring事务传播类型(非常重要)

  • *Propagation.REQUIRED:直译:必须的。默认的策略表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
  • Propagation.SUPPORTS:直译:支持的。表示若当前存在事务,则加入该事务;如果当前不存在事务,则以【非事务】的方式继续运行
  • Propagation.MANDATORY:直译:强制的(强制需要事务)。表示若当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
  • *Propagation.REQUIRES_NEW直译:新建的重新创建一个新的事务,如果当前存在事务,暂停当前的事务
  • Propagation.NOT_SUPPORTED:直译:被支持的。以非事务的方式运行,如果当前存在事务,暂停当前的事务
  • Propagation.NEVER:直译:从来不。以非事务的方式运行,如果当前存在事务,则抛出异常
  • Propagation.NESTED:直译:嵌套的。它整体上和 Propagation.REQUIRED 效果一样,但是当出现异常的时候不一样。NESTED会在执行方法前设置一个safePoint安全点,当出现回滚的时候,会回滚到当前安全点,而不是整体回滚

重要的事情说三遍
重要的事情说三遍
重要的事情说三遍
(PS:我希望大家看了上面的概念之后,不要过过脑门就算了。起码你得知道,哪些类型,会在同一个事务下进行,哪些会新建一个事务运行。并且标红的两个一定要背下来,重中之重)

@Transactional使用示例分析

示例一:常用

源码如下:

@Component
public class TransUserService {
   
   

    @Resource
    private JdbcTemplate template;
    @Resource
    private TransUserService userService;

    @Transactional
    public void test() {
   
   
        String sql = "INSERT INTO shen_name values('zhang', 'san');";
        template.execute(sql);

        userService.a();
    }

    @Transactional
    public void a() {
   
   
        String sql = "INSERT INTO shen_name values('li', 'si');";
        template.execute(sql);
    }
}

事务分析:默认情况下传播机制为REQUIRED,表示若当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn,默认的事务传播策略,开启事务
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql,插入一条记录
  4. 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
  5. 执行conn的commit()方法进行提交

结果如下:
在这里插入图片描述

PS:大家有没有注意到上面的一个细节?TransUserService 类下面,有一个属性TransUserService userService ,自己依赖自己。然后再test方法里面,这样来调用userService.a();方法。为嘛啊?因为啊,当方法加上@Transactional注解之后,这个类就被代理了。如果此时,我们在test()中这样调用:

@Transactional
public void test() {
   
   
	 // test方法中的sql:insert......
	 this.a();
}

那仅仅是普通的Java方法a()而已,他是不会去处理你a()上面的@Transactional的。不知道大家理解不?但是通过自己依赖自己,Spring就会帮我们找到自己的代理对象,等于我还是用的代理对象去调用方法a()了。

示例二:发生异常回滚

假如是这种情况:

@Component
public class TransUserService {
   
   

    @Resource
    private JdbcTemplate template;
    @Resource
    private TransUserService userService;

    @Transactional
    public void test() {
   
   
        String sql = "INSERT INTO shen_name values('zhang', 'san');";
        template.execute(sql);

        userService.a();
        int result = 100/0;
    }

    @Transactional
    public void a() {
   
   
        String sql = "INSERT INTO shen_name values('li', 'si');";
        template.execute(sql);
    }
}

又或者是这种情况:

@Component
public class TransUserService {
   
   

    @Resource
    private JdbcTemplate template;
    @Resource
    private TransUserService userService;

    @Transactional
    public void test() {
   
   
        String sql = "INSERT INTO shen_name values('zhang', 'san');";
        template.execute(sql);

        userService.a();
    }

    @Transactional
    public void a() {
   
   
        String sql = "INSERT INTO shen_name values('li', 'si');";
        template.execute(sql);
        int result = 100/0;
    }
}

事务分析:所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn,默认的事务传播策略,开启事务
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql,插入一条记录
  4. 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
  5. 发现异常
  6. 执行conn的rollback()方法进行回滚,由于事务的原子性,所以两个方法中的sql都会回滚掉

结果如下:
在这里插入图片描述

示例三:try-catch【经典】

那再假如是这种情况呢,try-catch住:

@Component
public class TransUserService {
   
   

    @Resource
    private JdbcTemplate template;
    @Resource
    private TransUserService userService;

    @Transactional
    public void test() {
   
   
        String sql = "INSERT INTO shen_name values('zhang', 'san');";
        template.execute(sql);

        try {
   
   
            userService.a();
        } catch (Exception e) {
   
   
            System.out.println("发生错误,捕获异常");
        }
    }

    @Transactional
    public void a() {
   
   
        String sql = "INSERT INTO shen_name values('li', 'si');";
        template.execute(sql);
        int result = 100/0;
    }
}

会不会有同学觉得是test里面的执行成功,a()的插入失败?如果你是这么想的,那你肯定没完全理解透我在【特别声明】里说到的内容。事务是具有原子性的,同一个事务之下的所有操作是一个整体,要么一起回滚,要么一起提交!
事务分析:所以上面这种情况的执行流程如下:

  1. 新建一个数据库连接conn,默认的事务传播策略,开启事务
  2. 设置conn的autocommit为false
  3. 执行test方法中的sql,插入一条记录
  4. 进入a方法,默认的事务传播策略,加入当前事务,执行a方法中的sql,插入一条记录
  5. a方法抛出异常,需要回滚
  6. 退回到test方法,捕捉了异常,然而并没有卵用
  7. 执行conn的rollback()方法进行回滚,由于事务的原子性,所以两个方法中的sql都会回滚掉

结果如下:
在这里插入图片描述

示例四:REQUIRES_NEW,没有捕获异常

如果是这种情况:

@Component
public class UserService {
   
   
	@Autowired
	private UserService userService;
	
	@Transactional
	public void 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

验证码有毒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值