逐行阅读Spring5.X源码(十三)spring事务源码分析

既然讲spring事务源码分析,想必读者都知道什么是事务吧!包括事务四大特性ACID,4大隔离级别。笔者就不详细讲这些简单的知识了,简单列一下事务的概念。

事务的概念

  1. 原子性(atomicity)
    一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
  2. 一致性(consistency)
    事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态
  3. 隔离性(isolation)
    事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
    在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化
  • 读未提交(Read Uncommited),该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取

  • 授权读取也称为已提交读(Read Commited),授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。

  • 可重复读(Repeatable Read)
    就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;

  • 串行化
    是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。

  1. 持久性(durability)
    一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态。

进入正题

1. spring事务三大接口

  1. PlatformTransactionManager:平台事务管理器
  2. TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  3. TransactionStatus:事务运行状态

PlatformTransactionManager

spring并不直接管理事务,而是提供了多种事务管理器,他们将事务的管理职责委托给Hibernate或JTA等持久化机制所提供的相关平台框架的事务来实现。通过这个接口,spring为各个平台如JDBC、Hibernate等提供了对应的事务管理器,但是事务的实现就是各平台自己的事情了。

public interface PlatformTransactionManager {
 
    //根据事务定义TransactionDefinition,获取事务
    TransactionStatus getTransaction(TransactionDefinition definition);
 
    //提交事务
    void commit(TransactionStatus status);
 
    //回滚事务
    void rollback(TransactionStatus status);
}

TransactionDefinition

该接口定义了5个方法以及一些表示事务属性的常量。

public interface TransactionDefinition {
    /**
     * 传播行为值
     */
    //当前如果有事务,Spring就会使用该事务; 否则会开始一个新事务
    int PROPAGATION_REQUIRED = 0;
    //当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务
    int PROPAGATION_SUPPORTS = 1;
    //当前如果有事务,Spring就会使用该事务;否则会抛出异常
    int PROPAGATION_MANDATORY = 2;
    //Spring总是开始一个新事务。如果当前有事务,则该事务挂起
    int PROPAGATION_REQUIRES_NEW = 3;
    //Spring不会执行事务中的代码。代码总是在非事务环境下执行,如果当前有事务,则该事务挂起
    int PROPAGATION_NOT_SUPPORTED = 4;
    //即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
    int PROPAGATION_NEVER = 5;
    //如果当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与
    //Transaction- Definition.PROPAGATION_REQUIRED一样
    int PROPAGATION_NESTED = 6;

    /**
     * 隔离级别
     */
    //默认隔离级别(对大多数数据库来说就是ISOLATION_ READ_COMMITTED)
    int ISOLATION_DEFAULT = -1;
    //最低的隔离级别。事实上我们不应该称其为隔离级别,因为在事务完成前,
    //其他事务可以看到该事务所修改的数据。
    //而在其他事务提交前,该事务也可以看到其他事务所做的修改
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    //大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。
    //遗憾的是,在该事务提交后,你就可以查看其他事务插入或更新的数据。
    // 这意味着在事务的不同点上,如果其他事务修改了数据,你就会看到不同的数据
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    //该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的
    // 数据集,即使其他事务修改了所查询的数据。
    //然而如果其他事务插入了新数据,你就可以查询到该新插入的数据
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    //代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    //默认事务的超时时间
    int TIMEOUT_DEFAULT = -1;

    //获取事务的传播行为
    int getPropagationBehavior();
    //获取事务的隔离级别
    int getIsolationLevel();
    //获取超时时间
    int getTimeout();
    //是否只读
    boolean isReadOnly();
    //事务名称
    @Nullable
    String getName();
}

TransactionStatus

记录事务的状态,该接口定义了一组方法,用来获取或判断事务相应状态信息。PlatformTransactionManager.getTransaction(TransactionDefinition definition);方法返回一个TransactionStatus对象,该对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

public interface TransactionStatus extends SavepointManager, Flushable {
    //获取是否是新事务
    boolean isNewTransaction();
    //获取是否存在保存点
    boolean hasSavepoint();
    //设置事务回滚
    void setRollbackOnly();
    //获取是否回滚
    boolean isRollbackOnly();
    //刷新事务
    @Override
    void flush();
    //获取事务是否完成
    boolean isCompleted();
}

以上为前置知识,警告,前方高能!

2. 测试环境

老规矩,先祭出我们的测试代码!为源码讲解做准备。

配置类,注意,我们添加了@EnableTransactionManagement注解

@ComponentScan("com.config.transaction")
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class Config {

}

数据源

@Configuration
@MapperScan("com.config.transaction")
public class DaoConfig {
    private String driverClass = "com.mysql.jdbc.Driver";
    private String username="root";
    private String password="123456";
    private String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong";

    //注册数据源
    @Bean
    public DataSource getDataSorce() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 获取sessionFactory
     */
    @Bean
    public SqlSessionFactory getSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(getDataSorce());
        return sqlSessionFactoryBean.getObject();
    }
    //注册jdbc事务管理器
    @Bean
    public TransactionManager getTrans() throws Exception {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(getDataSorce());
        return transactionManager;
    }

}

mybatis的Mapper

@Mapper
public interface DaoMapper {

    @Insert("insert into user values(1,'name')")
    public void add();
}

业务逻辑,注意,我们添加了@Transactional事务注解

@Service
@Transactional(propagation= Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class ServiceImpl {


    @Autowired
    DaoMapper daoMapper;

    public void add(){
        daoMapper.add();
        System.out.println("add");
    }

    public void del(){
        System.out.println("del");
    }
}

测试类

public class SpringTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class);
        context.refresh();
        ServiceImpl serviceImpl =  context.getBean(ServiceImpl.class);
        serviceImpl.add();
    }
}

OK,运行上面的测试类,就能往数据库添加一条记录。

本文,就来讲讲事务的源码。注意,源码的讲解,需要你知道循环依赖源码和AOP源码,前面的博文我们详细讲解了,本文会直接定位到AOP源码处,不再讲解前置知识,详细参见《spring循环依赖源码讲解》《AOP源码讲解》

追根溯源,还是AOP

以debug模式运行上面的测试类:


我们在ServiceImpl业务类上加了@Transactional注解,调试代码发现,ServiceImpl类生成的实例被CGLIB动态代理了。
我们去掉@Transactional注解再看看:

此时的实例,没有被CGLIB动态代理。好,这就说明,事务是通过AOP的方式实现的。

所以,研究spring事务源码,就是研究事务是怎么通过AOP实现的,没问题吧?

3. 源码分析

先写到这,读者先去自行分析。没阅读过循环依赖和AOP源码的,赶紧回去看,不然你听不懂。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342