Spring Boot 多数据源配置
第一种方式: AbstractRoutingDataSource
1.1. 手动切换数据源
application.properties
# Order
# 如果用 Druid 作为数据源,应该用 url 属性,而不是 jdbc-url
spring.datasource.order.jdbc-url=jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.order.username=root
spring.datasource.order.password=123456
spring.datasource.order.driver-class-name=com.mysql.cj.jdbc.Driver
# Stock
spring.datasource.stock.jdbc-url=jdbc:mysql://localhost:3306/stock?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.stock.username=root
spring.datasource.stock.password=123456
spring.datasource.stock.driver-class-name=com.mysql.cj.jdbc.Driver
# Account
spring.datasource.account.jdbc-url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.account.username=root
spring.datasource.account.password=123456
spring.datasource.account.driver-class-name=com.mysql.cj.jdbc.Driver
配置数据源
DataSourceConfig.java
package com.cjs.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.zaxxer.hikari.HikariDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
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.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
<span class="hljs-meta">@Bean("orderDataSource")</span>
<span class="hljs-meta">@ConfigurationProperties(prefix = "spring.datasource.order")</span>
<span class="hljs-keyword">public</span> DataSource <span class="hljs-title function_">orderDataSource</span><span class="hljs-params">()</span> {
// return new HikariDataSource();
// return new DruidDataSource();
return DataSourceBuilder.create().build();
}
<span class="hljs-meta">@Bean("accountDataSource")</span>
<span class="hljs-meta">@ConfigurationProperties(prefix = "spring.datasource.account")</span>
<span class="hljs-keyword">public</span> DataSource <span class="hljs-title function_">accountDataSource</span><span class="hljs-params">()</span> {
// return new HikariDataSource();
// return new DruidDataSource();
return DataSourceBuilder.create().build();
}
<span class="hljs-meta">@Bean("stockDataSource")</span>
<span class="hljs-meta">@ConfigurationProperties(prefix = "spring.datasource.stock")</span>
<span class="hljs-keyword">public</span> DataSource <span class="hljs-title function_">stockDataSource</span><span class="hljs-params">()</span> {
// return new HikariDataSource();
// return new DruidDataSource();
return DataSourceBuilder.create().build();
}
<span class="hljs-meta">@Primary</span>
<span class="hljs-meta">@Bean("dynamicDataSource")</span>
<span class="hljs-keyword">public</span> DataSource <span class="hljs-title function_">dynamicDataSource</span><span class="hljs-params">(<span class="hljs-meta">@Qualifier("orderDataSource")</span> DataSource orderDataSource,
<span class="hljs-meta">@Qualifier("accountDataSource")</span> DataSource accountDataSource,
<span class="hljs-meta">@Qualifier("stockDataSource")</span> DataSource stockDataSource)</span> {
Map<Object, Object> dataSourceMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span><>(<span class="hljs-number">3</span>);
dataSourceMap.put(DataSourceKey.ORDER.name(), orderDataSource);
dataSourceMap.put(DataSourceKey.STOCK.name(), stockDataSource);
dataSourceMap.put(DataSourceKey.ACCOUNT.name(), accountDataSource);
<span class="hljs-type">DynamicRoutingDataSource</span> <span class="hljs-variable">dynamicRoutingDataSource</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">DynamicRoutingDataSource</span>();
dynamicRoutingDataSource.setDefaultTargetDataSource(orderDataSource);
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
<span class="hljs-keyword">return</span> dynamicRoutingDataSource;
}
<span class="hljs-comment">/* https://baomidou.com/pages/3b5af0/ */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-keyword">public</span> MybatisSqlSessionFactoryBean <span class="hljs-title function_">sqlSessionFactoryBean</span><span class="hljs-params">(<span class="hljs-meta">@Qualifier("dynamicDataSource")</span> DataSource dataSource)</span> {
<span class="hljs-type">MybatisSqlSessionFactoryBean</span> <span class="hljs-variable">sqlSessionFactoryBean</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MybatisSqlSessionFactoryBean</span>();
sqlSessionFactoryBean.setDataSource(dataSource);
// sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml"));
return sqlSessionFactoryBean;
}
}
由于是 MyBatsi-Plus,所以配的是 MybatisSqlSessionFactoryBean,如果是 MyBatis,则应该是 SqlSessionFactoryBean
DataSourceKey.java
package com.cjs.example.config;
public enum DataSourceKey {
/**
* Order data source key.
/
ORDER,
/*
* Stock data source key.
/
STOCK,
/*
* Account data source key.
*/
ACCOUNT
}
DynamicDataSourceContextHolder.java
package com.cjs.example.config;
public class DynamicDataSourceContextHolder {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.ORDER::name);
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setDataSourceKey</span><span class="hljs-params">(DataSourceKey key)</span> {
CONTEXT_HOLDER.set(key.name());
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">getDataSourceKey</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> CONTEXT_HOLDER.get();
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">clearDataSourceKey</span><span class="hljs-params">()</span> {
CONTEXT_HOLDER.remove();
}
}
DynamicRoutingDataSource.java
package com.cjs.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
log.info("当前数据源 [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
好了,配置完以后,在操作数据库之前,先设置用哪个数据源即可,就像下面这样:
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);
举个例子:
package com.cjs.example;
import com.cjs.example.account.entity.Account;
import com.cjs.example.account.service.IAccountService;
import com.cjs.example.config.DataSourceKey;
import com.cjs.example.config.DynamicDataSourceContextHolder;
import com.cjs.example.order.entity.Order;
import com.cjs.example.order.service.IOrderService;
import com.cjs.example.stock.entity.Stock;
import com.cjs.example.stock.service.IStockService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
@SpringBootTest
public class Demo1122ApplicationTests {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> IOrderService orderService;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> IAccountService accountService;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> IStockService stockService;
<span class="hljs-meta">@Test</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doBusiness</span><span class="hljs-params">()</span> {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ORDER);
<span class="hljs-type">Order</span> <span class="hljs-variable">order</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Order</span>();
order.setOrderNo(<span class="hljs-string">"123"</span>);
order.setUserId(<span class="hljs-string">"1"</span>);
order.setCommodityCode(<span class="hljs-string">"abc"</span>);
order.setCount(<span class="hljs-number">1</span>);
order.setAmount(<span class="hljs-keyword">new</span> <span class="hljs-title class_">BigDecimal</span>(<span class="hljs-string">"9.9"</span>));
orderService.save(order);
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.STOCK);
<span class="hljs-type">Stock</span> <span class="hljs-variable">stock</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Stock</span>();
stock.setId(<span class="hljs-number">1</span>);
stock.setCommodityCode(<span class="hljs-string">"abc"</span>);
stock.setName(<span class="hljs-string">"huawei"</span>);
stock.setCount(<span class="hljs-number">1</span>);
stockService.updateById(stock);
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);
<span class="hljs-type">Account</span> <span class="hljs-variable">account</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Account</span>();
account.setId(<span class="hljs-number">1</span>);
account.setUserId(<span class="hljs-string">"1"</span>);
account.setAmount(<span class="hljs-keyword">new</span> <span class="hljs-title class_">BigDecimal</span>(<span class="hljs-number">100</span>));
accountService.updateById(account);
}
}
这样写看起来确实有些麻烦,通常可能不会像这样在一个方法里操作多个数据库,就比如说假设这是一个管理后台,为了图省事把所有业务都写在这一个项目里,这个时候就需要配置多个数据源,各个数据库的业务互相没有关联,只是写在同一个项目中而已,这样的话如果每次都手动设置数据源太麻烦,可以定义一个 AOP 切面来自动切换数据源。
1.2. 自动切换数据源
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj
給刚才的代码升个级,利用 AOP 来拦截目标方法自动切换数据源
1、添加 @EnableAspectJAutoProxy 注解
package com.cjs.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy
@MapperScan("com.cjs.example.*.mapper")
@SpringBootApplication
public class Demo1122Application {
public static void main(String[] args) {
SpringApplication.run(Demo1122Application.class, args);
}
}
2、定义切面、切点、通知
package com.cjs.example.aop;
import com.cjs.example.config.DataSourceKey;
import com.cjs.example.config.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAdvice {
// @Pointcut("within(com.cjs.example.order..)")
@Pointcut("execution( com.cjs.example.order...(..))")
public void orderPointcut() {}
// @Pointcut("within(com.cjs.example.account..)")
@Pointcut("execution( com.cjs.example.account...(..))")
public void accountPointcut() {}
// @Pointcut("within(com.cjs.example.stock..)")
@Pointcut("execution( com.cjs.example.stock...(..))")
public void stockPointcut() {}
<span class="hljs-meta">@Around("orderPointcut()")</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">order</span><span class="hljs-params">(ProceedingJoinPoint pjp)</span> <span class="hljs-keyword">throws</span> Throwable {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ORDER);
<span class="hljs-type">Object</span> <span class="hljs-variable">retVal</span> <span class="hljs-operator">=</span> pjp.proceed();
DynamicDataSourceContextHolder.clearDataSourceKey();
<span class="hljs-keyword">return</span> retVal;
}
<span class="hljs-meta">@Around("accountPointcut()")</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">account</span><span class="hljs-params">(ProceedingJoinPoint pjp)</span> <span class="hljs-keyword">throws</span> Throwable {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);
<span class="hljs-type">Object</span> <span class="hljs-variable">retVal</span> <span class="hljs-operator">=</span> pjp.proceed();
DynamicDataSourceContextHolder.clearDataSourceKey();
<span class="hljs-keyword">return</span> retVal;
}
<span class="hljs-meta">@Around("stockPointcut()")</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">stock</span><span class="hljs-params">(ProceedingJoinPoint pjp)</span> <span class="hljs-keyword">throws</span> Throwable {
DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.STOCK);
<span class="hljs-type">Object</span> <span class="hljs-variable">retVal</span> <span class="hljs-operator">=</span> pjp.proceed();
DynamicDataSourceContextHolder.clearDataSourceKey();
<span class="hljs-keyword">return</span> retVal;
}
}
现在就不用每次调用 service 方法前手动设置数据源了
工程结构
第二种方式:dynamic-datasource-spring-boot-starter
功能很强大,支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式
https://github.com/baomidou/dynamic-datasource-spring-boot-starter
1、引入 dynamic-datasource-spring-boot-starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
2、配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组, 默认值即为 master
strict: false #严格匹配数据源, 默认 false. true 未匹配到指定数据源时抛异常,false 使用默认数据源
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0 开始支持 SPI 可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx) # 内置加密, 使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
#...... 省略
#以上会配置一个默认库 master,一个组 slave 下有两个子库 slave_1,slave_2
主从配置,读写分离
# 多主多从 纯粹多库(记得设置 primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
改造一下前面的例子
spring.datasource.dynamic.primary=order
# Order
spring.datasource.dynamic.datasource.order.url=jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.dynamic.datasource.order.username=root
spring.datasource.dynamic.datasource.order.password=123456
spring.datasource.dynamic.datasource.order.driver-class-name=com.mysql.cj.jdbc.Driver
# Stock
spring.datasource.dynamic.datasource.stock.url=jdbc:mysql://localhost:3306/stock?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.dynamic.datasource.stock.username=root
spring.datasource.dynamic.datasource.stock.password=123456
spring.datasource.dynamic.datasource.stock.driver-class-name=com.mysql.cj.jdbc.Driver
# Account
spring.datasource.dynamic.datasource.account.url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
spring.datasource.dynamic.datasource.account.username=root
spring.datasource.dynamic.datasource.account.password=123456
spring.datasource.dynamic.datasource.account.driver-class-name=com.mysql.cj.jdbc.Driver
3、使用 @DS 切换数据源
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解
注解 | 结果 |
没有 @DS | 默认数据源 |
@DS("dsName") | dsName 可以为组名也可以为具体某个库的名称 |
package com.cjs.example.order.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.cjs.example.order.entity.Order;
import com.cjs.example.order.mapper.OrderMapper;
import com.cjs.example.order.service.IOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@DS("order")
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
}
package com.cjs.example.stock.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.cjs.example.stock.entity.Stock;
import com.cjs.example.stock.mapper.StockMapper;
import com.cjs.example.stock.service.IStockService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@DS("stock")
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements IStockService {
}
package com.cjs.example.account.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.cjs.example.account.entity.Account;
import com.cjs.example.account.mapper.AccountMapper;
import com.cjs.example.account.service.IAccountService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@DS("account")
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements IAccountService {
}
欢迎各位转载,但必须在文章页面中给出作者和原文链接!