【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来实现的,新开事务后之前的事务会被挂起
相关推荐
IT_10247 小时前
springboot从零入门之接口测试!
java·开发语言·spring boot·后端·spring·lua
皮皮林5517 小时前
项目终于用上了 Spring 状态机,太优雅了!
spring
迢迢星万里灬8 小时前
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析
java·spring boot·spring·mybatis·spring mvc·面试指南
Hanson Huang10 小时前
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(2)——Prompt(提示词)
java·人工智能·spring·spring ai
.生产的驴11 小时前
SpringBoot 服务器监控 监控系统开销 获取服务器系统的信息用户信息 运行信息 保持稳定
服务器·spring boot·分布式·后端·spring·spring cloud·信息可视化
没有烦恼的猫猫12 小时前
有关Spring事务的传播机制
spring·事务
magic 24512 小时前
事务传播行为详解
spring
考虑考虑14 小时前
@MockitoBean注解使用
spring boot·后端·spring
Dkodak14 小时前
Could not initialize Logback logging from classpath:logback-spring.xml
xml·spring·logback