• classification
    • The preparatory work
    • Multi-data Source Configuration
    • Dynamic data source configuration
  • Refer to the link

classification

The multi-data source configuration of MyBatis can be divided into two types.

  1. Multi-data source configuration: The services of the two libraries are irrelevant. Method A uses the data of library A, and method B uses the data of library B.
  2. Dynamic data source configuration: Two library services are related, such as read/write split libraries.

The first is to directly configure two separate data sources, and different modules introduce different sqlSessionFactory. The second requires configuring a dynamically switchable data source.

The preparatory work

In both cases, you need to import the JAR packages required by different databases in the pom. XML file. The following uses MySQL and Oracle as examples:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.40</version>
</dependency>
<dependency>
  <groupId>com.oracle</groupId>
  <artifactId>ojdbc</artifactId>
  <version>7</version>
</dependency>
Copy the code

The Oracle JAR package is not available in Maven’s central repository due to copyright issues and needs to be installed manually. Install the tutorial

Multi-data Source Configuration

Just configure two sets of data sources in applicationContext.xml

<! -- =============== start configuration for the first data source =============== -->
<! MySQL > select * from 'MySQL';
<bean id="mysqlDtaSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${mysql.driverClassName}"/>
    <property name="url" value="${mysql.url}"/>
    <property name="username" value="${mysql.username}"/>
    <property name="password" value="${mysql.password}"/>
    <! -- Initialize connection size -->
    <property name="initialSize" value="${mysql.druid.initialSize}"></property>
    <! -- Maximum number of connection pools -->
    <property name="maxActive" value="${mysql.druid.maxActive}"></property>
    <! -- Connection pool maximum free time -->
    <property name="maxIdle" value="${mysql.druid.maxIdle}"></property>
    <! -- Connection pool minimum idle -->
    <property name="minIdle" value="${mysql.druid.minIdle}"></property>
    <! Get the maximum waiting time for connections -->
    <property name="maxWait" value="${mysql.druid.maxWait}"></property>
</bean>
<! MySQL > select * from SqlSessionFactory;
<bean id="sqlSessionFactoryMySQL" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="mysqlDtaSource"/>
    <property name="configLocation" value="classpath*:mybatis-config.xml" />
    <! -- Automatically scan mapping.xml file -->
    <property name="mapperLocations" value="classpath*:com/rebecca/mybatismutildb/mysql/**/dao/mapper/*Mapper.xml"/>
    <property name="typeAliasesPackage" value="com.rebecca.mybatismutildb.mysql.**"/>
</bean>
<! Tell the framework to scan the mapper interface in the specified package and then automatically generate proxy objects for it.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.rebecca.mybatismutildb.mysql.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryMySQL"/>
</bean>
<! -- Configure transaction manager -->
<bean id="mysqlTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="mysqlDtaSource"/>
</bean>
<! Configure transaction propagation features -->
<tx:advice id="txAdvice" transaction-manager="mysqlTxManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="list*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<! -- =============== end of configuration for the first data source =============== -->

<! -- =============== start configuration for the second data source =============== -->
<! Oracle database configuration -->
<bean id="oracleDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${oracle.driverClassName}" />
    <property name="url" value="${oracle.url}" />
    <property name="username" value="${oracle.username}" />
    <property name="password" value="${oracle.password}" />
    <! -- Initialize connection size -->
    <property name="initialSize" value="${oracle.druid.initialSize}"></property>
    <! -- Maximum number of connection pools -->
    <property name="maxActive" value="${oracle.druid.maxActive}"></property>
    <! -- Connection pool maximum free time -->
    <property name="maxIdle" value="${oracle.druid.maxIdle}"></property>
    <! -- Connection pool minimum idle -->
    <property name="minIdle" value="${oracle.druid.minIdle}"></property>
    <! Get the maximum waiting time for connections -->
    <property name="maxWait" value="${oracle.druid.maxWait}"></property>
</bean>
<! - the configuration SqlSessionFactory -- -- >
<bean id="sqlSessionFactoryOracle" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="oracleDataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <! -- Automatically scan mapping.xml file -->
    <property name="mapperLocations" value="classpath*:com/rebecca/mybatismutildb/oracle/**/dao/mapper/*Mapper.xml">
    </property>
    <property name="typeAliasesPackage" value="com.rebecca.mybatismutildb.oracle.**"/>
</bean>
<! Tell the framework to scan the mapper interface in the specified package and then automatically generate proxy objects for it.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.rebecca.mybatismutildb.oracle.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryOracle"/>
</bean>

<! -- Configure transaction manager -->
<bean id="txManagerOracle" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="oracleDataSource"/>
</bean>

<! Configure transaction propagation features -->
<tx:advice id="txAdviceOracle" transaction-manager="txManagerOracle">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="list*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<! -- =============== End of configuration for the second data source =============== -->
Copy the code

Here the first data source to parse com/Rebecca/mybatismutildb/mysql / / dao/mapper package under the * * * mapper. The XML file, or specify these dao way to connect the mysql data source; The second data source to parse com/Rebecca/mybatismutildb/oracle / / dao/mapper package under the * * * mapper. The XML file, or specify these dao way to connect the oracle data sources.

Once configured, applicationContext.xml is encoded directly in the specified package.

Dynamic data source configuration

  1. Configure dynamic switching data sources in applicationContext.xml

<! MySQL > select * from 'MySQL';
<bean id="mysqlDtaSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${mysql.driverClassName}"/>
    <property name="url" value="${mysql.url}"/>
    <property name="username" value="${mysql.username}"/>
    <property name="password" value="${mysql.password}"/>
    <! -- Initialize connection size -->
    <property name="initialSize" value="${mysql.druid.initialSize}"></property>
    <! -- Maximum number of connection pools -->
    <property name="maxActive" value="${mysql.druid.maxActive}"></property>
    <! -- Connection pool maximum free time -->
    <property name="maxIdle" value="${mysql.druid.maxIdle}"></property>
    <! -- Connection pool minimum idle -->
    <property name="minIdle" value="${mysql.druid.minIdle}"></property>
    <! Get the maximum waiting time for connections -->
    <property name="maxWait" value="${mysql.druid.maxWait}"></property>
</bean>
<! Oracle database configuration -->
<bean id="oracleDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${oracle.driverClassName}" />
    <property name="url" value="${oracle.url}" />
    <property name="username" value="${oracle.username}" />
    <property name="password" value="${oracle.password}" />
    <! -- Initialize connection size -->
    <property name="initialSize" value="${oracle.druid.initialSize}"></property>
    <! -- Maximum number of connection pools -->
    <property name="maxActive" value="${oracle.druid.maxActive}"></property>
    <! -- Connection pool maximum free time -->
    <property name="maxIdle" value="${oracle.druid.maxIdle}"></property>
    <! -- Connection pool minimum idle -->
    <property name="minIdle" value="${oracle.druid.minIdle}"></property>
    <! Get the maximum waiting time for connections -->
    <property name="maxWait" value="${oracle.druid.maxWait}"></property>
</bean>
<! Tell the framework to scan the mapper interface in the specified package and then automatically generate proxy objects for it.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.rebecca.mybatismutildb.mysql.**.dao,com.rebecca.mybatismutildb.oracle.**.dao"/>
</bean>
<! -- Configure transaction manager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<! Configure transaction propagation features -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="list*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<! SqlSessionFactoryBean = SqlSessionFactoryBean = mapperLocations
<bean id="sqlSessionFactory" class="com.rebecca.mybatismutildb.db.PackagesSqlSessionFactoryBean" scope="prototype">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml" />
    <! -- Automatically scan mapping.xml file -->
    <property name="mapperLocations">
        <list>
            <value>classpath*:com/rebecca/mybatismutildb/mysql/**/dao/mapper/*Mapper.xml</value>
            <value>classpath*:com/rebecca/mybatismutildb/oracle/**/dao/mapper/*Mapper.xml</value>
        </list>
    </property>
    <property name="typeAliasesPackage" value="com.rebecca.mybatismutildb.**"/>
</bean>
<! -- Dynamically configure the data source -->
<bean id="dataSource" class="com.rebecca.mybatismutildb.db.DynamicDataSource" >
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <! DataSource = dataSource-->
            <entry value-ref="mysqlDtaSource" key="mysqlDtaSource"></entry>
            <entry value-ref="oracleDataSource" key="oracleDataSource"></entry>
        </map>
    </property>
    <! -- Set default dataSource-->
    <property name="defaultTargetDataSource" ref="mysqlDtaSource">
    </property>
</bean>

<! -- Dynamic data source switching -->
<bean id="dataSourceAspect" class="com.rebecca.mybatismutildb.db.DataSourceAspect" />
<! Dynamic data source switch spring AOP aspect -->
<aop:config>
    <! -- Set @order (0). Otherwise, data source switchover failure may occur! This is because you need to decide before the transaction starts and switch the data source! -->
    <aop:aspect ref="dataSourceAspect" order="0">
        <! -- expression to configure multiple packages, pay attention to the use or connection, such as: execution (* com. Rebecca. Mybatismutildb. Mysql.. dao.. *. * (..) ) or execution(* com.rebecca.mybatismutildb.oracle.. dao.. *. * (..) ); Namely intercept com. Rebecca. Mybatismutildb. Mysql and com. Rebecca. Mybatismutildb. Oracle under all the dao package -- -- >
        <aop:pointcut id="dataSourcePointcut" expression="execution(* com.rebecca.mybatismutildb.. dao.. *. * (..) )"/>
        <aop:before pointcut-ref="dataSourcePointcut" method="changeDataSource" />
        <aop:after pointcut-ref="dataSourcePointcut" method="removeDataSource" />
    </aop:aspect>
</aop:config>

Copy the code
  1. Related classes used in applicationContext.xml
  • The DataSource annotations
package com.rebecca.mybatismutildb.db;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** * DataSource annotation *@Author: rebecca
 * @Date: Created in 2019/4/25 17:27
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
    String value(a);
}
Copy the code
  • DataSourceAspect section
package com.rebecca.mybatismutildb.db;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/** * intercepts all annotations@DataSourceClass and method *@Author: rebecca
 * @Date: Created in 2019/4/25 17:28
 */
public class DataSourceAspect {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
    / * * *@Title: changeDataSource
     * @Description: Intercepts the target method to obtain the@DataSourceThe specified data source id that is set to thread storage to switch data sources *@param joinPoint
     * @return: void
     */
    public void changeDataSource(JoinPoint joinPoint){ Class<? > target = joinPoint.getTarget().getClass(); MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();for(Class<? > clazz : target.getInterfaces()){ resolveDataSource(clazz,methodSignature.getMethod()); } resolveDataSource(target,methodSignature.getMethod()); logger.debug("Data source switch to -->", DataSourceContextHolder.getDbType());
    }

    private void resolveDataSource(Class
        clazz, Method method){
        try {
            if(clazz.isAnnotationPresent(DataSource.class)){ DataSource source = clazz.getAnnotation(DataSource.class); DataSourceContextHolder.setDbType(source.value()); } Class<? >[] types = method.getParameterTypes(); Method m = clazz.getDeclaredMethod(method.getName(), types);if(null != m && m.isAnnotationPresent(DataSource.class)){
                DataSource source = m.getAnnotation(DataSource.class);
                DataSourceContextHolder.setDbType(source.value());
            }
        } catch(Exception e) { e.printStackTrace(); }}public void removeDataSource(a) {
        try {
            DataSourceContextHolder.clearDbType();
            logger.debug("Data source removed!");
        } catch (Exception e) {
            e.printStackTrace();
            logger.debug("Data source removed error!", e); }}}Copy the code
  • DataSourceContextHolder class
package com.rebecca.mybatismutildb.db;

/ * * *@Author: rebecca
 * @Description:
 * @Date: Created in 2019/3/28 11:43
 * @Modified By:
 */
public class DataSourceContextHolder {
    // Note that the attribute values are the same as configured in applicationContext.xml
    public static final String DATA_SOURCE_MYSQL = "mysqlDtaSource";
    public static final String DATA_SOURCE_ORACLE = "oracleDataSource";
    // Use ThreadLocal to set which dataSource is used by the current thread
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static void setDbType(String customerType) {
        contextHolder.set(customerType);
    }

    public static String getDbType(a) {
        return contextHolder.get();
    }

    public static void clearDbType(a) { contextHolder.remove(); }}Copy the code
  • DynamicDataSource class
package com.rebecca.mybatismutildb.db;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.logging.Logger;

/** * Dynamically switch data sources *@Author: rebecca
 * @Description:
 * @Date: Created in 2019/3/28 11:42
 * @Modified By:
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey(a) {
        return DataSourceContextHolder.getDbType();
    }

    @Override
    public Logger getParentLogger(a) {
        return null; }}Copy the code
  • PackagesSqlSessionFactoryBean class (custom implementations SqlSessionFactoryBean to support mapperLocations wildcard configuration)
package com.rebecca.mybatismutildb.db;

import org.apache.commons.lang.StringUtils;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/** * supports wildcard parsing of package names *@Author: rebecca
 * @Description:
 * @Date: Created in 2019/3/28 15:30
 * @Modified By:
 */
public class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean {

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private Logger logger = LoggerFactory.getLogger(PackagesSqlSessionFactoryBean.class);

    @Override
    public void setTypeAliasesPackage(String typeAliasesPackage) {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        typeAliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(typeAliasesPackage) + "/" + DEFAULT_RESOURCE_PATTERN;

        // Multiple absolutely matching resources will be loaded
        // The non-modal path part will be loaded first through classLoader.getResource (" meta-INF ")
        // Then perform traversal pattern matching
        try {
            List<String> result = new ArrayList<String>();
            Resource[] resources =  resolver.getResources(typeAliasesPackage);
            if(resources ! =null && resources.length > 0){
                MetadataReader metadataReader = null;
                for(Resource resource : resources){
                    if(resource.isReadable()){
                        metadataReader =  metadataReaderFactory.getMetadataReader(resource);
                        try {
                            result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                        } catch(ClassNotFoundException e) { e.printStackTrace(); }}}}if(result.size() > 0) {
                super.setTypeAliasesPackage(StringUtils.join(result.toArray(), ","));
            }else{
                logger.warn("Parameter typeAliasesPackage."+typeAliasesPackage+"No bag found."); }}catch(IOException e) { e.printStackTrace(); }}}Copy the code
  1. On the corresponding DAO interface, add@DataSource(string value)Annotation, where the value of value isDataSourceContextHolder.DATA_SOURCE_MYSQLorDataSourceContextHolder.DATA_SOURCE_ORACLE.

    Because in theapplicationContext.xmlThe default database connection is MySQL, so the interface connecting to MySQL library can not write@DataSource(string value)Annotation. Similarly, this annotation can be applied to the methods of the interface.

Refer to the link

Mybatis configures multiple data sources

Mybatis configures dynamic data sources

Spring AOP: Pointcut –expression– Multiple Execution connection methods