SpringBoot教程(二十五) | SpringBoot配置多个数据源
- 前言
- 方式一:使用dynamic-datasource-spring-boot-starter
- 方式二:使用AbstractRoutingDataSource
-
- [1. 创建数据源枚举类](#1. 创建数据源枚举类)
- [2. 创建数据源上下文持有者](#2. 创建数据源上下文持有者)
- [3. 创建自定义的 AbstractRoutingDataSource](#3. 创建自定义的 AbstractRoutingDataSource)
- [4. 配置yml文件](#4. 配置yml文件)
- [5. 配置数据源](#5. 配置数据源)
-
- [HikariCP 版本](#HikariCP 版本)
- [Druid 版本](#Druid 版本)
- [6. 自定义多数据源切换注解](#6. 自定义多数据源切换注解)
- [7. 使用 AOP 或拦截器设置数据源键](#7. 使用 AOP 或拦截器设置数据源键)
- [8. 在服务层 测试使用](#8. 在服务层 测试使用)
- 总结
- 方式三:分包方式
-
- [1. 添加依赖](#1. 添加依赖)
- [2. application.yml 配置文件](#2. application.yml 配置文件)
- [3. DataSourceConfig1.java 配置类](#3. DataSourceConfig1.java 配置类)
- [4. DataSourceConfig2.java 配置类](#4. DataSourceConfig2.java 配置类)
- [5. Mapper接口和XML文件](#5. Mapper接口和XML文件)
- [6. 使用Mapper接口的服务类](#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
xml
<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
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>
配置数据源
yaml
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可以为组名也可以为具体某个库的名称 |
java
/**
* 主库查询
*/
@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);
单元测试类
java
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. 创建数据源枚举类
java
/**
* 数据源
*/
public enum DataSourceType {
/**
* 数据源1
* */
DB1,
/**
* 数据源2
* */
DB2
}
2. 创建数据源上下文持有者
首先,你需要一个类来持有当前线程的数据源键(DataSource Type)。这通常是通过 ThreadLocal
实现的。
java
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
方法来返回当前线程的数据源键。
java
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文件
yaml
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 版本
java
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
bash
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
然后,在properties配置文件中 为每个数据源配置DruidDataSour数据库连接池
bash
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSour
接着把一下DataSourceConfig 类里面的 按下面的操作,就好了
- DataSource 对象 换成 DruidDataSource 对象
- DataSourceBuilder 对象 换成 DruidDataSourceBuilder 对象
6. 自定义多数据源切换注解
java
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依赖
xml
<!--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)")
java
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);
}
}
8. 在服务层 测试使用
最后,在你的服务层方法上使用 @DataSource
注解来指定要使用的数据源。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
@Autowired
private MyMapper myMapper; // 假设MyMapper是一个Mapper接口
// 使用第一个数据源
@DataSource(value = DataSourceType.DB1)
@Transactional
public void useDb1() {
// 执行数据库操作
myMapper.someMethod();
}
// 使用第二个数据源
@DataSource(value = DataSourceType.DB2)
@Transactional
public void useDb2() {
// 执行数据库操作
myMapper.anotherMethod();
}
}
总结
通过上述步骤,你已经成功地在 Spring Boot 项目中配置了基于 AbstractRoutingDataSource
的多数据源支持。
当你调用服务层的方法时,AOP 拦截器会根据 @DataSource
注解的值设置当前线程的数据源键,从而动态地选择数据源。
方式三:分包方式
- 配置多个数据源 :在Spring Boot的配置文件中(如
application.properties
或application.yml
),配置多个数据源的信息,包括URL、用户名、密码等。- 创建数据源配置类 :为每个数据源创建一个配置类,使用
@Configuration
注解标注,并在类中定义数据源(DataSource
)、事务管理器(TransactionManager
)以及SqlSessionFactory
等Bean。- 指定Mapper扫描路径 :使用
@MapperScan
注解指定每个数据源对应的Mapper扫描路径,以便Spring Boot能够正确地加载和注册Mapper接口。
1. 添加依赖
首先,在pom.xml文件中添加必要的依赖,包括Spring Boot的Web启动器、MyBatis的Spring Boot启动器、数据库驱动等。例如:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>版本号</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 其他依赖 -->
</dependencies>
2. application.yml 配置文件
yaml
# Spring Boot应用程序的配置文件
spring:
datasource:
# 第一个数据源的配置
db1:
jdbc-url: jdbc:mysql://localhost:3306/test1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
# 数据库用户名
username: root
# 数据库密码
password: 123456
# 数据库驱动类名
driver-class-name: com.mysql.cj.jdbc.Driver
# 第二个数据源的配置
db2:
jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
3. DataSourceConfig1.java 配置类
java
// 使用@Configuration注解表明这是一个配置类
@Configuration
// 使用@MapperScan注解指定Mapper接口所在的包,并指定SqlSessionFactory的引用
@MapperScan(basePackages = "com.example.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceConfig1 {
// 使用@Primary注解表明这是默认的数据源(如果有多个数据源时)
// 使用@Bean注解表明这是一个Spring管理的bean,name属性指定bean的名称
// 使用@ConfigurationProperties注解读取application.yml中的配置
@Primary
@Bean(name = "db1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource getDb1DataSource() {
// 创建并返回数据源对象
return DataSourceBuilder.create().build();
}
// 创建一个SqlSessionFactory对象,用于MyBatis的SQL会话管理
@Primary
@Bean(name = "db1SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sessionFactoryBean.setDataSource(dataSource);
// 设置Mapper XML文件的位置
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/db1/*.xml"));
// 返回SqlSessionFactory对象
return sessionFactoryBean.getObject();
}
}
4. DataSourceConfig2.java 配置类
这个类与DataSourceConfig1.java
类似,但它是为第二个数据源配置的。
java
// 使用@Configuration注解表明这是一个配置类
@Configuration
// 使用@MapperScan注解指定第二个数据源对应的Mapper接口所在的包
@MapperScan(basePackages = "com.example.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class DataSourceConfig2 {
// 创建一个名为db2DataSource的数据源bean
@Bean(name = "db2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource getDb2DataSource() {
// 创建并返回数据源对象
return DataSourceBuilder.create().build();
}
// 创建一个名为db2SqlSessionFactory的SqlSessionFactory对象
@Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory db2SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sessionFactoryBean.setDataSource(dataSource);
// 设置第二个数据源对应的Mapper XML文件的位置
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/db2/*.xml"));
// 返回SqlSessionFactory对象
return sessionFactoryBean.getObject();
}
}
5. Mapper接口和XML文件
在com.example.mapper.db1
和com.example.mapper.db2
包下分别创建Mapper接口,这些接口定义了操作数据库的方法。
同时,在resources/mapper/db1
和resources/mapper/db2
目录下创建对应的XML文件,这些文件包含了SQL语句和映射规则。
6. 使用Mapper接口的服务类
java
// 使用@Service注解表明这是一个服务类
@Service
public class MyService {
// 注入第一个数据源的Mapper接口
@Autowired
private Db1Mapper db1Mapper;
// 注入第二个数据源的Mapper接口
@Autowired
private Db2Mapper db2Mapper;
// 使用第一个数据源的方法
public void useDb1() {
// 调用db1Mapper的方法执行数据库操作
}
// 使用第二个数据源的方法
public void useDb2() {
// 调用db2Mapper的方法执行数据库操作
}
}
总结
通过上述配置,您可以在Spring Boot项目中配置多个数据源,并通过不同的Mapper接口来操作这些数据源。
每个数据源都有自己的配置类、数据源对象、SqlSessionFactory对象和Mapper接口。
这样,您就可以在同一个项目中方便地访问多个数据库了。
参考文章