利用SpringBoot實現多數據源的兩種方式總結
前言
公司項目有連接多個不同數據庫的需求,特研究了一下,根據網上的資料,造了一個基于AOP方式的數據源切換輪子,但繼續(xù)探索,突然發(fā)現有開源的多數據源管理啟動器。不過,本篇兩種方式都會介紹。
基于dynamic-datasource實現多數據源
dynamic-datasource介紹
dynamic-datasource-spring-boot-starter 是一個基于springboot的快速集成多數據源的啟動器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x
dynamic-datasource特性
- 支持 數據源分組 ,適用于多種場景 純粹多庫 讀寫分離 一主多從 混合模式。
- 支持數據庫敏感配置信息 加密 ENC()。
- 支持每個數據庫獨立初始化表結構schema和數據庫database。
- 支持無數據源啟動,支持懶加載數據源(需要的時候再創(chuàng)建連接)。
- 支持 自定義注解 ,需繼承DS(3.2.0+)。
- 提供并簡化對Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供對Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等組件的集成方案。
- 提供 自定義數據源來源 方案(如全從數據庫加載)。
- 提供項目啟動后 動態(tài)增加移除數據源 方案。
- 提供Mybatis環(huán)境下的 純讀寫分離 方案。
- 提供使用 spel動態(tài)參數 解析數據源方案。內置spel,session,header,支持自定義。
- 支持 多層數據源嵌套切換 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 基于seata的分布式事務方案。
- 提供 本地多數據源事務方案。 附:不能和原生spring事務混用。
我們目前只探討使用dynamic-datasource進行數據源切換,其他請自行搜索
dynamic-datasource的相關約定
- dynamic-datasource只做 切換數據源 這件核心的事情,并不限制你的具體操作,切換了數據源可以做任何CRUD。
- 配置文件所有以下劃線 _ 分割的數據源 首部 即為組的名稱,相同組名稱的數據源會放在一個組下。
- 切換數據源可以是組名,也可以是具體數據源名稱。組名則切換時采用負載均衡算法切換。
- 默認的數據源名稱為 master ,你可以通過 spring.datasource.dynamic.primary 修改。
- 方法上的注解優(yōu)先于類上注解。
- DS支持繼承抽象類上的DS,暫不支持繼承接口上的DS。
引入dynamic-datasource依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
配置數據源
spring:
datasource:
dynamic:
primary: mysql #設置默認的數據源或者數據源組,默認值即為master
strict: false #嚴格匹配數據源,默認false. true未匹配到指定數據源時拋異常,false使用默認數據源
datasource:
mysql:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0開始支持SPI可省略此配置
pgsql:
url: ENC(xxxxx) # 內置加密
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: org.postgresql.Driver
使用 @DS 切換數據源
@DS 可以注解在方法上或類上,同時存在就近原則 方法上注解 優(yōu)先于 類上注解。
| 注解 | 結果 |
|---|---|
| 不使用@DS注解 | 默認數據源,即primary: mysql |
| @DS(“dsName”) | dsName可以為組名也可以為具體某個庫的名稱 |
@DS使用實例
@Service
@DS("mysql")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 不使用@DS注解則代表使用默認數據源
// 如果類上存在,則使用類上標注的數據源
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("pgsql")
// 方法上注解 優(yōu)先于 類上注解,即使類上標注也優(yōu)先采用方法上的標注
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
基于AOP手動實現多數據源
本次代碼參考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源碼不滿足的需求,因此我在此基礎做了修改。
項目工程結構

項目依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>me.mason.demo</groupId>
<artifactId>dynamic-datasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dynamic-datasource</name>
<description>Demo project for dynamic datasource</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mysql 驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server.port=8080 server.servlet.context-path=/dd logging.level.root=INFO logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG # mybatis-plus mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity # 默認位置,可不配置 #mybatis-plus.mapper-locations=classpath*:/mapper/*.xml mybatis.mapper-locations=classpath*:/mapper/*.xml # 使用數據庫自增ID mybatis-plus.global-config.db-config.id-type=auto # master spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.master.username=root spring.datasource.master.password=123456 # slave spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.slave.username=root spring.datasource.slave.password=123456
自定義注解
// 標記注解可使用在方法與類上
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
// 默認值為MASTER
String value() default DataSourceConstants.DS_KEY_MASTER;
}
編寫DataSourceConstants
/**
* 數據源常量
**/
public class DataSourceConstants {
/**
* master數據源
*/
public static final String DS_KEY_MASTER = "master";
/**
* slave數據源
*/
public static final String DS_KEY_SLAVE = "slave";
}
動態(tài)數據源名稱上下文處理
/**
* 動態(tài)數據源名稱上下文處理
**/
public class DynamicDataSourceContextHolder {
/**
* 動態(tài)數據源名稱上下文
*/
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/**
* 設置數據源
* @param key
*/
public static void setContextKey(String key){
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/**
* 獲取數據源名稱
* @return
*/
public static String getContextKey(){
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null?DataSourceConstants.DS_KEY_MASTER:key;
}
/**
* 刪除當前數據源名稱
*/
public static void removeContextKey(){
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
獲取當前動態(tài)數據源方法
/**
* 動態(tài)數據源
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
動態(tài)數據源配置
/**
* 動態(tài)數據源配置
**/
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@Configuration
// 此處我們
//@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
// 需要與配置文件中對應
@ConfigurationProperties(prefix = "spring.datasource.master")
public DruidDataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DruidDataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
//設置動態(tài)數據源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
AOP切面
/**
* 切面
*/
@Aspect
@Component
//@Order(-10)
public class DynamicDataSourceAspect {
// 以在類上使用了@Service作為切入點
@Pointcut("@within(org.springframework.stereotype.Service)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?> aClass = Class.forName(signature.getDeclaringType().getName());
// 方法優(yōu)先,如果方法上存在注解,則優(yōu)先使用方法上的注解
if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value());
// 其次類優(yōu)先,如果類上存在注解,則使用類上的注解
}else if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value());
// 如果都不存在,則使用默認
} else {
DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER);
}
try {
return joinPoint.proceed();
} finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
}
編寫TestUser實體
@Data
@TableName("test_user")
public class TestUser implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 姓名 */
private String name;
/** 手機號 */
private String phone;
/** 職稱職別 */
private String title;
/** 郵箱 */
private String email;
/** 性別 */
private String gender;
/** 出生時間 */
private Date dateOfBirth;
/** 1:已刪除,0:未刪除 */
private Integer deleted;
/** 創(chuàng)建時間 */
private Date sysCreateTime;
/** 創(chuàng)建人 */
private String sysCreateUser;
/** 更新時間 */
private Date sysUpdateTime;
/** 更新人 */
private String sysUpdateUser;
/** 版本號 */
private Long recordVersion;
public TestUser() {
}
}
TestUserMapper
@Repository
public interface TestUserMapper extends BaseMapper<TestUser> {
/**
* 自定義查詢
* @param wrapper 條件構造器
* @return
*/
List<TestUser> selectAll(@Param(Constants.WRAPPER) Wrapper<TestUser> wrapper);
}
TestUserService
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查詢master庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser(){
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查詢slave庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getSlaveUser(){
return testUserMapper.selectList(null);
}
}
TestUserController
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private TestUserService testUserService;
/**
* 查詢全部
*/
@GetMapping("/listall")
public Object listAll() {
int initSize = 2;
Map<String, Object> result = new HashMap<>(initSize);
List<TestUser> masterUser = testUserService.getMasterUser();
result.put("masterUser", masterUser);
List<TestUser> slaveUser = testUserService.getSlaveUser();
result.put("getSlaveUser", slaveUser);
return ResponseResult.success(result);
}
}
MapperXml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.mason.demo.dynamicdatasource.mapper.TestUserMapper">
<select id="selectAll" resultType="me.mason.demo.dynamicdatasource.entity.TestUser">
select * from test_user
<if test="ew!=null">
${ew.customSqlSegment}
</if>
</select>
</mapper>
啟動測試
不使用注解
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查詢master庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser(){
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查詢slave庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getSlaveUser(){
return testUserMapper.selectList(null);
}
}
效果
該代碼優(yōu)先級與使用框架效果一致,即不使用注解將默認使用MASTER數據庫,方法上存在注解優(yōu)先使用方法上標注的注解。
已知MASTER 6條數據, SLAVE4條數據
訪問 http://127.0.0.1:8080/dd/user/listall 查看效果

類上使用注解
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查詢master庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser(){
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查詢slave庫User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getSlaveUser(){
return testUserMapper.selectList(null);
}
}
效果

方法上使用注解
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查詢master庫User
* @return
*/
@DS(DataSourceConstants.DS_KEY_SLAVE)
public List<TestUser> getMasterUser(){
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查詢slave庫User
* @return
*/
@DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getSlaveUser(){
return testUserMapper.selectList(null);
}
}
效果

總結
到此這篇關于利用SpringBoot實現多數據源的兩種方式的文章就介紹到這了,更多相關SpringBoot實現多數據源內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot如何獲取applicationContext?servletContext
這篇文章主要介紹了springboot如何獲取applicationContext?servletContext問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
使用restTemplate.postForEntity()的問題
這篇文章主要介紹了使用restTemplate.postForEntity()的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09

