vlambda博客
学习文章列表

Spring多数据源事务

点击上方 码农沉思录 ,选择“设为星标”
优质文章,及时送达

前言

接着上一篇文章Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用。

如果你非常着急,那么可以直接下载这个项目看看即可:

https://github.com/xbmchina/multidatatsource-druid/tree/master/multidatasource-atomikos

总体思路

网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:

1、配置mybatis以及druid使得其能够实现连接多个数据源。

2、通过自定义数据源,将多个数据源的事务整合成一个SqlSession,进而实现统一管理事务。

3、利用AOP以及自定义注解实现动态的切换数据源(即是A的dao应该连接A的数据源。)。

更多详细了解可以查看源码,或者下面的简单介绍。


添加依赖

主要依赖就是jta-atomikos,其余的mybatis与druid的相关依赖就不粘贴了。


 
   
   
 
  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-aop</artifactId>

  4. </dependency>

  5. <!--atomikos transaction management-->

  6. <dependency>

  7. <groupId>org.springframework.boot</groupId>

  8. <artifactId>spring-boot-starter-jta-atomikos</artifactId>

  9. </dependency>


配置多个数据源

1、首先,定义一个枚举来说明一下当前数据源实例key有哪些。


 
   
   
 
  1. publicclassDataSourceKey{

  2. /** 数据库源one*/

  3. publicstaticfinalString ONE= "one";


  4. /** 数据库源two*/

  5. publicstaticfinalString TWO= "two";

  6. }


2、其次,使用ThreadLocal存储当前使用数据源实例的key。ThreadLocal实例化的时候给一个master的默认值,也就是默认数据源是master数据源。


 
   
   
 
  1. publicclassDynamicDataSourceContextHolder{


  2. privatestaticThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.MASTER.getName());


  3. publicstaticList<Object> dataSourceKeys = newArrayList<Object>();


  4. publicstaticvoid setDataSourceKey(String key){

  5. CONTEXT_HOLDER.set(key);

  6. }


  7. publicstaticObject getDataSourceKey(){

  8. return CONTEXT_HOLDER.get();

  9. }


  10. publicstaticvoid clearDataSourceKey(){

  11. CONTEXT_HOLDER.remove();

  12. }


  13. publicstaticBoolean containDataSourceKey(String key){

  14. return dataSourceKeys.contains(key);

  15. }


  16. }


3、重写AbstractRoutingDataSource的determineCurrentLookupKey方法,在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key。


 
   
   
 
  1. publicclassDynamicDataSourceextendsAbstractRoutingDataSource{


  2. /**

  3. * 取得当前使用那个数据源。

  4. */

  5. @Override

  6. protectedObject determineCurrentLookupKey() {

  7. returnDataSourceContextHolder.getDatasourceType();

  8. }


  9. }


4、通过SqlSessionFactory 重新组装整合多个数据源,最终返回sqlSessionTemplate给到dao层。


 
   
   
 
  1. @Configuration

  2. @MapperScan(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")

  3. publicclassMyBatisConfigextendsAbstractDataSourceConfig{


  4. //mapper模式下的接口层

  5. staticfinalString BASE_PACKAGE = "cn.xbmchina.multidatasourceatomikos.mapper";


  6. //对接数据库的实体层

  7. staticfinalString ALIASES_PACKAGE = "ccn.xbmchina.multidatasourceatomikos.domain";


  8. staticfinalString MAPPER_LOCATION = "classpath:mapper/*.xml";



  9. @Primary

  10. @Bean(name = "dataSourceOne")

  11. publicDataSource dataSourceOne(Environment env) {

  12. String prefix = "spring.datasource.druid.one.";

  13. return getDataSource(env,prefix,"one");

  14. }


  15. @Bean(name = "dataSourceTwo")

  16. publicDataSource dataSourceTwo(Environment env) {

  17. String prefix = "spring.datasource.druid.two.";

  18. return getDataSource(env,prefix,"two");

  19. }




  20. @Bean("dynamicDataSource")

  21. publicDynamicDataSource dynamicDataSource(@Qualifier("dataSourceOne")DataSource dataSourceOne, @Qualifier("dataSourceTwo")DataSource dataSourceTwo) {

  22. Map<Object, Object> targetDataSources = newHashMap<>();

  23. targetDataSources.put("one",dataSourceOne);

  24. targetDataSources.put("two",dataSourceTwo);


  25. DynamicDataSource dataSource = newDynamicDataSource();

  26. dataSource.setTargetDataSources(targetDataSources);

  27. dataSource.setDefaultTargetDataSource(dataSourceOne);

  28. return dataSource;

  29. }


  30. @Bean(name = "sqlSessionFactoryOne")

  31. publicSqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource dataSource)

  32. throwsException{

  33. return createSqlSessionFactory(dataSource);

  34. }


  35. @Bean(name = "sqlSessionFactoryTwo")

  36. publicSqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource dataSource)

  37. throwsException{

  38. return createSqlSessionFactory(dataSource);

  39. }





  40. @Bean(name = "sqlSessionTemplate")

  41. publicCustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryOne")SqlSessionFactory factoryOne, @Qualifier("sqlSessionFactoryTwo")SqlSessionFactory factoryTwo) throwsException{

  42. Map<Object,SqlSessionFactory> sqlSessionFactoryMap = newHashMap<>();

  43. sqlSessionFactoryMap.put("one",factoryOne);

  44. sqlSessionFactoryMap.put("two",factoryTwo);


  45. CustomSqlSessionTemplate customSqlSessionTemplate = newCustomSqlSessionTemplate(factoryOne);

  46. customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);

  47. return customSqlSessionTemplate;

  48. }


  49. /**

  50. * 创建数据源

  51. * @param dataSource

  52. * @return

  53. */

  54. privateSqlSessionFactory createSqlSessionFactory(DataSource dataSource) throwsException{

  55. SqlSessionFactoryBean bean = newSqlSessionFactoryBean();

  56. bean.setDataSource(dataSource);

  57. bean.setVfs(SpringBootVFS.class);

  58. bean.setTypeAliasesPackage(ALIASES_PACKAGE);

  59. bean.setMapperLocations(newPathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));

  60. return bean.getObject();

  61. }

  62. }


5、使用AOP,以自定义注解注解在的方法为切点,动态切换数据源


 
   
   
 
  1. import cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource;

  2. import cn.xbmchina.multidatasourceatomikos.db.DataSourceContextHolder;

  3. import org.aspectj.lang.JoinPoint;

  4. import org.aspectj.lang.annotation.After;

  5. import org.aspectj.lang.annotation.Before;

  6. import org.aspectj.lang.annotation.Pointcut;


  7. import java.lang.reflect.Method;


  8. publicclassDataSourceAspect{

  9. protectedstaticfinalThreadLocal<String> preDatasourceHolder = newThreadLocal<>();


  10. /**

  11. * @param clazz

  12. * @param name

  13. * @return

  14. */

  15. privatestaticMethod findUniqueMethod(Class<?> clazz, String name) {

  16. Class<?> searchType = clazz;

  17. while(searchType != null) {

  18. Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());

  19. for(Method method : methods) {

  20. if(name.equals(method.getName())) {

  21. return method;

  22. }

  23. }

  24. searchType = searchType.getSuperclass();

  25. }

  26. returnnull;

  27. }


  28. @Pointcut("@annotation(cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource)")

  29. protectedvoid datasourceAspect() {


  30. }


  31. /**

  32. * 根据@TargetDataSource的属性值设置不同的dataSourceKey,以供DynamicDataSource

  33. */

  34. @Before("datasourceAspect()")

  35. publicvoid changeDataSourceBeforeMethodExecution(JoinPoint jp) {

  36. String key = determineDatasource(jp);

  37. if(key == null) {

  38. DataSourceContextHolder.setDatasourceType(null);

  39. return;

  40. }

  41. preDatasourceHolder.set(DataSourceContextHolder.getDatasourceType());

  42. DataSourceContextHolder.setDatasourceType(key);


  43. }


  44. /**

  45. * @param jp

  46. * @return

  47. */

  48. publicString determineDatasource(JoinPoint jp) {

  49. String methodName = jp.getSignature().getName();

  50. Class targetClass = jp.getSignature().getDeclaringType();

  51. String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);

  52. String dataSourceForTargetMethod = resolveDataSourceFromMethod(targetClass, methodName);

  53. String resultDS = determinateDataSource(dataSourceForTargetClass, dataSourceForTargetMethod);

  54. return resultDS;

  55. }


  56. /**

  57. *

  58. */

  59. @After("datasourceAspect()")

  60. publicvoid restoreDataSourceAfterMethodExecution() {

  61. DataSourceContextHolder.setDatasourceType(preDatasourceHolder.get());

  62. preDatasourceHolder.remove();

  63. }


  64. /**

  65. * @param targetClass

  66. * @param methodName

  67. * @return

  68. */

  69. privateString resolveDataSourceFromMethod(Class targetClass, String methodName) {

  70. Method m = findUniqueMethod(targetClass, methodName);

  71. if(m != null) {

  72. TargetDataSource choDs = m.getAnnotation(TargetDataSource.class);

  73. return resolveDataSourceName(choDs);

  74. }

  75. returnnull;

  76. }


  77. /**

  78. * @param classDS

  79. * @param methodDS

  80. * @return

  81. */

  82. privateString determinateDataSource(String classDS, String methodDS) {

  83. return methodDS == null? classDS : methodDS;

  84. }


  85. /**

  86. * @param targetClass

  87. * @return

  88. */

  89. privateString resolveDataSourceFromClass(Class targetClass) {

  90. TargetDataSource classAnnotation = (TargetDataSource) targetClass.getAnnotation(TargetDataSource.class);

  91. returnnull!= classAnnotation ? resolveDataSourceName(classAnnotation) : null;

  92. }


  93. /**

  94. * @param ds

  95. * @return

  96. */

  97. privateString resolveDataSourceName(TargetDataSource ds) {

  98. return ds == null? null: ds.value();

  99. }

  100. }



文章不错的话记得点个在看哟,在看越多的话可以给大家带来更多福利