天天看點

mysql多資料源切換_Spring多資料源動态切換

原理

mysql多資料源切換_Spring多資料源動态切換
mysql多資料源切換_Spring多資料源動态切換

DataSource向外提供一個 getConnection() 方法,得getConnection者得資料庫

AbstractRoutingDataSource 實作了 getConnection() 方法

// line 166

@Override

public Connection getConnection() throws SQLException {

return determineTargetDataSource().getConnection();

}

... 省略若幹代碼

// line 190

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;

}

@Nullable

protected abstract Object determineCurrentLookupKey();

然而 ....

AbstractRoutingDataSource 的getConnection() 方法隻是調用了 determinTargetDataSource().getConnection() 來擷取真正DataSource的getConnection()。

mysql多資料源切換_Spring多資料源動态切換

這是典型的裝飾模式!!自己沒有的功能通過引入其他類來增強。

我們先來看看 AbstractRoutingDataSource 的類結構

mysql多資料源切換_Spring多資料源動态切換

被框框套住的都是重要的。

方法determineCurrentLookupKey() 是留給我們開發者的(就像你家的網線口),我們通過實作該方法在不同資料源之間切換。

實踐

1. 配置多資料源

在 application.yml 如下配置

spring:

datasource:

# 資料源類型

type: com.alibaba.druid.pool.DruidDataSource

# 預設資料源

default-datasource:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/db0?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8

username: root

password: 123456

# 多資料源

target-datasources:

datasource1:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8

username: root

password: 123456

datasource2:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8

username: root

password: 123456

# druid 預設配置

druid:

# 初始連接配接數

initial-size: 10

# 最大連接配接池數量

max-active: 100

# 最小連接配接池數量

min-idle: 10

# 配置擷取連接配接等待逾時的時間

max-wait: 60000

# 打開PSCache,并且指定每個連接配接上PSCache的大小

pool-prepared-statements: true

max-pool-prepared-statement-per-connection-size: 20

# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒

timeBetweenEvictionRunsMillis: 60000

# 配置一個連接配接在池中最小生存的時間,機關是毫秒

min-evictable-idle-time-millis: 300000

validation-query: SELECT 1 FROM DUAL

test-while-idle: true

test-on-borrow: false

test-on-return: false

stat-view-servlet:

enabled: true

url-pattern: /monitor/druid*Mapper.xml

# 加載全局的配置檔案

configLocation: classpath:mybatis-config.xml

此處配置的名稱(如 defaultDataSource、targetDataSources)的命名并無特殊要求,隻要和下面第n步的 DataSourceConfig 中對應起來就可以

使用 Druid 資料源的話,要在 pom.xml 中引入依賴

com.alibaba

druid-spring-boot-starter

1.1.10

2. 實作動态資料源

DynamicDataSource 動态資料源,在多個資料源之間切換

public class DynamicDataSource extends AbstractRoutingDataSource {

public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {

super.setDefaultTargetDataSource(defaultTargetDataSource);

super.setTargetDataSources(targetDataSources);

super.afterPropertiesSet();

}

@Override

protected Object determineCurrentLookupKey() {

return DataSourceContextHolder.getDataSourceType();

}

}

DataSourceContextHolder 資料源上下文,使用線程變量來存儲代表目前使用的資料源的key值(每個key值都對應一個資料源,用以區分多資料源)

public class DataSourceContextHolder {

public static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal();

public static void setDataSourceType(String dsType) {

CONTEXT_HOLDER.set(dsType);

}

public static String getDataSourceType() {

return CONTEXT_HOLDER.get();

}

public static void removeDataSourceType() {

CONTEXT_HOLDER.remove();

}

}

DataSourceType 資料源對應的key(其實單純的用字元串來表示資料源,替換枚舉類DataSourceType也是可以的,但是寫代碼時要注意字元串統一)

public enum DataSourceType {

DEFAULT_DATASOURCE,

DATASOURCE1,

DATASOURCE2;

}

3. 将資料源添加到 Spring 容器中

@Configuration

public class DataSourceConfig {

@Bean

@ConfigurationProperties(prefix = "spring.datasource.default-datasource")

public DataSource defaultDataSource() {

return DruidDataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource1")

public DataSource dataSource1() {

return DruidDataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource2")

public DataSource dataSource2() {

return DruidDataSourceBuilder.create().build();

}

@Bean

@Primary

public DataSource dynamicDataSource(DataSource defaultDataSource, DataSource dataSource1, DataSource dataSource2) {

// 注意:該方法的參數名稱要和前面前面三個datasource對象在Spring容器中的bean名稱一樣

// 或者使用 @Qualifier 指定具體的bean

Map targetDataSources = new HashMap<>();

targetDataSources.put(DataSourceType.DEFAULT_DATASOURCE.name(), defaultDataSource);

targetDataSources.put(DataSourceType.DATASOURCE1.name(), dataSource1);

targetDataSources.put(DataSourceType.DATASOURCE2.name(), dataSource2);

return new DynamicDataSource(defaultDataSource, targetDataSources);

}

}

測試

為了友善,省略了 Service 層

TestController

@RestController

@RequestMapping("/test")

public class TestController {

@Autowired

private TestMapper testMapper;

@GetMapping

public List> test(String dataSourceIndex) {

// 根據參數值的不同,切換資料源

if ("1".equals(dataSourceIndex)) {

DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE1.name());

} else if ("2".equals(dataSourceIndex)) {

DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE2.name());

}

List> mapList = testMapper.selectList();

// 清除線程内部變量資料源key

DataSourceContextHolder.removeDataSourceType();

return mapList;

}

}

TestMapper

@Repository

public interface TestMapper {

List> selectList();

}

TestMapper.xml

SELECT * FROM test

别忘了要準備資料哦!

下面SQL語句,建立3個資料庫,然後在3個資料庫中都建立一張test表,并各自插入不同的資料。

-- 建立資料庫

create database db0 character set utf8 collate utf8_general_ci;

create database db1 character set utf8 collate utf8_general_ci;

create database db2 character set utf8 collate utf8_general_ci;

-- 在資料庫db1下執行以下SQL

use db0;

create table test(

id int(11) primary key auto_increment,

name varchar(20)

) ;

insert into test(name) values('張三');

-- 在資料庫db1下執行以下SQL

use db1;

create table test(

id int(11) primary key auto_increment,

name varchar(20)

) ;

insert into test(name) values('李四');

-- 在資料庫db2下執行以下SQL

use db2;

create table test(

id int(11) primary key auto_increment,

name varchar(20)

) ;

insert into test(name) values('王五');

OK,一切準備就緒,啟動應用吧!!!

一啟動就出現了各種各樣的,似乎無窮無盡的報錯!一頭黑線。

1. 找不到TestMapper

Field testMapper in com.liuchuanv.dynamicdatasource.controller.TestController required a bean of type 'com.liuchuanv.dynamicdatasource.mapper.TestMapper' that could not be found.

解決方法:在 DynamicdatasourceApplication 頭上添加注解 @MapperScan("com.liuchuanv.*.mapper")

2. dynamicDataSource 依賴循環

┌─────┐

| dynamicDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]

↑ ↓

| defaultDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]

↑ ↓

| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker

└─────┘

解決方法:在 DynamicdatasourceApplication 頭上修改注解 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

mysql多資料源切換_Spring多資料源動态切換

使用的是預設資料源 defaultDataSource

mysql多資料源切換_Spring多資料源動态切換

使用的是資料源 dataSource1

mysql多資料源切換_Spring多資料源動态切換

使用的是資料源 dataSource2

建議大家在心裡總結一下整個的過程,其實很簡單