国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > Java > 正文

Spring+Mybatis動態切換數據源的方法

2024-07-13 10:17:25
字體:
來源:轉載
供稿:網友

功能需求是公司要做一個大的運營平臺:

1、運營平臺有自身的數據庫,維護用戶、角色、菜單、部分以及權限等基本功能。

2、運營平臺還需要提供其他不同服務(服務A,服務B)的后臺運營,服務A、服務B的數據庫是獨立的。

所以,運營平臺至少要連三個庫:運營庫,A庫,B庫,并且希望達到針對每個功能請求能夠自動切換到對應的數據源(我最終實現是針對Service的方法級別進行切換的,也可以實現針對每個DAO層的方法進行切換。我們系統的功能是相互之間比較獨立的)。

第一步:配置多數據源

1、定義數據源:

我采用的數據源是阿里的DruidDataSource(用DBCP也行,這個隨便)。配置如下:

<!-- op dataSource -->  <bean id="opDataSource" class="com.alibaba.druid.pool.DruidDataSource"    init-method="init" destroy-method="close">    <property name="url" value="${db.master.url}" />    <property name="username" value="${db.master.user}" />    <property name="password" value="${db.master.password}" />    <property name="driverClassName" value="${db.master.driver}" />    <property name="initialSize" value="5" />    <property name="maxActive" value="100" />    <property name="minIdle" value="10" />    <property name="maxWait" value="60000" />    <property name="validationQuery" value="SELECT 'x'" />    <property name="testOnBorrow" value="false" />    <property name="testOnReturn" value="false" />    <property name="testWhileIdle" value="true" />    <property name="timeBetweenEvictionRunsMillis" value="600000" />    <property name="minEvictableIdleTimeMillis" value="300000" />    <property name="removeAbandoned" value="true" />    <property name="removeAbandonedTimeout" value="1800" />    <property name="logAbandoned" value="true" />    <!-- 配置監控統計攔截的filters -->    <property name="filters" value="config,mergeStat,wall,log4j2" />    <property name="connectionProperties" value="config.decrypt=true" />  </bean>  <!-- serverA dataSource -->  <bean id="serverADataSource" class="com.alibaba.druid.pool.DruidDataSource"    init-method="init" destroy-method="close">    <property name="url" value="${db.serverA.master.url}" />    <property name="username" value="${db.serverA.master.user}" />    <property name="password" value="${db.serverA.master.password}" />    <property name="driverClassName" value="${db.serverA.master.driver}" />    <property name="initialSize" value="5" />    <property name="maxActive" value="100" />    <property name="minIdle" value="10" />    <property name="maxWait" value="60000" />    <property name="validationQuery" value="SELECT 'x'" />    <property name="testOnBorrow" value="false" />    <property name="testOnReturn" value="false" />    <property name="testWhileIdle" value="true" />    <property name="timeBetweenEvictionRunsMillis" value="600000" />    <property name="minEvictableIdleTimeMillis" value="300000" />    <property name="removeAbandoned" value="true" />    <property name="removeAbandonedTimeout" value="1800" />    <property name="logAbandoned" value="true" />    <!-- 配置監控統計攔截的filters -->    <property name="filters" value="config,mergeStat,wall,log4j2" />    <property name="connectionProperties" value="config.decrypt=true" />  </bean>  <!-- serverB dataSource -->  <bean id="serverBDataSource" class="com.alibaba.druid.pool.DruidDataSource"    init-method="init" destroy-method="close">    <property name="url" value="${db.serverB.master.url}" />    <property name="username" value="${db.serverB.master.user}" />    <property name="password" value="${db.serverB.master.password}" />    <property name="driverClassName" value="${db.serverB.master.driver}" />    <property name="initialSize" value="5" />    <property name="maxActive" value="100" />    <property name="minIdle" value="10" />    <property name="maxWait" value="60000" />    <property name="validationQuery" value="SELECT 'x'" />    <property name="testOnBorrow" value="false" />    <property name="testOnReturn" value="false" />    <property name="testWhileIdle" value="true" />    <property name="timeBetweenEvictionRunsMillis" value="600000" />    <property name="minEvictableIdleTimeMillis" value="300000" />    <property name="removeAbandoned" value="true" />    <property name="removeAbandonedTimeout" value="1800" />    <property name="logAbandoned" value="true" />    <!-- 配置監控統計攔截的filters -->    <property name="filters" value="config,mergeStat,wall,log4j2" />    <property name="connectionProperties" value="config.decrypt=true" />  </bean>

我配置了三個數據源:oPDataSource(運營平臺本身的數據源),serverADataSource,serverBDataSource。

2、配置multipleDataSource

multipleDataSource相當于以上三個數據源的一個代理,真正與Spring/Mybatis相結合的時multipleDataSource,和單獨配置的DataSource使用沒有分別:

<!-- Spring整合Mybatis:配置multipleDatasource -->  <bean id="sqlSessionFactory"    class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">    <property name="dataSource" ref="multipleDataSource" />    <!-- 自動掃描Mapping.xml文件 -->    <property name="mapperLocations">      <list>        <value>classpath*:/sqlMapperXml/*.xml</value>        <value>classpath*:/sqlMapperXml/*/*.xml</value>      </list>    </property>    <property name="configLocation" value="classpath:xml/mybatis-config.xml"></property>    <property name="typeAliasesPackage" value="com.XXX.platform.model" />    <property name="globalConfig" ref="globalConfig" />    <property name="plugins">      <array>        <!-- 分頁插件配置 -->        <bean id="paginationInterceptor"          class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">          <property name="dialectType" value="mysql" />          <property name="optimizeType" value="aliDruid" />        </bean>      </array>    </property>  </bean>    <!-- MyBatis 動態實現 -->  <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">    <!-- 對Dao 接口動態實現,需要知道接口在哪 -->    <property name="basePackage" value="com.XXX.platform.mapper" />    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>  </bean>    <!-- MP 全局配置 -->  <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">    <property name="idType" value="0" />    <property name="dbColumnUnderline" value="true" />  </bean>  <!-- 事務管理配置multipleDataSource -->  <bean id="transactionManager"    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    <property name="dataSource" ref="multipleDataSource"></property>  </bean>

了解了multipleDataSource所處的位置之后,接下來重點看下multipleDataSource怎么實現,配置文件如下:

<bean id="multipleDataSource" class="com.xxxx.platform.commons.db.MultipleDataSource">    <property name="defaultTargetDataSource" ref="opDataSource" />    <property name="targetDataSources">      <map>        <entry key="opDataSource" value-ref="opDataSource" />        <entry key="serverADataSource" value-ref="serverADataSource" />        <entry key="serverBDataSource" value-ref="serverBDataSource" />      </map>    </property>  </bean>

實現的Java代碼如下,不需要過多的解釋,很一目了然:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** *  * @ClassName: MultipleDataSource * @Description: 配置多個數據源<br> * @author: yuzhu.peng * @date: 2018年1月12日 下午4:37:25 */public class MultipleDataSource extends AbstractRoutingDataSource {    private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();    public static void setDataSourceKey(String dataSource) {    dataSourceKey.set(dataSource);  }    @Override  protected Object determineCurrentLookupKey() {    return dataSourceKey.get();  }    public static void removeDataSourceKey() {    dataSourceKey.remove();  }}

繼承自spring的AbstractRoutingDataSource,實現抽象方法determineCurrentLookupKey,這個方法會在每次獲得數據庫連接Connection的時候之前,決定本次連接的數據源Datasource,可以看下Spring的代碼就很清晰了:

/*獲取連接*/  public Connection getConnection()    throws SQLException {    return determineTargetDataSource().getConnection();  }  protected DataSource determineTargetDataSource() {    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");    /*此處的determineCurrentLookupKey為抽象接口,獲取具體的數據源名稱*/    Object lookupKey = determineCurrentLookupKey();    DataSource 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;  }  /*抽象接口:也即我們的multipleDataSource實現的接口*/  protected abstract Object determineCurrentLookupKey();

第二步:每次請求(Service方法級別)動態切換數據源

 實現思路是利用Spring的AOP思想,攔截每次的Service方法調用,然后根據方法的整體路徑名,動態切換multipleDataSource中的數據的key。我們的項目,針對不同服務也即不同數據庫的操作,是彼此之間互相獨立的,不太建議在同一個service方法中調用不同的數據源,這樣的話需要將動態判斷是否需要切換的頻次(AOP攔截的頻次)放在DAO級別,也就是SQL級別。另外,還不方便進行事務管理。

我們來看動態切換數據源的AOP實現:

 

import java.lang.reflect.Proxy;import org.apache.commons.lang.ClassUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.annotation.Order;/** * 數據源切換AOP *  * @author yuzhu.peng * @since 2018-01-15 */@Aspect@Order(1)public class MultipleDataSourceInterceptor {  /**   * 攔截器對所有的業務實現類請求之前進行數據源切換 特別注意,由于用到了多數據源,Mapper的調用最好只在*ServiceImpl,不然調用到非默認數據源的表時,會報表不存在的異常   *    * @param joinPoint   * @throws Throwable   */  @Before("execution(* com.xxxx.platform.service..*.*ServiceImpl.*(..))")  public void setDataSoruce(JoinPoint joinPoint)    throws Throwable {    Class<?> clazz = joinPoint.getTarget().getClass();    String className = clazz.getName();    if (ClassUtils.isAssignable(clazz, Proxy.class)) {      className = joinPoint.getSignature().getDeclaringTypeName();    }    // 對類名含有serverA的設置為serverA數據源,否則默認為后臺的數據源    if (className.contains(".serverA.")) {      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);    }    else if (className.contains(".serverB.")) {      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);    }    else {      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);    }  }    /**   * 當操作完成時,釋放當前的數據源 如果不釋放,頻繁點擊時會發生數據源沖突,本是另一個數據源的表,結果跑到另外一個數據源去,報表不存在   *    * @param joinPoint   * @throws Throwable   */  @After("execution(* com.xxxx.service..*.*ServiceImpl.*(..))")  public void removeDataSoruce(JoinPoint joinPoint)    throws Throwable {    MultipleDataSource.removeDataSourceKey();  }}

攔截所有的ServiceImpl方法,根據方法的全限定名去判斷屬于那個數據源的功能,然后選擇相應的數據源,發放執行完后,釋放當前的數據源。注意我用到了Spring的 @Order,注解,接下來會講到,當定義多個AOP的時候,order是很有用的。

其他:

一開始項目中并沒有引入事務,所以一切都OK,每次都能訪問到正確的數據源,當加入SPring的事務管理后,不能動態切換數據源了(也好像是事務沒有生效,反正是二者沒有同時有效),后來發現原因是AOP的執行順序問題,所以用到了上邊提到的SPring的Order:

Spring,Mybatis,數據源

Spring,Mybatis,數據源

 order越小,先被執行。至此,既可以動態切換數據源,又可以成功用事務(在同一個數據源)。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 称多县| 岑巩县| 宜兰县| 龙陵县| 牙克石市| 阜新市| 无锡市| 郓城县| 广丰县| 湘潭县| 伊春市| 黔南| 洛阳市| 道孚县| 海盐县| 内乡县| 新龙县| 南郑县| 忻州市| 上饶市| 桐庐县| 新泰市| 伊通| 平昌县| 枣阳市| 顺平县| 永靖县| 佛冈县| 七台河市| 大荔县| 天长市| 永济市| 闽侯县| 鸡泽县| 抚远县| 清原| 虞城县| 辽阳县| 即墨市| 正宁县| 天长市|