springboot集成多數據源

来源:https://www.cnblogs.com/eternityz/archive/2020/01/29/12241413.html
-Advertisement-
Play Games

簡要原理: 1)DataSourceEnum列出所有的數據源的key key 2)DataSourceHolder是一個線程安全的DataSourceEnum容器,並提供了向其中設置和獲取DataSourceEnum的方法 3)DynamicDataSource繼承AbstractRoutingDa ...


簡要原理:

1)DataSourceEnum列出所有的數據源的key---key

2)DataSourceHolder是一個線程安全的DataSourceEnum容器,並提供了向其中設置和獲取DataSourceEnum的方法

3)DynamicDataSource繼承AbstractRoutingDataSource並重寫其中的方法determineCurrentLookupKey(),在該方法中使用DataSourceHolder獲取當前線程的DataSourceEnum

4)MyBatisConfig中生成2個數據源DataSource的bean---value

5)MyBatisConfig中將1)和4)組成的key-value對寫入到DynamicDataSource動態數據源的targetDataSources屬性(當然,同時也會設置2個數據源其中的一個為DynamicDataSource的defaultTargetDataSource屬性中)

6)將DynamicDataSource作為primary數據源註入到SqlSessionFactory的dataSource屬性中去,並且該dataSource作為transactionManager的入參來構造DataSourceTransactionManager

7)使用spring aop根據不同的包設置不同的數據源(DataSourceExchange),先使用DataSourceHolder設置將要使用的數據源key

註意:在mapper層進行操作的時候,會先調用determineCurrentLookupKey()方法獲取一個數據源(獲取數據源:先根據設置去targetDataSources中去找,若沒有,則選擇defaultTargetDataSource),之後在進行資料庫操作。

DataSourceEnum

package com.theeternity.common.dataSource;

/**
 * @program: boxApi
 * @description: 多數據源枚舉類
 * @author: tonyzhang
 * @create: 2018-12-18 11:14
 */
public enum DataSourceEnum {
    /**
     * @Description: DS1數據源1, DS2數據源2
     * @Param:
     * @return:
     * @Author: tonyzhang
     * @Date: 2018-12-18 11:20
     */
    DS1("ds1"), DS2("ds2");

    private String key;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    DataSourceEnum(String key) {
        this.key = key;
    }
}

DataSourceHolder

作用:構建一個DatabaseType容器,並提供了向其中設置和獲取DataSourceEnmu的方法

package com.theeternity.common.dataSource;

/**
 * @program: boxApi
 * @description: DynamicDataSourceHolder用於持有當前線程中使用的數據源標識
 * @author: tonyzhang
 * @create: 2018-12-18 11:16
 */
public class DataSourceHolder {
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();

    public static void setDataSources(String dataSource) {
        dataSources.set(dataSource);
    }

    public static String getDataSources() {
        return dataSources.get();
    }
}

DynamicDataSource

作用:使用DatabaseContextHolder獲取當前線程的DataSourceEnmu

package com.theeternity.common.dataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @program: boxApi
 * @description: DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法
 * @author: tonyzhang
 * @create: 2018-12-18 11:17
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSources();
    }
}

MyBatisConfig

作用:
通過讀取application-test.yml文件生成兩個數據源(writeDS、readDS)
使用以上生成的兩個數據源構造動態數據源dataSource
@Primary:指定在同一個介面有多個實現類可以註入的時候,預設選擇哪一個,而不是讓@Autowire註解報錯(一般用於多數據源的情況下)
@Qualifier:指定名稱的註入,當一個介面有多個實現類的時候使用(在本例中,有兩個DataSource類型的實例,需要指定名稱註入)
@Bean:生成的bean實例的名稱是方法名(例如上邊的@Qualifier註解中使用的名稱是前邊兩個數據源的方法名,而這兩個數據源也是使用@Bean註解進行註入的)
通過動態數據源構造SqlSessionFactory和事務管理器(如果不需要事務,後者可以去掉)

package com.theeternity.beans.mybatisConfig;

import com.theeternity.common.dataSource.DataSourceEnum;
import com.theeternity.common.dataSource.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: ApiBoot
 * @description: 動態數據源配置
 * @author: TheEternity Zhang
 * @create: 2019-02-18 11:04
 */
@Configuration
public class MyBatisConfig {

    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    @Value("${mybatis.type-aliases-package}")
    private String basicPackage;

    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocation;


    @Bean(name="writeDS")
    @ConfigurationProperties(prefix = "primary.datasource.druid")
    public DataSource writeDataSource() {
        return DataSourceBuilder.create().type(dataSourceType).build();
    }
    /**
     * 有多少個從庫就要配置多少個
     * @return
     */
    @Bean(name = "readDS")
    @ConfigurationProperties(prefix = "back.datasource.druid")
    public DataSource readDataSourceOne(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }


    /**
     * @Primary 該註解表示在同一個介面有多個實現類可以註入的時候,預設選擇哪一個,而不是讓@autowire註解報錯
     * @Qualifier 根據名稱進行註入,通常是在具有相同的多個類型的實例的一個註入(例如有多個DataSource類型的實例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("writeDS") DataSource writeDS,
                                        @Qualifier("readDS") DataSource readDS) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceEnum.DS1.getKey(), writeDS);
        targetDataSources.put(DataSourceEnum.DS2.getKey(), readDS);

        DynamicDataSource dataSource =new DynamicDataSource();
        // 該方法是AbstractRoutingDataSource的方法
        dataSource.setTargetDataSources(targetDataSources);
        // 預設的datasource設置為writeDS
        dataSource.setDefaultTargetDataSource(writeDS);

        return dataSource;
    }

    /**
     * 根據數據源創建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        // 指定數據源(這個必須有,否則報錯)
        fb.setDataSource(dataSource);
        // 下邊兩句僅僅用於*.xml文件,如果整個持久層操作不需要使用到xml文件的話(只用註解就可以搞定),則不加
        // 指定基包
        fb.setTypeAliasesPackage(basicPackage);
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation));
        return fb.getObject();
    }

    /**
     * 配置事務管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }
}

DataSourceExchange

package com.theeternity.common.aop;

import com.theeternity.common.dataSource.DataSourceEnum;
import com.theeternity.common.dataSource.DataSourceHolder;
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @program: boxApi
 * @description: 數據源自動切換AOP
 * @author: tonyzhang
 * @create: 2018-12-18 11:20
 */
@Aspect
@Component
public class DataSourceExchange {

    private Logger logger= LoggerFactory.getLogger(DataSourceExchange.class);

    @Pointcut("execution(* com.theeternity.core.*.service..*(..))")
    public void pointcut(){}

    /** 
     * @Description: 在service方法開始之前切換數據源
     * @Param: [joinPoint] 
     * @return: void 
     * @Author: tonyzhang 
     * @Date: 2018-12-18 11:28
     */ 
    @Before(value="pointcut()")
    public void before(JoinPoint joinPoint){
        //獲取目標對象的類類型
        Class<?> aClass = joinPoint.getTarget().getClass();
        String c = aClass.getName();
        System.out.println("作用包名:"+c);
        String[] ss = c.split("\\.");
        //獲取包名用於區分不同數據源
        String packageName = ss[3];
        System.out.println("包名:"+packageName);
        if ("AutoGenerator".equals(packageName)) {
            DataSourceHolder.setDataSources(DataSourceEnum.DS1.getKey());
            logger.info("數據源:"+DataSourceEnum.DS1.getKey());
        } else {
            DataSourceHolder.setDataSources(DataSourceEnum.DS2.getKey());
            logger.info("數據源:"+DataSourceEnum.DS2.getKey());
        }
    }

    /** 
     * @Description: 執行完畢之後將數據源清空 
     * @Param: [joinPoint] 
     * @return: void 
     * @Author: tonyzhang 
     * @Date: 2018-12-18 11:27
     */ 
    @After(value="pointcut()")
    public void after(JoinPoint joinPoint){
        DataSourceHolder.setDataSources(null);

    }
}

屏蔽springboot自帶的自動註冊數據源

很多朋友反映遇到數據源迴圈依賴的問題,可以試一下將MyBatisConfig中的相關代碼換成這樣試試

首先要將spring boot自帶的DataSourceAutoConfiguration禁掉,因為它會讀取application.properties文件的spring.datasource.*屬性並自動配置單數據源。在@SpringBootApplication註解中添加exclude屬性即可:

package com.theeternity.core;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(scanBasePackages = "com.theeternity",exclude = {DataSourceAutoConfiguration.class})
/**
 * 全局配置,掃描指定包下的dao介面,不用每個dao介面上都寫@Mapper註解了
 */
@MapperScan("com.theeternity.core.*.dao")
public class CoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(CoreApplication.class, args);
    }

}

如果不屏蔽DataSourceAutoConfiguration可以使用如下測試一下(待測試)

將MyBatisConfig中SqlSessionFactory的構建方法改為下麵的

 @Bean
2     public SqlSessionFactory sqlSessionFactory(@Qualifier("writeDS") DataSource writeDS,
3                                                @Qualifier("readDS") DataSource readDS) throws Exception{
4         SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
5         fb.setDataSource(this.dataSource(writeDS, readDS));
6         fb.setTypeAliasesPackage(basicPackage);
          fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation));
8         return fb.getObject();
9     }

主配置文件

#配置使用的文件
spring:
  profiles:
    active: test,redis
  devtools:
    restart:
      enabled: true
      additional-paths: src/main/java
#配置tomcat埠及路徑
server:
  port: 8088
  servlet:
    context-path: /core
#mybatis配置
mybatis:
  mapper-locations: classpath*:/mybatis-mapper/*.xml
  type-aliases-package: com.theeternity.core.*.entity
  configuration:
    map-underscore-to-camel-case: true #駝峰命名
#mybatis plus配置
mybatis-plus:
  mapper-locations: classpath*:/mybatis-plus-mapper/*.xml
  # MyBaits 別名包掃描路徑,通過該屬性可以給包中的類註冊別名,註冊後在 Mapper 對應的 XML 文件中可以直接使用類名,而不用使用全限定的類名
  type-aliases-package: com.theeternity.core.*.entity
  # 資料庫表與實體類的駝峰命名自動轉換
  configuration:
    map-underscore-to-camel-case: true

配置文件application-test.yml

spring:
  datasource:
    #使用druid連接池
    type: com.alibaba.druid.pool.DruidDataSource

# 自定義的主數據源配置信息
primary:
  datasource:
    #druid相關配置
    druid:
      #監控統計攔截的filters
      filters: stat
      driverClassName: com.mysql.cj.jdbc.Driver
      #配置基本屬性
      url: jdbc:mysql://localhost:3306/wechatMVC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
      username: ***
      password: ***
      #配置初始化大小/最小/最大
      initialSize: 1
      minIdle: 1
      maxActive: 20
      #獲取連接等待超時時間
      maxWait: 60000
      #間隔多久進行一次檢測,檢測需要關閉的空閑連接
      timeBetweenEvictionRunsMillis: 60000
      #一個連接在池中最小生存的時間
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      #打開PSCache,並指定每個連接上PSCache的大小。oracle設為true,mysql設為false。分庫分表較多推薦設置為false
      poolPreparedStatements: false
      maxPoolPreparedStatementPerConnectionSize: 20

# 自定義的從數據源配置信息
back:
  datasource:
    #druid相關配置
    druid:
      #監控統計攔截的filters
      filters: stat
      driverClassName: com.mysql.cj.jdbc.Driver
      #配置基本屬性
      url: jdbc:mysql://localhost:3306/mycrm?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
      username: ***
      password: ***
      #配置初始化大小/最小/最大
      initialSize: 1
      minIdle: 1
      maxActive: 20
      #獲取連接等待超時時間
      maxWait: 60000
      #間隔多久進行一次檢測,檢測需要關閉的空閑連接
      timeBetweenEvictionRunsMillis: 60000
      #一個連接在池中最小生存的時間
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      #打開PSCache,並指定每個連接上PSCache的大小。oracle設為true,mysql設為false。分庫分表較多推薦設置為false
      poolPreparedStatements: false
      maxPoolPreparedStatementPerConnectionSize: 20

參考文檔:

https://www.cnblogs.com/java-zhao/p/5413845.html (主參考流程)

http://www.cnblogs.com/java-zhao/p/5415896.html (轉aop更改數據源)

https://blog.csdn.net/maoyeqiu/article/details/74011626 (將datasource註入bean簡單方法)

https://blog.csdn.net/neosmith/article/details/61202084(將spring boot自帶的DataSourceAutoConfiguration禁掉)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 圖解Java設計模式之設計模式七大原則 2.1 設計模式的目的 2.2 設計模式七大原則 2.3 單一職責原則 2.3.1 基本介紹 2.3.2 應用實例 2.4 介面隔離原則(Interface Segregation Principle) 2.4.1 基本介紹 2.4.2 應用實例 2.5 依賴 ...
  • 觀察者模式 發佈&訂閱 一對多 示例:點好咖啡之後坐等被叫 傳統 UML 類圖 javascript 中的 UML 類圖 應用場景 網頁事件綁定 promise jQuery callback nodejs 自定義事件 nodejs 處理文件 其他應用場景 nodejs 中:處理 http 請求,多 ...
  • 外觀模式 為子系統的一組介面提供了提個高層介面 使用者使用這個高層介面 示例:去醫院看病,接待員區掛號,門診,劃價,取藥 UML類圖 場景 設計原則驗證 + 不符合單一職責原則和開放封閉原則,因此謹慎使用,不可濫用 ...
  • 昨天簡單的看了看Unsafe的使用,今天我們看看JUC中的原子類是怎麼使用Unsafe的,以及分析一下其中的原理! 一.簡單使用AtomicLong 還記的上一篇博客中我們使用了volatile關鍵字修飾了一個int類型的變數,然後兩個線程,分別對這個變數進行10000次+1操作,最後結果不是200 ...
  • 大體流程: 1、瀏覽器向web伺服器發送HTTP請求 2、DispatcherServlet攔截所有請求,將請求地址(url)傳給HandlerMapping 3、HandlerMapping根據url-controller之間的映射關係,確定要調用的controller,並將要調用哪個contro ...
  • Apache Shiro是一個功能強大且易於使用的Java安全框架,它為開發人員提供了一種直觀,全面的身份驗證,授權,加密和會話管理解決方案。下麵是在SpringBoot中使用Shiro進行認證和授權的例子,代碼如下: pom.xml 導入SpringBoot和Shiro依賴: 也可以直接導入Apa ...
  • 有如下兩個切點: 此時可以這麼寫 ...
  • 問題場景 場景很簡單,就是一個正常 axios post 請求: 後臺說沒有接收到你的傳參。 這就有點奇怪了,我看了一下瀏覽器的請求信息是 OK 的,參數都是有的,而且之前這樣用 axios 也沒有這個問題。 但是這個介面是通用的,別人都用了,是 OK 的,介面沒問題。 問題原因 要點1 原因就是這 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...