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

目的

  • 能使用spring事务解决开发需求
  • 了解spring事务是如何被spring管理的
  • 了解spring事务底层原理实现,比如代理、事务传播机制等

Spring事务简单使用

配置数据源及事务管理器:

java 复制代码
@Component
@ComponentScan("com.firechou.aop")
@EnableTransactionManagement
public class JdbcAppConfig {

	@Bean
	public JdbcTemplate jdbcTemplate(){
		return new JdbcTemplate(dataSource());
	}

	@Bean
	public PlatformTransactionManager transactionManager(){
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource());
		// 部分失败是否全局回滚
//		transactionManager.setGlobalRollbackOnParticipationFailure(false);
		return transactionManager;
	}

	@Bean
	public DataSource dataSource(){
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://rm-cn-zsk3ar7jm0010m0o.rwlb.rds.aliyuncs.com:3306/db_test");
		dataSource.setUsername("firechou");
		dataSource.setPassword("xxx");
		return dataSource;
	}
}

业务逻辑代码:

java 复制代码
@Service
public class JdbcService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private JdbcService jdbcService;

	@Transactional
	public void test(){
		jdbcTemplate.execute("INSERT INTO `db_test`.`film`(`id`, `name`) VALUES (4, 'film2')");
		// 注意此处不要直接写this.a(),否则a()方法事务不生效
        jdbcService.a();
		jdbcTemplate.execute("INSERT INTO `db_test`.`film`(`id`, `name`) VALUES (5, 'film2')");
	}

	@Transactional
	public void a(){
		jdbcTemplate.execute("INSERT INTO `db_test`.`film`(`id`, `name`) VALUES (6, 'film2')");
		throw new NullPointerException();
	}
}

调用:

java 复制代码
public class FireTest {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JdbcAppConfig.class);
		JdbcService jdbcService = context.getBean(JdbcService.class);
		jdbcService.test();
	}
}

结果:

bash 复制代码
因为a()方法中抛出了一次,所以数据库不会新增数据

如上,就是Spring中事务的简单应用,接下来分析Spring事务的实现原理。

@EnableTransactionManagement工作原理

开启Spring事务本质上就是增加了一个Advisor,当我们使用@EnableTransactionManagement注解来开启Spring事务时,该注解的功能就是向Spring容器中添加了两个Bean:

  1. AutoProxyRegistrar
  2. ProxyTransactionManagementConfiguration

关键代码如下:

java 复制代码
// org.springframework.transaction.annotation.TransactionManagementConfigurationSelector#selectImports
@Override
protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        case PROXY:
            // 默认是PROXY
            return new String[] {AutoProxyRegistrar.class.getName(),
                    ProxyTransactionManagementConfiguration.class.getName()};
        case ASPECTJ:
            // 表示不用动态代理技术,用ASPECTJ技术,比较麻烦了
            return new String[] {determineTransactionAspectClass()};
        default:
            return null;
    }
}

AutoProxyRegistrar:

主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator 的Bean。

而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator ,所以这个类的主要作用就是开启自动代理 ,也就是一个BeanPostProcessor,Spring会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。

ProxyTransactionManagementConfiguration:

是一个配置类,它又定义了另外三个bean:

  1. BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor
  2. AnnotationTransactionAttributeSource:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut。用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解。
  3. TransactionInterceptor:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice。代理逻辑实现,当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。

Spring事务基本执行原理

一个Bean在执行Bean的创建生命周期时,会经过InfrastructureAdvisorAutoProxyCreator的初始化后的方法,会判断当前当前Bean对象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象。

该代理对象在执行某个方法时,会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke()方法,执行基本流程为:

  1. 利用所配置的PlatformTransactionManager事务管理器新建一个数据库连接
  2. 修改数据库连接的autocommit为false
  3. 执行MethodInvocation.proceed()方法,简单理解就是执行业务方法,其中就会执行sql
  4. 如果没有抛异常,则提交
  5. 如果抛了异常,则回滚

Spring事务传播机制

在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()调用b():

  1. a()和b()方法中的所有sql需要在同一个事务中吗?
  2. a()和b()方法需要单独的事务吗?
  3. a()需要在事务中执行,b()还需要在事务中执行吗?
  4. 等等情况...

所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务传播机制是如何实现的呢?

先来看上述几种场景中的一种情况,a()在一个事务中执行,调用b()方法时需要新开一个事务执行:

  1. 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a
  2. 将数据库连接a的autocommit改为false
  3. 把数据库连接a设置到ThreadLocal中
  4. 执行a()方法中的sql
  5. 执行a()方法过程中,调用了b()方法(注意用代理对象调用b()方法)
    1. 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线程其实已经拥有一个Spring事务了,则进行挂起
    2. 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象
    3. 挂起完成后,再次利用事务管理器新建一个数据库连接b
    4. 将数据库连接b的autocommit改为false
    5. 把数据库连接b设置到ThreadLocal中
    6. 执行b()方法中的sql
    7. b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交
    8. 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到ThreadLocal中
  6. a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交

这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。

属性propagation,事务传播机制:

  • Propagation.REQUIRED

支持当前事务,如果不存在,则创建一个新事务。默认隔离级别。

  • Propagation.SUPPORTS

支持当前事务,如果不存在,则以非事务方式执行。

  • Propagation.MANDATORY

支持当前事务,如果不存在则抛出异常。

  • Propagation.REQUIRES_NEW

创建一个新事务,并挂起当前事务(如果存在)。

  • Propagation.NOT_SUPPORTED

以非事务方式执行,如果存在,则挂起当前事务。

  • Propagation.NEVER

以非事务方式执行,如果存在事务,则抛出异常。

  • Propagation.NESTED

如果存在当前事务,则在嵌套事务内执行,类似于REQUIRED;

其中,以非事务方式运行,表示以非Spring事务运行,表示在执行这个方法时,Spring事务管理器不会去建立数据库连接,执行sql时,由Mybatis或JdbcTemplate自己来建立数据库连接来执行sql。

Spring事务强制回滚

正常情况下,a()调用b()方法时,如果b()方法抛了异常,但是在a()方法捕获了,那么a()的事务还是会正常提交的,但是有的时候,我们捕获异常可能仅仅只是不把异常信息返回给客户端,而是为了返回一些更友好的错误信息,而这个时候,我们还是希望事务能回滚的,那这个时候就得告诉Spring把当前事务回滚掉,做法就是:

java 复制代码
@Transactional
public void test(){

    // 执行sql
	try {
		b();
	} catch (Exception e) {
		// 构造友好的错误信息返回
        // 如果此处不处理,就算这里b()的异常被捕获了,因为b()与test()方法的conn是同一个,所以还是会回滚
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
    
}

public void b() throws Exception {
	throw new Exception();
}

TransactionSynchronization

Spring事务有可能会提交,回滚、挂起、恢复,所以Spring事务提供了一种机制,可以让程序员来监听当前Spring事务所处于的状态。

java 复制代码
@Component
public class UserService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private UserService userService;

	@Transactional
	public void test(){
		TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

			@Override
			public void suspend() {
				System.out.println("test被挂起了");
			}

			@Override
			public void resume() {
				System.out.println("test被恢复了");
			}

			@Override
			public void beforeCommit(boolean readOnly) {
				System.out.println("test准备要提交了");
			}

			@Override
			public void beforeCompletion() {
				System.out.println("test准备要提交或回滚了");
			}

			@Override
			public void afterCommit() {
				System.out.println("test提交成功了");
			}

			@Override
			public void afterCompletion(int status) {
				System.out.println("test提交或回滚成功了");
			}
		});

		jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
		System.out.println("test");
		userService.a();
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void a(){
		TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

			@Override
			public void suspend() {
				System.out.println("a被挂起了");
			}

			@Override
			public void resume() {
				System.out.println("a被恢复了");
			}

			@Override
			public void beforeCommit(boolean readOnly) {
				System.out.println("a准备要提交了");
			}

			@Override
			public void beforeCompletion() {
				System.out.println("a准备要提交或回滚了");
			}

			@Override
			public void afterCommit() {
				System.out.println("a提交成功了");
			}

			@Override
			public void afterCompletion(int status) {
				System.out.println("a提交或回滚成功了");
			}
		});

		jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
		System.out.println("a");
	}


}

总结

  • spring的事务是通过动态代理来实现的
  • 相同类中的事务方法调用,要注意事务是否生效,只有都是被代理类调用的方法才会事务生效
  • 事务传播机制底层是通过ThreadLocal来实现的,新开事务后之前的事务会被挂起
相关推荐
不能再留遗憾了3 小时前
【SpringCloud】Sentinel
spring·spring cloud·sentinel
whltaoin4 小时前
AI 超级智能体全栈项目阶段五:RAG 四大流程详解、最佳实践与调优(基于 Spring AI 实现)
java·人工智能·spring·rag·springai
心勤则明4 小时前
Spring AI 文档ETL实战:集成text-embedding-v4 与 Milvus
人工智能·spring·etl
艾菜籽5 小时前
Spring Web MVC入门补充1
java·后端·spring·mvc
艾菜籽7 小时前
Spring MVC入门补充2
java·spring·mvc
为java加瓦11 小时前
Spring 方法注入机制深度解析:Lookup与Replace Method原理与应用
java·数据库·spring
无名客011 小时前
SpringCloud中的网关(Gateway)的作用是什么?
spring·spring cloud·gateway
hrrrrb13 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶13 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
hello 早上好20 小时前
深入 Spring 依赖注入底层原理
数据库·sql·spring