1. 概述
隨著項目規模的擴大和業務需求的復雜化,單一數據源已經不能滿足實際開發中的需求。在許多情況下,我們需要同時操作多個數據庫,或者需要將不同類型的數據存儲在不同的數據庫中。這時,多數據源場景成為必不可少的解決方案。
市面上常見的多數據源實現方案如下:
-
方案1:基于Spring框架提供的AbstractRoutingDataSource。
- 優點: 簡單易用,支持動態切換數據源;適用于少量數據源情況。
- 場景:適用于需要動態切換數據源,且數據庫較少的情況。
- 文檔地址:
-
方案2:使用MP提供的Dynamic-datasource多數據源框架。
- 文檔地址:https://baomidou.com/guides/dynamic-datasource/#dynamic-datasource
-
方案3:通過自定義注解在方法或類上指定數據源,實現根據注解切換數據源的功能。
- 優點: 靈活性高,能夠精確地控制數據源切換;在代碼中直觀明了。
- 場景: 適用于需要在代碼層面進行數據源切換,并對數據源切換有精細要求的情況。
-
方案4:使用動態代理技術,在運行時動態切換數據源,實現多數據源的切換。
- 優點: 靈活性高,支持在運行時動態切換數據源;適合對數據源切換的邏輯有特殊需求的情況。
- 場景: 適用于需要在運行時動態決定數據源切換策略的情況。
-
…
2. 基于SpringBoot的多數據源實現方案
1、執行sql腳本:(分別創建兩個數據庫,里面都提供一張user表)
-- 創建數據庫ds1
CREATE DATABASE `ds1`;-- 使用ds1數據庫
USE ds1;-- 創建user表
CREATE TABLE `user` (`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵id',`username` VARCHAR(50) COMMENT '用戶名',`gender` TINYINT(1) COMMENT '性別:0男,1女'
);-- 向user表插入數據
INSERT INTO user (username, gender) VALUES
('張三', 1),
('李四', 0),
('王五', 1);-- 創建數據庫ds2
CREATE DATABASE `ds2`;-- 使用ds2數據庫
USE ds2;-- 創建user表
CREATE TABLE `user` (`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵id',`username` VARCHAR(50) COMMENT '用戶名',`gender` TINYINT(1) COMMENT '性別:0男,1女'
);-- 向user表插入數據
INSERT INTO user (username, gender) VALUES
('趙六', 1),
('陳七', 0),
('寶國', 1);
2、創建一個maven工程,向pom.xml中添加依賴:
<!--鎖定SpringBoot版本-->
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version><relativePath/> <!-- lookup parent from repository -->
</parent><dependencies><!--jdbc起步依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency><!--test起步依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!--mysql驅動--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.20</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--jdbc起步依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>
</dependencies>
3、編寫實體類:
package cn.aopmin.entity;import lombok.*;/*** 實體類** @author 白豆五* @since 2024/7/4*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {private Integer id;private String username;private Integer gender;
}
4、創建application.yml文件,配置數據源:
spring:#動態數據源配置datasource:ds1:driverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://localhost:3306/ds1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456ds2:driverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://localhost:3306/ds2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456logging:level:cn.aopmin: debug
5、編寫數據源配置類:
package cn.aopmin.config;import org.springframework.beans.factory.annotation.Qualifier;
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.jdbc.core.JdbcTemplate;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 數據源配置類* 配置多數據源和動態數據源** @author 白豆五* @since 2024/7/4*/
@Configuration
public class DataSourceConfig {//定義數據源1@Bean("ds1")@ConfigurationProperties(prefix = "spring.datasource.ds1")public DataSource ds1() {return DataSourceBuilder.create().build();}//定義數據源2@Bean("ds2")@ConfigurationProperties(prefix = "spring.datasource.ds2")public DataSource ds2() {return DataSourceBuilder.create().build();}//定義動態數據源@Bean(name = "dataSource")public DataSource dynamicDataSource(@Qualifier("ds1") DataSource ds1,@Qualifier("ds2") DataSource ds2) {//1.定義數據源mapMap<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("ds1", ds1);targetDataSources.put("ds2", ds2);//2.實例化自定義的DynamicDataSource對象, 并設置數據源mapDynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);//3.設置默認數據源,未匹配上則使用默認數據源dynamicDataSource.setDefaultTargetDataSource(ds1);return dynamicDataSource;}// 通過JdbcTemplate @Beanpublic JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource ds) {return new JdbcTemplate(ds);}
}
6、創建DynamicDataSource動態數據類:
package cn.aopmin.config;import cn.aopmin.common.DataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** AbstractRoutingDataSource(抽象的數據源路由器) 的基本原理是, 它維護了一個數據源的集合,每個數據源都有唯一的一個標識符* 當應用程序需要訪問數據庫的時候,AbstractRoutingDataSource會根據某種匹配規則(例如請求參數、用戶身份等)來選擇一個合適的數據源,* 并將請求轉發給這個數據源。*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 獲取數據源名稱* @return*/@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSource();}
}
7、定義一個ThreadLocal工具類:
package cn.aopmin.common;/*** 使用ThreadLocal保存數據源名稱** @author 白豆五* @since 2024/7/4*/
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();// 將數據源名稱綁定到當前線程上public static void setDataSource(String dataSourceName) {contextHolder.set(dataSourceName);}// 獲取當前線程上的數據源名稱public static String getDataSource() {return contextHolder.get();}// 清除數據源名稱public static void clearDataSource() {contextHolder.remove();}
}
8、創建啟動類
package cn.aopmin;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** 啟動類** @author 白豆五* @since 2024/7/3*/
@SpringBootApplication
public class Demo01Application {public static void main(String[] args) {SpringApplication.run(Demo01Application.class, args);}
}
9、創建UserService:
package cn.aopmin.service;import cn.aopmin.common.DataSourceContextHolder;
import cn.aopmin.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;/*** @author 白豆五* @since 2024/7/4*/
@Service
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;public void insertDs1(User user) {try {// todo:自定義注解+SpringAop實現數據源的切換DataSourceContextHolder.setDataSource("ds1");String sql = "insert into user(username,gender) values(?,?)";jdbcTemplate.update(sql,user.getUsername(), user.getGender());} finally {DataSourceContextHolder.clearDataSource();}}public void insertDs2(User user) {try {DataSourceContextHolder.setDataSource("ds2");String sql = "insert into user(username,gender) values(?,?)";jdbcTemplate.update(sql,user.getUsername(), user.getGender());} finally {DataSourceContextHolder.clearDataSource();}}
}
10、編寫測試:
package cn.aopmin.service;import cn.aopmin.Demo01Application;
import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest(classes = {Demo01Application.class})
public class UserServiceTest {@Resourceprivate UserService userService;@Testpublic void testInsertDs1() {User user = new User();user.setUsername("jack");user.setGender(0);user.setGender(1);userService.insertDs1(user);}@Testpublic void testInsertDs2() {User user = new User();user.setUsername("rose");user.setGender(1);userService.insertDs2(user);}
}
最終效果:
3. 基于Dynamic-datasource實現方案
mp文檔:https://baomidou.com/guides/dynamic-datasource/#_top
1、創建SpringBoot工程,引入Dynamic-datasource依賴:
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.2</version>
</dependency>
2、配置數據源:
spring:#多數據源配置datasource:dynamic:primary: master #設置默認數據源strict: false #是否嚴格檢查動態數據源提供的數據庫名datasource:#數據源1master:url: jdbc:mysql:///ds1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver#數據源2slave1:url: jdbc:mysql:///ds2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
3、實體類:
package cn.aopmin.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 實體類** @author 白豆五* @since 2024/7/4*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer id;private String username;private Integer gender;
}
4、業務類:
package cn.aopmin.service;import cn.aopmin.entity.User;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;/*** 通過@DS注解切換數據源* @author 白豆五* @since 2024/7/4*/
@Service
// @DS("master") //不加@DS注解,會使用默認數據源
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;public void insertDs1(User user) {String sql = "insert into user(username,gender) values(?,?)";jdbcTemplate.update(sql,user.getUsername(), user.getGender());}@DS("slave1")public void insertDs2(User user) {String sql = "insert into user(username,gender) values(?,?)";jdbcTemplate.update(sql,user.getUsername(), user.getGender());}
}
4、測試類:
package cn.aopmin.service;import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest
public class UserServiceTest2 {@Resourceprivate UserService userService;@Testpublic void testInsertDs1() {User user = new User();user.setUsername("jack");user.setGender(0);user.setGender(1);userService.insertDs1(user);}@Testpublic void testInsertDs2() {User user = new User();user.setUsername("rose");user.setGender(1);userService.insertDs2(user);}
}