MyBatisConfig and CustomSqlSessionTemplate are the main classes used to handle transactions. If you want to see a more professional analysis, you can read the two reference articles.

preface

Following up on the previous article, Spring Transaction Basics, this article focused on how to ensure proper rollback of transactions in the case of Spring’s multiple data sources. Here we also use jTA-Atomikos which is widely used. I just make some summaries for you to use directly in the future.

If you are in a hurry, you can just download this project and have a look:

Github.com/xbmchina/mu…

Note: the version here is very exquisite oh, do not go to mysql8.0. Mysql-connector-java: 5.1.47 mybatis-spring-boot-starter: 1.3.2 druid-spring-boot-starter: 1.1.9

The overall train of thought

There are many articles about JTA-Atomikos on the Internet. This article may be a bit convoluted and not easy to follow, so here are some ideas:

Configure Mybatis and Druid to connect to multiple data sources. 2. Integrate the transactions of multiple data sources into a SqlSession through user-defined data sources, so as to achieve unified management of transactions. Use AOP and custom annotations to dynamically switch data sources (that is, A’s DAO should connect to A’s data source). .

See the source code for more details, or a brief introduction below.

Add the dependent

The main dependency is JTA-Atomikos, the rest of mybatis and Druid dependencies are not pasted.

	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<! --atomikos transaction management-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jta-atomikos</artifactId>
		</dependency>
Copy the code

Configuring multiple data sources

1. First, define an enumeration to show what the current data source instance keys are.

public class DataSourceKey {
    /** database source one*/
    public static final String ONE= "one";

    /** database source two*/
    public static final String TWO= "two";
}
Copy the code

2. Second, use ThreadLocal to store keys that are currently using the data source instance. ThreadLocal instantiates with a default value of master, that is, the default data source is the master data source.

public class DynamicDataSourceContextHolder {
	
	private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.MASTER.getName());
	
	public static List<Object> dataSourceKeys = new ArrayList<Object>();
	
	public static void setDataSourceKey(String key){
		CONTEXT_HOLDER.set(key);
	}
	
	public static Object getDataSourceKey() {return CONTEXT_HOLDER.get();
	}
	
	public static void clearDataSourceKey(){
		CONTEXT_HOLDER.remove();
	}
	
	public static Boolean containDataSourceKey(String key){
		returndataSourceKeys.contains(key); }}Copy the code

3, rewrite AbstractRoutingDataSource determineCurrentLookupKey method, in the access database called when the class of determineCurrentLookupKey () method to obtain the key of the database instance.


public class DynamicDataSource extends AbstractRoutingDataSource {

    /** * gets which data source is currently used. * /
    @Override
    protected Object determineCurrentLookupKey(a) {
        returnDataSourceContextHolder.getDatasourceType(); }}Copy the code

4. Reassemble multiple data sources through SqlSessionFactory, and finally return sqlSessionTemplate to dao layer.


@Configuration
@MapperScan(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig extends AbstractDataSourceConfig {

    // Interface layer in mapper mode
    static final String BASE_PACKAGE = "cn.xbmchina.multidatasourceatomikos.mapper";

    // Connect to the entity layer of the database
    static final String ALIASES_PACKAGE = "ccn.xbmchina.multidatasourceatomikos.domain";

    static final String MAPPER_LOCATION = "classpath:mapper/*.xml";


    @Primary
    @Bean(name = "dataSourceOne")
    public DataSource dataSourceOne(Environment env) {
        String prefix = "spring.datasource.druid.one.";
        return getDataSource(env,prefix,"one");
    }

    @Bean(name = "dataSourceTwo")
    public DataSource dataSourceTwo(Environment env) {
        String prefix = "spring.datasource.druid.two.";
        return getDataSource(env,prefix,"two");
    }



    @Bean("dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("dataSourceOne")DataSource dataSourceOne, @Qualifier("dataSourceTwo")DataSource dataSourceTwo) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("one",dataSourceOne);
        targetDataSources.put("two",dataSourceTwo);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(dataSourceOne);
        return dataSource;
    }

    @Bean(name = "sqlSessionFactoryOne")
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource dataSource)
            throws Exception {
        return createSqlSessionFactory(dataSource);
    }

    @Bean(name = "sqlSessionFactoryTwo")
    public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource dataSource)
            throws Exception {
        return createSqlSessionFactory(dataSource);
    }




    @Bean(name = "sqlSessionTemplate")
    public CustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryOne")SqlSessionFactory factoryOne, @Qualifier("sqlSessionFactoryTwo")SqlSessionFactory factoryTwo) throws Exception {
        Map<Object,SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put("one",factoryOne);
        sqlSessionFactoryMap.put("two",factoryTwo);

        CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(factoryOne);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

    /** * Create data source *@param dataSource
     * @return* /
    private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setVfs(SpringBootVFS.class);
        bean.setTypeAliasesPackage(ALIASES_PACKAGE);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        returnbean.getObject(); }}Copy the code

5, use AOP, with custom annotations annotations in the method of pointcut, dynamic switching data sources


import cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource;
import cn.xbmchina.multidatasourceatomikos.db.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.lang.reflect.Method;

public class DataSourceAspect {
    protected static final ThreadLocal<String> preDatasourceHolder = new ThreadLocal<>();

    /**
     * @param clazz
     * @param name
     * @return*/ private static Method findUniqueMethod(Class<? > clazz, String name) { Class<? > searchType = clazz;while(searchType ! = null) { Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());for (Method method : methods) {
                if (name.equals(method.getName())) {
                    return method;
                }
            }
            searchType = searchType.getSuperclass();
        }
        return null;
    }

    @Pointcut("@annotation(cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource)")
    protected void datasourceAspect() {} /** * set the dataSourceKey to @targetdatasource */ @before ("datasourceAspect()")
    public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {
        String key = determineDatasource(jp);
        if (key == null) {
            DataSourceContextHolder.setDatasourceType(null);
            return;
        }
        preDatasourceHolder.set(DataSourceContextHolder.getDatasourceType());
        DataSourceContextHolder.setDatasourceType(key);

    }

    /**
     * @param jp
     * @return
     */
    public String determineDatasource(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Class targetClass = jp.getSignature().getDeclaringType();
        String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);
        String dataSourceForTargetMethod = resolveDataSourceFromMethod(targetClass, methodName);
        String resultDS = determinateDataSource(dataSourceForTargetClass, dataSourceForTargetMethod);
        return resultDS;
    }

    /**
     *
     */
    @After("datasourceAspect()")
    public void restoreDataSourceAfterMethodExecution() {
        DataSourceContextHolder.setDatasourceType(preDatasourceHolder.get());
        preDatasourceHolder.remove();
    }

    /**
     * @param targetClass
     * @param methodName
     * @return
     */
    private String resolveDataSourceFromMethod(Class targetClass, String methodName) {
        Method m = findUniqueMethod(targetClass, methodName);
        if(m ! = null) { TargetDataSource choDs = m.getAnnotation(TargetDataSource.class);return resolveDataSourceName(choDs);
        }
        return null;
    }

    /**
     * @param classDS
     * @param methodDS
     * @return
     */
    private String determinateDataSource(String classDS, String methodDS) {
        return methodDS == null ? classDS : methodDS;
    }

    /**
     * @param targetClass
     * @return
     */
    private String resolveDataSourceFromClass(Class targetClass) {
        TargetDataSource classAnnotation = (TargetDataSource) targetClass.getAnnotation(TargetDataSource.class);
        returnnull ! = classAnnotation ? resolveDataSourceName(classAnnotation) : null; } /** * @param ds * @return
     */
    private String resolveDataSourceName(TargetDataSource ds) {
        returnds == null ? null : ds.value(); }}Copy the code

6. For dynamic multi-data source architecture scenarios, data sources are switched using AOP, but because transaction control precedes the switch, the switch is prevented by transactions. The solution is to override an SqlSessionTemplate to change the way SqlSessionFactory dynamically retrieves the data source.

targetSqlSessionFactorys.get(DataSourceContextHolder.getDatasourceType());
Copy the code

The DataSourceContextHolder is typically the data source context manipulation class that you created in step 2. You can only change this to suit your needs.

public class CustomSqlSessionTemplate extends SqlSessionTemplate {
    
   / /... omit
    @Override
    public SqlSessionFactory getSqlSessionFactory(a) {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DataSourceContextHolder.getDatasourceType());
        if(targetSqlSessionFactory ! =null) {
            return targetSqlSessionFactory;
        } else if(defaultTargetSqlSessionFactory ! =null) {
            return defaultTargetSqlSessionFactory;
        } else {
            Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
            Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
        }
        return this.sqlSessionFactory;
    }
    / /... omit

}

Copy the code

7. The final effect is as follows:

Refer to the article

Blog.csdn.net/WayneLee080…

www.cnblogs.com/xiaofengfen…

The last

More articles can pay attention to the public number ** [love coding], reply 2020** has actual combat video materials oh.