SpringBoot多数据源动态切换
附gitee工程地址
https://gitee.com/xuanzijin/test-muti-datasource.git (除了数据库名字和下面文章对不上,实际上是一样的代码)
应用场景
在一个后台系统中连接多个数据库,并实现在不同的接口选择使用不同的数据源。
准备工作
创建3个数据库(比如:test1,test2,test3),分别创建一张简单的user表,分别插入明显不同的数据,因为后面会用三个接口分别查这三个数据库的user表;具体建库建表语句略。具体User实体类,Service,Dao略,需要各位自行创建。
maven依赖
lombok和mybatis-plus不会使用的话,去掉也没关系;使用lombok主要是可以使用@Data省掉写get/set方法
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<mybatisplus.version>3.0.7.1</mybatisplus.version>
<mysql.version>5.1.38</mysql.version>
<druid.version>1.1.13</druid.version>
<lombok.version>1.18.4</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
application.yml
# Tomcat
server:
tomcat:
uri-encoding: UTF-8
max-threads: 1000
min-spare-threads: 30
port: 8080
connection-timeout: 5000ms
servlet:
context-path: /
#mybatis
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: entity
global-config:
#数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
#字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
field-strategy: NOT_NULL
banner: false
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
##多数据源的配置
dynamic:
datasource:
slave1:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
slave2:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test3?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
@DataSource注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
String value() default "";
}
DynamicContextHolder多数据源上下文
public class DynamicContextHolder {
@SuppressWarnings("unchecked")
private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = new ThreadLocal() {
@Override
protected Object initialValue() {
return new ArrayDeque();
}
};
/**
* 获得当前线程数据源
*
* @return 数据源名称
*/
public static String peek() {
return CONTEXT_HOLDER.get().peek();
}
/**
* 设置当前线程数据源
*
* @param dataSource 数据源名称
*/
public static void push(String dataSource) {
CONTEXT_HOLDER.get().push(dataSource);
}
/**
* 清空当前线程数据源
*/
public static void poll() {
Deque<String> deque = CONTEXT_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
CONTEXT_HOLDER.remove();
}
}
}
DynamicDataSource继承AbstractRoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicContextHolder.peek();
}
}
DynamicDataSourceConfig动态数据源配置类
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import test.datasource.properties.DataSourceProperties;
import test.datasource.properties.DynamicDataSourceProperties;
import java.util.HashMap;
import java.util.Map;
/**
* 配置多数据源
*
*/
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceConfig {
@Autowired
private DynamicDataSourceProperties properties;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(getDynamicDataSource());
//默认数据源
HikariDataSource defaultDataSource = DynamicDataSourceFactory.buildDataSource(dataSourceProperties);
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
return dynamicDataSource;
}
private Map<Object, Object> getDynamicDataSource(){
Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource();
Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
dataSourcePropertiesMap.forEach((k, v) -> {
HikariDataSource druidDataSource = DynamicDataSourceFactory.buildDataSource(v);
targetDataSources.put(k, druidDataSource);
});
return targetDataSources;
}
}
DynamicDataSourceFactory生成数据源
使用springBoot默认的hikari数据库连接池
import com.zaxxer.hikari.HikariDataSource;
import test.datasource.properties.DataSourceProperties;
public class DynamicDataSourceFactory {
public static HikariDataSource buildDataSource(DataSourceProperties properties) {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setDriverClassName(properties.getDriverClassName());
hikariDataSource.setJdbcUrl(properties.getUrl());
hikariDataSource.setUsername(properties.getUsername());
hikariDataSource.setPassword(properties.getPassword());
return hikariDataSource;
}
}
DataSourceProperties数据源参数封装类
@Data
public class DataSourceProperties {
private String driverClassName;
private String url;
private String username;
private String password;
}
DynamicDataSourceProperties动态数据源参数封装类
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 多数据源属性
*
*/
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceProperties {
private Map<String, DataSourceProperties> datasource = new LinkedHashMap<>();
public Map<String, DataSourceProperties> getDatasource() {
return datasource;
}
public void setDatasource(Map<String, DataSourceProperties> datasource) {
this.datasource = datasource;
}
}
写一个controller测试
其中调用service的page方法是mybatis-plus自带的方法,不用mybatis-plus的话需要自己去写一个查询的方法。
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import test.datasource.annotation.DataSource;
import test.entity.User;
import test.service.IUserService;
@RestController
public class TestController {
@Autowired
private IUserService userService;
@GetMapping("/master")
public IPage<User> master(){
return userService.page(new Page<User>());
}
@DataSource("slave1")
@GetMapping("/slave1")
public IPage<User> slave1(){
return userService.page(new Page<User>());
}
@DataSource("slave2")
@GetMapping("/slave2")
public IPage<User> slave2(){
return userService.page(new Page<User>());
}
}
User实体类,Service,Dao略。
分别调用controller的三个接口,可以发现返回的数据确实是分别从三个不同的数据库获取的,实现成功!