前言

接着上一篇文章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.  

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

  6. publicstaticfinalString TWO= "two";

  7. }

 

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

 

  1. publicclassDynamicDataSourceContextHolder{

  2.  

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

  4.  

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

  6.  

  7. publicstaticvoid setDataSourceKey(String key){

  8. CONTEXT_HOLDER.set(key);

  9. }

  10.  

  11. publicstaticObject getDataSourceKey(){

  12. return CONTEXT_HOLDER.get();

  13. }

  14.  

  15. publicstaticvoid clearDataSourceKey(){

  16. CONTEXT_HOLDER.remove();

  17. }

  18.  

  19. publicstaticBoolean containDataSourceKey(String key){

  20. return dataSourceKeys.contains(key);

  21. }

  22.  

  23. }

 

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

 

  1. publicclassDynamicDataSourceextendsAbstractRoutingDataSource{

  2.  

  3. /**

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

  5. */

  6. @Override

  7. protectedObject determineCurrentLookupKey() {

  8. returnDataSourceContextHolder.getDatasourceType();

  9. }

  10.  

  11. }

 

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

 

  1. @Configuration

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

  3. publicclassMyBatisConfigextendsAbstractDataSourceConfig{

  4.  

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

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

  7.  

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

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

  10.  

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

  12.  

  13.  

  14. @Primary

  15. @Bean(name = "dataSourceOne")

  16. publicDataSource dataSourceOne(Environment env) {

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

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

  19. }

  20.  

  21. @Bean(name = "dataSourceTwo")

  22. publicDataSource dataSourceTwo(Environment env) {

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

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

  25. }

  26.  

  27.  

  28.  

  29. @Bean("dynamicDataSource")

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

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

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

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

  34.  

  35. DynamicDataSource dataSource = newDynamicDataSource();

  36. dataSource.setTargetDataSources(targetDataSources);

  37. dataSource.setDefaultTargetDataSource(dataSourceOne);

  38. return dataSource;

  39. }

  40.  

  41. @Bean(name = "sqlSessionFactoryOne")

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

  43. throwsException{

  44. return createSqlSessionFactory(dataSource);

  45. }

  46.  

  47. @Bean(name = "sqlSessionFactoryTwo")

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

  49. throwsException{

  50. return createSqlSessionFactory(dataSource);

  51. }

  52.  

  53.  

  54.  

  55.  

  56. @Bean(name = "sqlSessionTemplate")

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

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

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

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

  61.  

  62. CustomSqlSessionTemplate customSqlSessionTemplate = newCustomSqlSessionTemplate(factoryOne);

  63. customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);

  64. return customSqlSessionTemplate;

  65. }

  66.  

  67. /**

  68. * 创建数据源

  69. * @param dataSource

  70. * @return

  71. */

  72. privateSqlSessionFactory createSqlSessionFactory(DataSource dataSource) throwsException{

  73. SqlSessionFactoryBean bean = newSqlSessionFactoryBean();

  74. bean.setDataSource(dataSource);

  75. bean.setVfs(SpringBootVFS.class);

  76. bean.setTypeAliasesPackage(ALIASES_PACKAGE);

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

  78. return bean.getObject();

  79. }

  80. }

 

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.  

  8. import java.lang.reflect.Method;

  9.  

  10. publicclassDataSourceAspect{

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

  12.  

  13. /**

  14. * @param clazz

  15. * @param name

  16. * @return

  17. */

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

  19. Class<?> searchType = clazz;

  20. while(searchType != null) {

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

  22. for(Method method : methods) {

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

  24. return method;

  25. }

  26. }

  27. searchType = searchType.getSuperclass();

  28. }

  29. returnnull;

  30. }

  31.  

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

  33. protectedvoid datasourceAspect() {

  34.  

  35. }

  36.  

  37. /**

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

  39. */

  40. @Before("datasourceAspect()")

  41. publicvoid changeDataSourceBeforeMethodExecution(JoinPoint jp) {

  42. String key = determineDatasource(jp);

  43. if(key == null) {

  44. DataSourceContextHolder.setDatasourceType(null);

  45. return;

  46. }

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

  48. DataSourceContextHolder.setDatasourceType(key);

  49.  

  50. }

  51.  

  52. /**

  53. * @param jp

  54. * @return

  55. */

  56. publicString determineDatasource(JoinPoint jp) {

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

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

  59. String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);

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

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

  62. return resultDS;

  63. }

  64.  

  65. /**

  66. *

  67. */

  68. @After("datasourceAspect()")

  69. publicvoid restoreDataSourceAfterMethodExecution() {

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

  71. preDatasourceHolder.remove();

  72. }

  73.  

  74. /**

  75. * @param targetClass

  76. * @param methodName

  77. * @return

  78. */

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

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

  81. if(m != null) {

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

  83. return resolveDataSourceName(choDs);

  84. }

  85. returnnull;

  86. }

  87.  

  88. /**

  89. * @param classDS

  90. * @param methodDS

  91. * @return

  92. */

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

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

  95. }

  96.  

  97. /**

  98. * @param targetClass

  99. * @return

  100. */

  101. privateString resolveDataSourceFromClass(Class targetClass) {

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

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

  104. }

  105.  

  106. /**

  107. * @param ds

  108. * @return

  109. */

  110. privateString resolveDataSourceName(TargetDataSource ds) {

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

  112. }

  113. }

Spring多数据源事务_数据库