分類
MyBatis的多資料來源配置分2種,
- 多資料來源配置:兩個庫業務互不相干,a方法使用a庫的資料,b方法使用b庫的資料;
- 動態資料來源配置:兩個庫業務有關聯,如讀寫分離庫。
第一種直接配置2個單獨的資料來源,不同模組引入不同的sqlSessionFactory即可;第二種需要配置可動態切換的資料來源。
準備工作
兩種方式都需要在pom.xml檔案引入不同資料庫需要的jar包,此處以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
檔案,即指定這些dao的方法去連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中用到的相關類
- DataSource 註解
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)
註解。同樣,此註解也可以加在介面的方法上。