Классификация
Конфигурация MyBatis с несколькими источниками данных делится на 2 типа:
- Конфигурация с несколькими источниками данных: две библиотеки независимы друг от друга, метод a использует данные библиотеки a, а метод b использует данные библиотеки b;
- Конфигурация источника динамических данных: две библиотечные службы связаны, например библиотека разделения чтения и записи.
Первый напрямую настраивает два отдельных источника данных, и разные модули могут вводить разные sqlSessionFactory; второму нужно настроить динамически переключаемые источники данных.
Готов к работе
Оба метода должны вводить пакеты jar, необходимые для разных баз данных, в файл pom.xml.Здесь в качестве примеров используются MySQL и Oracle:
<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>
Пакет Oracle jar здесь недоступен в центральном репозитории maven из-за проблем с авторскими правами, и его необходимо установить вручную.Учебник по установке
Конфигурация нескольких источников данных
Вы можете настроить два набора источников данных в applicationContext.xml.
<!-- ===============第一个数据源的配置开始=============== -->
<!--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}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${mysql.druid.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${mysql.druid.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${mysql.druid.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${mysql.druid.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${mysql.druid.maxWait}"></property>
</bean>
<!--配置MySQL的SqlSessionFactory-->
<bean id="sqlSessionFactoryMySQL" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="mysqlDtaSource"/>
<property name="configLocation" value="classpath*:mybatis-config.xml" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/rebecca/mybatismutildb/mysql/**/dao/mapper/*Mapper.xml"/>
<property name="typeAliasesPackage" value="com.rebecca.mybatismutildb.mysql.**"/>
</bean>
<!--告诉框架扫描指定包中的mapper接口,然后为其自动的生成代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rebecca.mybatismutildb.mysql.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryMySQL"/>
</bean>
<!--配置事务管理器-->
<bean id="mysqlTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="mysqlDtaSource"/>
</bean>
<!--配置事务的传播特性-->
<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>
<!-- ===============第一个数据源的配置结束=============== -->
<!-- ===============第二个数据源的配置开始=============== -->
<!--Oracle的数据库配置-->
<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}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${oracle.druid.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${oracle.druid.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${oracle.druid.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${oracle.druid.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${oracle.druid.maxWait}"></property>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactoryOracle" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="oracleDataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/rebecca/mybatismutildb/oracle/**/dao/mapper/*Mapper.xml">
</property>
<property name="typeAliasesPackage" value="com.rebecca.mybatismutildb.oracle.**"/>
</bean>
<!--告诉框架扫描指定包中的mapper接口,然后为其自动的生成代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rebecca.mybatismutildb.oracle.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryOracle"/>
</bean>
<!--配置事务管理器-->
<bean id="txManagerOracle" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="oracleDataSource"/>
</bean>
<!--配置事务的传播特性-->
<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>
<!-- ===============第二个数据源的配置结束=============== -->
Здесь указан первый источник данных для разбораcom/rebecca/mybatismutildb/mysql/**/dao/mapper
упакованный*Mapper.xml
файл, то есть указание этих методов dao для подключения к источнику данных MySQL; второй источник данных указан для разбораcom/rebecca/mybatismutildb/oracle/**/dao/mapper
упакованный*Mapper.xml
файл, то есть указание методов этих дао для подключения к источнику данных Oracle.
После настройки applicationContext.xml его можно закодировать непосредственно в указанном пакете.
Конфигурация источника динамических данных
- Настроить динамическое переключение источников данных в applicationContext.xml
<!--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}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${mysql.druid.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${mysql.druid.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${mysql.druid.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${mysql.druid.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${mysql.druid.maxWait}"></property>
</bean>
<!--Oracle的数据库配置-->
<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}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${oracle.druid.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${oracle.druid.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${oracle.druid.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${oracle.druid.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${oracle.druid.maxWait}"></property>
</bean>
<!--告诉框架扫描指定包中的mapper接口,然后为其自动的生成代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rebecca.mybatismutildb.mysql.**.dao,com.rebecca.mybatismutildb.oracle.**.dao"/>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的传播特性-->
<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>
<!--配置SqlSessionFactory(此处自定义实现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" />
<!-- 自动扫描mapping.xml文件 -->
<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>
<!--动态配置数据源-->
<bean id="dataSource" class="com.rebecca.mybatismutildb.db.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--通过不同的key决定用哪个dataSource-->
<entry value-ref="mysqlDtaSource" key="mysqlDtaSource"></entry>
<entry value-ref="oracleDataSource" key="oracleDataSource"></entry>
</map>
</property>
<!--设置默认的dataSource-->
<property name="defaultTargetDataSource" ref="mysqlDtaSource">
</property>
</bean>
<!--动态数据源切换 -->
<bean id="dataSourceAspect" class="com.rebecca.mybatismutildb.db.DataSourceAspect" />
<!--动态数据源切换 spring的aop切面-->
<aop:config>
<!--请设置 @Order(0)。否则可能出现 数据源切换失败问题! 因为要在事务开启之前就进行判断,并进行切换数据源!-->
<aop:aspect ref="dataSourceAspect" order="0">
<!--expression中要配置多个包时,注意用or连接,如:execution(* com.rebecca.mybatismutildb.mysql..dao..*.*(..)) or execution(* com.rebecca.mybatismutildb.oracle..dao..*.*(..)) ; 即拦截com.rebecca.mybatismutildb.mysql和com.rebecca.mybatismutildb.oracle下所有的dao包 -->
<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>
- Связанные классы, используемые в applicationContext.xml
- Аннотация источника данных
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的注解
* @Author: rebecca
* @Date: Created in 2019/4/25 17:27
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
String value();
}
- Аспект DataSourceAspect
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;
/**
* 拦截所有带有注解@DataSource的类和方法
* @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: 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
* @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("数据源切换至--->", 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() {
try {
DataSourceContextHolder.clearDbType();
logger.debug("数据源已移除!");
} catch (Exception e) {
e.printStackTrace();
logger.debug("数据源移除报错!", e);
}
}
}
- Класс DataSourceContextHolder
package com.rebecca.mybatismutildb.db;
/**
* @Author: rebecca
* @Description:
* @Date: Created in 2019/3/28 11:43
* @Modified By:
*/
public class DataSourceContextHolder {
// 注意此处的属性值要与applicationContext.xml中配置的一致
public static final String DATA_SOURCE_MYSQL = "mysqlDtaSource";
public static final String DATA_SOURCE_ORACLE = "oracleDataSource";
//用ThreadLocal来设置当前线程使用哪个dataSource
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDbType(String customerType) {
contextHolder.set(customerType);
}
public static String getDbType() {
return contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
- Класс DynamicDataSource
package com.rebecca.mybatismutildb.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.logging.Logger;
/**
* 动态切换数据源
* @Author: rebecca
* @Description:
* @Date: Created in 2019/3/28 11:42
* @Modified By:
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}
@Override
public Logger getParentLogger() {
return null;
}
}
- Класс PackagesSqlSessionFactoryBean (пользовательская реализация SqlSessionFactoryBean для поддержки конфигурации с подстановочными знаками mapperLocations)
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;
/**
* 支持包名的通配符解析
* @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;
// 将加载多个绝对匹配的所有Resource
// 将首先通过ClassLoader.getResource("META-INF")加载非模式路径部分
// 然后进行遍历模式匹配
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("参数typeAliasesPackage:"+typeAliasesPackage+",未找到任何包");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- В соответствующем интерфейсе dao добавьте
@DataSource(string value)
аннотация, где значение value равноDataSourceContextHolder.DATA_SOURCE_MYSQL
илиDataSourceContextHolder.DATA_SOURCE_ORACLE
.
Потому чтоapplicationContext.xml
Соединение с базой данных по умолчанию настроено в MySQL, поэтому нет необходимости писать в интерфейсе, подключающемся к библиотеке MySQL.@DataSource(string value)
аннотация. Точно так же эту аннотацию можно добавить и к методам интерфейса.
Ссылка на ссылку
mybatis настроить несколько источников данных
mybatis настроить динамический источник данных
Spring aop:pointcut--expression--множественные методы подключения выполнения