MyBatis之配置多數(shù)據(jù)源

項目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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容