項目4的高級實現(xiàn)思路大解密。
在做項目的過程中,有時候一個數(shù)據(jù)源是不夠,那么就需要配置多個數(shù)據(jù)源。本例介紹mybatis多數(shù)據(jù)源配置
前言
一般項目單數(shù)據(jù)源,使用流程如下:

單個數(shù)據(jù)源綁定給sessionFactory,再在Dao層操作,若多個數(shù)據(jù)源的話,那不是就成了下圖

可見,sessionFactory都寫死在了Dao層,若我再添加個數(shù)據(jù)源的話,則又得添加一個sessionFactory。所以比較好的做法應該是下圖

實現(xiàn)原理
1、擴展Spring的AbstractRoutingDataSource抽象類(該類充當了DataSource的路由中介, 能有在運行時, 根據(jù)某種key值來動態(tài)切換到真正的DataSource上。)
從AbstractRoutingDataSource的源碼中:
1 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
2、我們可以看到,它繼承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子類,So我們可以分析下它的getConnection方法:
public Connection getConnection() throws SQLException {
? ? return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
? ? return determineTargetDataSource().getConnection(username, password);
}
3、?獲取連接的方法中,重點是determineTargetDataSource()方法,看源碼:
/**
? ? * Retrieve the current target DataSource. Determines the
? ? * {@link #determineCurrentLookupKey() current lookup key}, performs
? ? * a lookup in the {@link #setTargetDataSources targetDataSources} map,
? ? * falls back to the specified
? ? * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
? ? * @see #determineCurrentLookupKey()
? ? */
? ? protected DataSource determineTargetDataSource() {
? ? ? ? Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
? ? ? ? Object lookupKey = determineCurrentLookupKey();
? ? ? ? DataSource dataSource = this.resolvedDataSources.get(lookupKey);
? ? ? ? if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
? ? ? ? ? ? dataSource = this.resolvedDefaultDataSource;
? ? ? ? }
? ? ? ? if (dataSource == null) {
? ? ? ? ? ? throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
? ? ? ? }
? ? ? ? return dataSource;
? ? }
上面這段源碼的重點在于determineCurrentLookupKey()方法,這是AbstractRoutingDataSource類中的一個抽象方法,而它的返回值是你所要用的數(shù)據(jù)源dataSource的key值,有了這個key值,resolvedDataSource(這是個map,由配置文件中設置好后存入的)就從中取出對應的DataSource,如果找不到,就用配置默認的數(shù)據(jù)源。
看完源碼,應該有點啟發(fā)了吧,沒錯!你要擴展AbstractRoutingDataSource類,并重寫其中的determineCurrentLookupKey()方法,來實現(xiàn)數(shù)據(jù)源的切換
案例
1、搭建一個Springmvc + Spring + Mybatis? maven項目,POM文件中引入AOP相關依賴,2、編輯一個擴展AbstractRoutingDataSource類,DynamicDataSource.java
package com.test.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*? ? 動態(tài)數(shù)據(jù)源(依賴于spring)
* @author peter huang
* @date 2019-08-03 17:27:35
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
? ? @Override
? ? protected Object determineCurrentLookupKey() {
? ? ? ? return DataSourceHolder.getDataSource();
? ? }
}
3、?封裝一個的對數(shù)據(jù)源進行操作的類,DataSourceHolder.java
package com.test.datasource;
public class DataSourceHolder {
? ? // 線程本地環(huán)境
? ? private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
? ? // 設置數(shù)據(jù)源
? ? public static void setDataSource(String customerType) {
? ? ? ? dataSources.set(customerType);
? ? }
? ? // 獲取數(shù)據(jù)源
? ? public static String getDataSource() {
? ? ? ? return (String) dataSources.get();
? ? }
? ? // 清除數(shù)據(jù)源
? ? public static void clearDataSource() {
? ? ? ? dataSources.remove();
? ? }
}
4、當需要切換數(shù)據(jù)源的時候執(zhí)行啦。手動在代碼中調用寫死嗎?調用setDataSource方法
但是這種方法比較死板,所以我們可以應用spring aop來設置,把配置的數(shù)據(jù)源類型都設置成為注解標簽,在service層中需要切換數(shù)據(jù)源的方法上,寫上注解標簽,調用相應方法切換數(shù)據(jù)源咯(就跟你設置事務一樣)
1 @TargetDataSource(name=TargetDataSource.SLAVE)
2 publicList getEmpsFromSalve()
編輯注解標簽TargetDataSource.java
package com.test.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
? ? String name() default TargetDataSource.MASTER;
? ? public static String MASTER = "dataSource1";
? ? public static String SLAVE = "dataSource2";
}
5、編輯切面的Bean,DataSourceExchange.java
package com.test.datasource;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import com.test.annotation.TargetDataSource;
public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {
? ? @Override
? ? public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
? ? ? ? DataSourceHolder.clearDataSource();
? ? }
? ? @Override
? ? public void before(Method method, Object[] args, Object target) throws Throwable {
? ? ? ? // 這里TargetDataSource是自定義的注解
? ? ? ? if (method.isAnnotationPresent(TargetDataSource.class)) {
? ? ? ? ? ? TargetDataSource datasource = method.getAnnotation(TargetDataSource.class);
? ? ? ? ? ? DataSourceHolder.setDataSource(datasource.name());
? ? ? ? } else {
? ? ? ? ? ? if(target.getClass().isAnnotationPresent(TargetDataSource.class))
? ? ? ? ? ? {
? ? ? ? ? ? ? ? TargetDataSource datasource = target.getClass().getAnnotation(TargetDataSource.class);
? ? ? ? ? ? ? ? DataSourceHolder.setDataSource(datasource.name());
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
6、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
? ? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
? ? xmlns:aop="http://www.springframework.org/schema/aop"
? ? xmlns:context="http://www.springframework.org/schema/context"
? ? xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
? ? xmlns:tx="http://www.springframework.org/schema/tx"
? ? xsi:schemaLocation="http://www.springframework.org/schema/beans
? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd
? ? ? ? http://mybatis.org/schema/mybatis-spring
? ? ? ? http://mybatis.org/schema/mybatis-spring.xsd
? ? ? ? http://www.springframework.org/schema/aop
? ? ? ? http://www.springframework.org/schema/aop/spring-aop.xsd
? ? ? ? http://www.springframework.org/schema/tx
? ? ? ? http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
? ? ? ? http://www.springframework.org/schema/context
? ? ? ? http://www.springframework.org/schema/context/spring-context-4.0.xsd">
? ? <!-- 引入數(shù)據(jù)庫的配置文件 -->
? ? <context:property-placeholder location="classpath:dbconfig.properties" />
? ? <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
? ? ? ? <property name="jdbcUrl" value="${datasource1.jdbc.url}"></property>
? ? ? ? <property name="driverClass" value="${datasource1.jdbc.driver}"></property>
? ? ? ? <property name="user" value="${datasource1.jdbc.username}"></property>
? ? ? ? <property name="password" value="${datasource1.jdbc.password}"></property>
? ? </bean>
? ? <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
? ? ? ? <property name="jdbcUrl" value="${datasource2.jdbc.url}"></property>
? ? ? ? <property name="driverClass" value="${datasource2.jdbc.driver}"></property>
? ? ? ? <property name="user" value="${datasource2.jdbc.username}"></property>
? ? ? ? <property name="password" value="${datasource2.jdbc.password}"></property>
? ? </bean>
? ? <!-- 數(shù)據(jù)源:Spring用來控制業(yè)務邏輯。數(shù)據(jù)源、事務控制、aop -->
? ? <bean id="dataSource" class="com.test.datasource.DynamicDataSource">
? ? ? ? <property name="targetDataSources">
? ? ? ? ? ? <map key-type="java.lang.String">
? ? ? ? ? ? ? ? <entry key="dataSource1" value-ref="dataSource1"></entry>
? ? ? ? ? ? ? ? <entry key="dataSource2" value-ref="dataSource2"></entry>
? ? ? ? ? ? </map>
? ? ? ? </property>
? ? ? ? <!-- 默認目標數(shù)據(jù)源為你主庫數(shù)據(jù)源 -->
? ? ? ? <property name="defaultTargetDataSource" ref="dataSource1"/>
? ? </bean>
? ? <!-- spring事務管理 -->
? ? <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
? ? ? ? <property name="dataSource" ref="dataSource"></property>
? ? </bean>
? ? <!-- 開啟基于注解的事務 -->
? ? <tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>
? ? <!--
? ? 整合mybatis
? ? ? ? 目的:1、spring管理所有組件。mapper的實現(xiàn)類。
? ? ? ? ? ? ? ? service==>Dao? @Autowired:自動注入mapper;
? ? ? ? ? ? 2、spring用來管理事務,spring聲明式事務
? ? -->
? ? <!--創(chuàng)建出SqlSessionFactory對象? -->
? ? <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
? ? ? ? <property name="dataSource" ref="dataSource"></property>
? ? ? ? <!-- configLocation指定全局配置文件的位置 -->
? ? ? ? <property name="configLocation" value="classpath:mybatis-config.xml"></property>
? ? ? ? <!--mapperLocations: 指定mapper文件的位置-->
? ? ? ? <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
? ? </bean>
? ? <!--配置一個可以進行批量執(zhí)行的sqlSession? -->
? ? <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
? ? ? ? <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
? ? ? ? <constructor-arg name="executorType" value="BATCH"></constructor-arg>
? ? </bean>
? ? <!-- 掃描所有的mapper接口的實現(xiàn),讓這些mapper能夠自動注入;
? ? base-package:指定mapper接口的包名
? ? -->
? ? <mybatis-spring:scan base-package="com.test.dao"/>
? ? <!-- 配置切面的Bean -->
? ? <bean id="dataSourceExchange" class="com.test.datasource.DataSourceExchange"/>
? ? <!-- 配置AOP -->
? ? <aop:config>
? ? ? ? <!-- 配置切點表達式? -->
? ? ? ? <aop:pointcut id="servicePointcut" expression="execution(* com.test.service.*.*(..))"/>
? ? ? ? <!-- 關鍵配置,切換數(shù)據(jù)源一定要比持久層代碼更先執(zhí)行(事務也算持久層代碼) <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/> -->
? ? ? ? <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
? ? </aop:config>
</beans>
注意:Spring中的事務是通過aop來實現(xiàn)的,當我們自己寫aop攔截的時候,會遇到跟spring的事務aop執(zhí)行的先后順序問題,比如說動態(tài)切換數(shù)據(jù)源的問題,如果事務在前,數(shù)據(jù)源切換在后,會導致數(shù)據(jù)源切換失效,所以就用到了Order(排序)這個關鍵字
1<aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
1<!-- 開啟基于注解的事務 -->2<tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>
7、在service上加上注解即可使用
1@Transactional
2@TargetDataSource(name=TargetDataSource.SLAVE)
3publicint addEmployeeFromSalve(Employee employee)?
{4
5return employeeMapper.insert(employee);
6}
數(shù)據(jù)流轉順序:
? 1.xml<aop>攔截到數(shù)據(jù)源名稱
? 2.執(zhí)行切面DataSourceExchange中的before方法,將數(shù)據(jù)源名稱放入 DataSourceHolder中
? 3.Spring 調用determineCurrentLookupKey()方法<DynamicDataSource中重寫AbstractRoutingDataSource類中的方法>?,從DataSourceHolder取出當前的數(shù)據(jù)庫名稱,并返回
4.AbstractRoutingDataSource類中determineTargetDataSource()方法調用determineCurrentLookupKey()匹配到指定的數(shù)據(jù)庫,并建立鏈接,即為切換到相應的數(shù)據(jù)庫;
5.在指定的數(shù)據(jù)庫中執(zhí)行相應的sql