spring多个事务管理器踩坑及解决

 更新时间:2022年11月21日 10:23:58   作者:小小少年_  
这篇文章主要介绍了spring多个事务管理器踩坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

多个事务管理器踩坑

在项目中使用了两个事务管理器,因为项目中设计到两个数据库的操作,所以就声明了两个事务管理器;

但是在使用@Transactional注解的时候,没有手动指定事务要使用哪个,就有可能会导致事务不生效

案例

1、在项目中,我用到了A和B两个数据库,所以,此时就需要声明两个事务管理器,我们假设命名为:ATransactionManager和BTransactionManager(这两个事务管理器,分别对应着A和B两个数据库就)

2、在使用的时候,如果当前方法中,都是对A数据库的表进行操作,那就需要在@Transactional注解的value属性上指定ATransactional

这里的aDataSource和bDataSource都是根据a和b两个数据库生成的,只需要在配置类中,加上这几行代码,spring在启动的时候,就会把这两个事务管理器放入到spring容器中

@Bean
public PlatformTransactionManager aTransactionManager() {
    return new DataSourceTransactionManager(aDataSource());
}

@Bean
public PlatformTransactionManager bTransactionManager() {
    return new DataSourceTransactionManager(bDataSource());
}

配置就完成了,这里,就和添加一个普通的bean对象是一样的,都会放到spring容器中

我遇到的问题是

我在一个方法上加了事务注解,但是没有指定value,实际上,这个接口中是要去操作a数据库的三张表,但是发现接口中事务回滚的时候,并没有把其他三张表的数据都回滚,debug看代码,也确实去执行了回滚的操作,最后发现是没有指定aTransactionManager的原因

源码解析

我们都知道,事务的源码,在

org.springframework.transaction.interceptor.TransactionInterceptor#invoke
    org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
    
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//根据事务属性获取获取事务管理器,这里一般是 DataSourceTransactionManager,我们也可以自己去指定事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);    

在这个方法中,有两行代码,是需要关注的,上面一行,是根据当前的方法名,获取对应的事务注解信息,这里有一个前置的知识点,需要说明:在spring初始化的过程中,会对方法或者类上对的事务注解进行解析,解析之后,会存到内存中,其中

org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation(org.springframework.core.annotation.AnnotationAttributes)

在这个方法中,会解析@Transactional注解,并将解析到的配置信息,存储到RuleBasedTransactionAttribute这个bean中,存储到这个bean的qualifier字段上,所以,事务拦截器在解析的时候,是根据这个qualifier字段的value,来获取事务管理器的,这是一个前提知识,需要知道

determineTransactionManager()

我们着重来看这个方法:

/**
 * 这个方法,是来获取事务管理器的,如果我们有自己指定,就可以在使用@Transactional注解的时候,通过value,指定我们要使用的事务管理器
 * Determine the specific transaction manager to use for the given transaction.
 */
@Nullable
protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
	// Do not attempt to lookup tx manager if no tx attributes are set
	if (txAttr == null || this.beanFactory == null) {
		return getTransactionManager();
	}

	// 1.由于在解析事务注解的时候,将value,放到了qualifier属性上,所以要这样获取,我们只需要知道,这里的qualifier就是事务注解中的value对应的值
	String qualifier = txAttr.getQualifier();
	// 2.如果在@Transaction的value指定事务管理器,会执行这里,其实就是从spring容器中获取指定的事务管理器
	if (StringUtils.hasText(qualifier)) {
		return determineQualifiedTransactionManager(this.beanFactory, qualifier);
	}
	// 3.
	else if (StringUtils.hasText(this.transactionManagerBeanName)) {
		return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
	}
	else {
		// 4.先判断是否有指定事务管理器,如果没有,就获取默认的
		PlatformTransactionManager defaultTransactionManager = getTransactionManager();
		if (defaultTransactionManager == null) {
			// 获取默认的事务管理器
			defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
			if (defaultTransactionManager == null) {
				//由于我们没有指定,会从spring容器中按照类型获取,所以我们只需要往spring容器中注入一个事务管理器即可,springboot应用会默认注入一个事务管理器
				defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
				this.transactionManagerCache.putIfAbsent(
						DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
			}
		}
		return defaultTransactionManager;
	}
}

这个方法,我认为,就是这篇博客所有的内容了,注释中的第一点和第二点我搞懂了,第二点如果点进去,就会发现,实际上,调用的就是beanFactory.getBean(name,type)这个方法,就是根据我们指定的事务管理器,从spring容器中获取我们想要使用的事务管理器

对于第三点,我还没有搞懂,只是看到是在org.springframework.transaction.interceptor.TransactionInterceptor#readObject中赋的值,所以先暂时跳过

对于第四点:其实简单来说,就是我们没有指定要使用的事务管理器,spring会帮助我们去当前spring容器中,根据类型(PlatformTransactionManager.class)获取一个默认的事务管理器,对于springboot应用来说,无需关心这一点,因为springboot帮助我们自动注入了一个事务管理器:DataSourceTransactionManager

所以,上面这个方法,就解释了,为什么,我在没有指定事务管理器的情况下,事务未生效

springboot自动注入的事务管理器

在spring自动注入的配置中,帮我们自动注入了这个一个类

org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\

@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

	@Configuration
	@ConditionalOnSingleCandidate(DataSource.class)
	static class DataSourceTransactionManagerConfiguration {

		private final DataSource dataSource;

		private final TransactionManagerCustomizers transactionManagerCustomizers;

		DataSourceTransactionManagerConfiguration(DataSource dataSource,
				ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
			this.dataSource = dataSource;
			this.transactionManagerCustomizers = transactionManagerCustomizers
					.getIfAvailable();
		}

		@Bean
		@ConditionalOnMissingBean(PlatformTransactionManager.class)
		public DataSourceTransactionManager transactionManager(
				DataSourceProperties properties) {
			// 根据DataSource,初始化一个DataSourceTransactionManager对象,然后return;所以,这里就会把初始化的事务管理器放到spring容器中
			DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
					this.dataSource);
			if (this.transactionManagerCustomizers != null) {
				this.transactionManagerCustomizers.customize(transactionManager);
			}
			return transactionManager;
		}
	}
}

在这个bean中,就注入了一个事务管理器,但是有一个前提,就是@ConditionalOnSingleCandidate(DataSource.class),这个注解,百度了一下,大致的意思就是说如果当前容器中只有一个DataSource,才会执行下面注入的动作

所以,对于springboot应用,如果我们在项目中,只有一个DataSource,那spring会帮我们自动注入一个,在使用事务注解的时候,也就不需要去指定value了

未确认点

这里就是有一个点不太明白,在有多个数据源的情况下,springboot就不帮我们注入事务管理器了,但是我在debug的时候,发现,还是取的默认的事务管理器,也就是DataSourceTransactionalManager, 这个点没太搞懂原因,因为感觉和源码中自己理解的不太一样

结论

总之,在项目中有多个数据源的时候,在事务注解上,最好标明使用哪个事务管理器

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java多线程之线程安全问题详情

    Java多线程之线程安全问题详情

    这篇文章主要介绍了Java多线程之线程安全问题详情,线程安全问题是指因多线程抢占式执行而导致程序出现bug的问题。内容介绍详细内容需要的小伙伴可以参考下面文章内容
    2022-06-06
  • SpringBoot处理接口幂等性的两种方法详解

    SpringBoot处理接口幂等性的两种方法详解

    接口幂等性处理算是一个非常常见的需求了,我们在很多项目中其实都会遇到。本文为大家总结了两个处理接口幂等性的两种常见方案,需要的可以参考一下
    2022-06-06
  • 基于Java实现QQ邮箱发送工具类

    基于Java实现QQ邮箱发送工具类

    我们在日常开发中,需要实现一个对邮箱的发送,今天就实现邮箱的发送工具类,只需要一些注册邮箱之后的配置即可,感兴趣的小伙伴可以了解下
    2023-12-12
  • JAVA中通过自定义注解进行数据验证的方法

    JAVA中通过自定义注解进行数据验证的方法

    java 自定义注解验证可自己添加所需要的注解,下面这篇文章主要给大家介绍了关于JAVA中通过自定义注解进行数据验证的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-08-08
  • mybatis-plus支持null字段全量更新的两种方法

    mybatis-plus支持null字段全量更新的两种方法

    本文主要介绍了mybatis-plus支持null字段全量更新的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • MyBatis Mapper映射器的具体用法

    MyBatis Mapper映射器的具体用法

    映射器是MyBatis中最重要的文件,映射器由Java接口和XML文件共同组成,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • java实现多选批量删除功能

    java实现多选批量删除功能

    工作中批量删除可以提高我们的工作效率,今天这篇文章主要介绍了java实现多选批量删除功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • java中list使用时需避免的场景总结

    java中list使用时需避免的场景总结

    众所周知,Java为开发者提供了多种集合类的实现,其中几乎所有业务代码都需要用到List,但List的错误使用也会导致诸多问题,所以本文我们就来看一看几个错误使用List的场景吧
    2023-10-10
  • Java中的二维数组的赋值与输出方式

    Java中的二维数组的赋值与输出方式

    这篇文章主要介绍了Java中的二维数组的赋值与输出方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • spring boot 全局异常处理方法汇总

    spring boot 全局异常处理方法汇总

    这篇文章主要介绍了spring boot 全局异常处理方法汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10

最新评论