Spring源码学习七——事务管理主流程

一、Spring事务

事务依托于数据库,在生产开发中Spring帮我们更好的管理事务。标准的JDBC也可以控制事务,但是需要写大量的模板代码,繁复、冗杂、不方便,Spring事务正好解决了这些问题。Spring事务需要生成一个代理来增强方法,添加了@Transactional注解的方法,会被识别到并进行AOP代理。如果对SpringAOP不熟,可以先看上一篇AOP主流程

1.1 事务的隔离级别

Spring定义了五个事务隔离级别常量,这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

  • DEFAULT :采 用 DB 默 认 的 事 务 隔 离 级 别 。 MySql 的默认为REPEATABLE_READ; Oracle 默认为 READ_COMMITTED
  • READ_UNCOMMITTED:读未提交,未解决任何并发问题。
  • READ_COMMITTED:读已提交,解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读,解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化,不存在并发问题,性能最差。

1.2 事务的传播特性

Spring定义了七个事务传播行为,所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。事务传播行为常量都是以 PROPAGATION_ 开头,形如PROPAGATION_XXX。

  • PROPAGATION_REQUIRED 指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中,Spring 默认的事务传播行为
  • PROPAGATION_REQUIRES_NEW 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
  • PROPAGATION_SUPPORTS 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
  • PROPAGATION_MANDATORY 支持当前事务,如果没有事务就抛出异常
  • PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作
  • PROPAGATION_NEVER 不使用事务,如果存在事务就报错
  • PROPAGATION_NOT_SUPPORTED 以非事务方式执行,如果当前存在事务则将当前事务挂起

二、源码跟踪

2.1 环境准备

AppConfig

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@ComponentScan("com.spring.study")
//@EnableAspectJAutoProxy
@EnableTransactionManagement //开启事务管理,Spring事务的源头就在这里
@Configuration
public class AppConfig {
    @Bean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    DataSource dataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
        hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
        hikariConfig.setUsername("test");
        hikariConfig.setPassword("123456");
        return new HikariDataSource(hikariConfig);
    }
}

UserService

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


@Component
public class UserService implements IUserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void run() {
        System.out.println("run run run quickly!");
    }


    @Transactional
    public String selectOne() {
        return jdbcTemplate.queryForObject("select 1 from dual;", String.class);
    }
}

TestSpring

import com.spring.study.service.IUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        IUserService o = (IUserService) context.getBean("userService");
        System.out.println(o.getClass());
        o.run();
        System.out.println("selectOne :"+o.selectOne());
    }
}

2.2 源码实现

Spring的事务需要AOP代理来增强方法,先看AOP部分,再看事务部分

2.2.1 生成代理对象

2.2.1.1 EnableTransactionManagement

在这里插入图片描述

EnableTransactionManagement是入口,它引入了TransactionManagementConfigurationSelector,mode默认是AdviceMode.PROXY

2.2.1.2 TransactionManagementConfigurationSelector

在这里插入图片描述

TransactionManagementConfigurationSelector实现了ImportSelector接口,那么selectImports返回的String数组就是会被Spring管理的bean的名字,注意这里包含了包名。adviceMode来源于父类,从注解EnableTransactionManagement中获取的,因此AutoProxyRegistrar、ProxyTransactionManagementConfiguration将被加入到容器。

2.2.1.3 AutoProxyRegistrar

在这里插入图片描述
AutoProxyRegistrar的registerBeanDefinitions方法,在容器启动阶段,处理BeanFactoryPostProcesser的子类ConfigurationClassPostProcessor时触发执行。

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);–>registerAutoProxyCreatorIfNecessary(registry, null);–>registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);

在这里插入图片描述
InfrastructureAdvisorAutoProxyCreator被添加到容器中,InfrastructureAdvisorAutoProxyCreator继承了AbstractAutoProxyCreator(Spring AOP在这里进行),因此具备了产生代理对象的能力。上一篇讲到使用@EnableAspectJAutoProxy注解会向容器添加AnnotationAwareAspectJAutoProxyCreator,两者的作用都是为了进行AOP代理

2.2.1.4 ProxyTransactionManagementConfiguration


ProxyTransactionManagementConfiguration产生了一个Advisor(BeanFactoryTransactionAttributeSourceAdvisor),在上一篇SpringAOP里面讲到过,Bean匹配到Advisor之后就会被动态代理

2.2.1.5 Bean与Advisor匹配

在这里插入图片描述
在AbstractAutoProxyCreator.wrapIfNecessary方法中,可以看到UserService匹配到了BeanFactoryTransactionAttributeSourceAdvisor,那么它将被代理。在执行事务方法时会进入JdkDynamicAopProxy的invoke方法,而Advisor也会被添加到JdkDynamicAopProxy里面使用。
注意看this=InfrastructureAdvisorAutoProxyCreator,所以加了@Transactional注解的方法或对象是在InfrastructureAdvisorAutoProxyCreator里面进行的AOP代理

2.2.2 事务执行

2.2.2.1 JdkDynamicAopProxy

得到代理对象之后,执行代理对象的方法,进入JdkDynamicAopProxy的invoke方法
在这里插入图片描述
run是普通方法,得到的拦截器链个数为0
在这里插入图片描述
selectOne方法的拦截器有一个TransactionInterceptor,接着往下

retVal = invocation.proceed();–>((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);

进入到TransactionInterceptor.invoke

2.2.2.2 TransactionInterceptor开启事务

在这里插入图片描述
进入invokeWithinTransaction方法
在这里插入图片描述
这里开启了事务管理,传播机制是PROPAGATION_REQUIRED,,隔离级别是ISOLATION_DEFAULT

2.2.2.3 获取Connection

在这里插入图片描述
还是在invokeWithinTransaction里面执行到createTransactionIfNecessary,看下注释:使用getTransaction进行标准事务划分,然调用后提交或回滚。createTransactionIfNecessary就进行了设置手动提交事务、获取连接,跟进去看看

createTransactionIfNecessary–>status = tm.getTransaction(txAttr);–>startTransaction(def, transaction, debugEnabled, suspendedResources);–>doBegin(transaction, definition);–>Connection newCon = obtainDataSource().getConnection();

在这里插入图片描述
Connection是从数据源持有的连接池里面获取的,然后将其添加到ConnectionHolder,再让txObject持有ConnectionHolder。后面还有一个重要的方法bindResource
在这里插入图片描述
意思是将Connection和当前线程绑定。这里面就不细讲了,简单说下原理:使用了ThreadLocal<Map<Object, Object>> ,Map的key值就是数据源对象,value就是数据库连接
作用:

  • 同一个线程,相同的数据源,对于事务的处理使用同一个连接
  • 同一个线程,不相同的数据源,使用不同的连接
  • 不同的线程,相同的数据源,使用不同的连接
  • 不同的线程,不同的数据源,使用不同的连接
2.2.2.4 设置手动提交事务

继续往下,可以看到将con设置为非自动提交
在这里插入图片描述
往下走,结束该方法返回到createTransactionIfNecessary
在这里插入图片描述
TransactionStatus持有了本次事务,事务下有了本次执行SQL的Connection,再返回
在这里插入图片描述
txInfo里面包含了本次事务的所有信息,后面提交/回滚会用上

2.2.2.5 执行事务方法

invocation.proceedWithInvocation();将会执行目标对象的方法,看下调用链

selectOne–>queryForObject–>query(sql, rowMapper);–>execute(new QueryStatementCallback());

在这里插入图片描述
这里获取连接之后进行数据库的操作,返回结果,我们看下是如何获取数据库连接的

getConnection–>doGetConnection(dataSource);–>TransactionSynchronizationManager.getResource(dataSource)–>Object value = doGetResource(actualKey);

在这里插入图片描述
从resources拿出的Connection就是前面bindResource放进去的,因为他们是在同一个线程进行的

2.2.2.6 提交事务

返回到proceedWithInvocation,得到返回值“1”
在这里插入图片描述
继续往下

commitTransactionAfterReturning(txInfo);–>txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());–>processCommit(defStatus);–>doCommit(status);

在这里插入图片描述
最终事务提交。事务的传播特性,这里没有具体分析,有兴趣的伙伴可以自行研讨。

三、事务失效情况

3.1 目标对象执行事务方法

在这里插入图片描述
test加了事务注解,不允许有事务,有就报错
在这里插入图片描述
正常输出,没报错,这是因为test方法在目标对象调用,没有经过动态代理增强。在这里插入图片描述
改成这样,事务就生效了

3.2 子线程执行事务方法

在这里插入图片描述
子线程执行test(),未报错,事务不生效。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值