SpringBoot教程(二十五) | SpringBoot配置多个数据源
- 前言
- 方式一:使用dynamic-datasource-spring-boot-starter
- 引入maven依赖
- 配置数据源
- 动态切换数据源实战
- 方式二:使用AbstractRoutingDataSource
- 1. 创建数据源枚举类
- 2. 创建数据源上下文持有者
- 3. 创建自定义的 AbstractRoutingDataSource
- 4. 配置yml文件
- 5. 配置数据源
- HikariCP 版本
- Druid 版本
- 6. 自定义多数据源切换注解
- 7. 使用 AOP 或拦截器设置数据源键
- 8. 在服务层 测试使用
- 总结
- 方式三:分包方式
- 1. 添加依赖
- 2. application.yml 配置文件
- 3. DataSourceConfig1.java 配置类
- 4. DataSourceConfig2.java 配置类
- 5. Mapper接口和XML文件
- 6. 使用Mapper接口的服务类
- 总结
前言
SpringBoot配置多数据源指的是在一个Spring Boot项目中配置和连接到多个数据库实例的能力。
这种架构允许应用程序根据不同的业务需求、数据类型或性能要求,与多个独立的数据库环境交互。
在实现上,每个数据源都有自己的连接池、事务管理和数据访问对象。
方式一:使用dynamic-datasource-spring-boot-starter
引入maven依赖
Spring Boot 3 区别其他版本,使用的是dynamic-datasource-spring-boot3-starter ,
其他版本的 Spring Boot 使用 dynamic-datasource-spring-boot-starter , 除了依赖区别,其他配置和使用方式新老版本无差别。
Spring Boot 3
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.2.0</version>
</dependency>
Spring 1.5.x 及 Spring 2.x.x
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>
配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为 master
strict: false # 设置严格模式,当数据源找不到时,是否抛出异常,默认为false不抛出
datasource:
master: # 主库
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
url: jdbc:mysql://www.youlai.tech:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
username: youlai
password: 123456
slave: # 从库
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
username: root
password: 123456
动态切换数据源实战
注解切换数据源
@DS注解是 dynamic-datasource-spring-boot-starter提供的
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
/**
* 主库查询
*/
@DS("master")
@Select("select * from sys_user where id = #{userId}")
SysUser getUserFromMaster(Long userId);
/**
* 从库查询
*/
@DS("slave")
@Select("select * from sys_user where id = #{userId}")
SysUser getUserFromSlave(Long userId);
单元测试类
package com.youlai.system.mapper;
import com.youlai.system.model.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class SysUserMapperTest {
@Autowired
private SysUserMapper userMapper;
private final Long userId = 1L;
/**
* 测试注解方式切换数据源
*/
@Test
void testSwitchDataSourceByAnnotation() {
SysUser masterUser = userMapper.getUserFromMaster(userId);
log.info("用户ID:{} 主库姓名:{}", userId, masterUser.getNickname());
SysUser slaveUser = userMapper.getUserFromSlave(userId);
log.info("用户ID:{} 从库姓名:{}", userId, slaveUser.getNickname());
}
}
测试结果
方式二:使用AbstractRoutingDataSource
AbstractRoutingDataSource
是 Spring 框架中用于实现多数据源路由的一个抽象类。
它允许你在运行时根据某种键(通常是线程本地变量)动态地选择数据源。
1. 创建数据源枚举类
/**
* 数据源
*/
public enum DataSourceType {
/**
* 数据源1
* */
DB1,
/**
* 数据源2
* */
DB2
}
2. 创建数据源上下文持有者
首先,你需要一个类来持有当前线程的数据源键(DataSource Type)。这通常是通过 ThreadLocal
实现的。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*/
public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType) {
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
3. 创建自定义的 AbstractRoutingDataSource
接下来,你需要扩展 AbstractRoutingDataSource
并重写 determineCurrentLookupKey
方法来返回当前线程的数据源键。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Spring的AbstractRoutingDataSource抽象类,实现动态数据源(他的作用就是动态切换数据源)
* AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心,
* 这里对该方法进行Override。【上下文DynamicDataSourceContextHolder为一线程安全的ThreadLocal】
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 取得当前使用哪个数据源
* @return dbTypeEnum
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
4. 配置yml文件
spring:
datasource:
# 配置第一个数据源
db1:
url: jdbc:mysql://localhost:3306/db1
username: user1
password: pass1
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置第二个数据源
db2:
url: jdbc:mysql://localhost:3306/db2
username: user2
password: pass2
driver-class-name: com.mysql.cj.jdbc.Driver
5. 配置数据源
在你的配置类中,你需要配置多个数据源,并将它们添加到 multipleDataSource
中。
HikariCP 版本
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.springbootfull.quartztest.datasource.DynamicDataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
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 org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置
*/
@Configuration
public class DataSourceConfig {
/**
* 创建第一个数据源
*
* @return dataSource
*/
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 创建第二个数据源
*
* @return dataSource
*/
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源配置
* 多个相同类型的 Bean,那么就会出现歧义,需要使用@Qualifier
* @Qualifier 是按名称来查找和注入 Bean 的
* @return dataSource
*/
@Primary
@Bean("multipleDataSource")
public DataSource multipleDataSource(@Qualifier("dataSource1") DataSource db1,
@Qualifier("dataSource2") DataSource db2) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DataSourceType.DB1, db1);
dataSources.put(DataSourceType.DB2, db2);
dynamicDataSource.setTargetDataSources(dataSources);
//默认数据源
dynamicDataSource.setDefaultTargetDataSource(db1);
return dynamicDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("multipleDataSource") DataSource multipleDataSource) throws Exception {
// 导入mybatissqlsession配置
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
// 指明数据源
sessionFactory.setDataSource(multipleDataSource);
// 设置mapper.xml的位置路径
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml");
sessionFactory.setMapperLocations(resources);
//指明实体扫描(多个package用逗号或者分号分隔)
//sessionFactory.setTypeAliasesPackage("com.szylt.projects.project.entity");
// 导入mybatis配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
//数据源事务配置
@Bean
public PlatformTransactionManager transactionManager(DataSource multipleDataSource) {
return new DataSourceTransactionManager(multipleDataSource);
}
}
Druid 版本
需要先引入 Druid 依赖,这里使用的是 Druid 官方的 Starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
然后,在properties配置文件中 为每个数据源配置DruidDataSour数据库连接池
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSour
接着把一下DataSourceConfig 类里面的 按下面的操作,就好了
- DataSource 对象 换成 DruidDataSource 对象
- DataSourceBuilder 对象 换成 DruidDataSourceBuilder 对象
6. 自定义多数据源切换注解
import com.example.springbootfull.quartztest.enums.DataSourceType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义多数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
* 默认为DB1
*/
public DataSourceType value() default DataSourceType.DB1;
}
7. 使用 AOP 或拦截器设置数据源键
为了在使用时能够动态地切换数据源,你需要在执行数据库操作之前设置数据源键。
这通常是通过 AOP(面向切面编程)或拦截器来实现的。
需要引入aop依赖
<!--spring切面aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
以下是一个使用 AOP 的示例:
以下这个位置,记得 替换成你项目中 自定义注解 DataSource 所在的具体位置
@Pointcut(“@annotation(com.example.xx.xx.DataSource) || @within(com.example.xx.xx.DataSource)”)
import java.util.Objects;
import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.datasource.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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 多数据源处理
*
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
//此处替换成你项目中 自定义注解 DataSource 所在的具体位置
@Pointcut("@annotation(com.example.xx.xx.DataSource)"
+ "|| @within(com.example.xx.xx.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}