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 #设置默认的数据源或者数据源组,默认值即为 masterstrict: false # 设置严格模式,当数据源找不到时,是否抛出异常,默认为false不抛出datasource:master: # 主库type: com.alibaba.druid.pool.DruidDataSourcedriver-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=trueusername: youlaipassword: 123456slave: # 从库type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=trueusername: rootpassword: 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 {@Autowiredprivate SysUserMapper userMapper;private final Long userId = 1L;/*** 测试注解方式切换数据源*/@Testvoid 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*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}
4. 配置yml文件
spring:datasource:# 配置第一个数据源db1:url: jdbc:mysql://localhost:3306/db1username: user1password: pass1driver-class-name: com.mysql.cj.jdbc.Driver# 配置第二个数据源db2:url: jdbc:mysql://localhost:3306/db2username: user2password: pass2driver-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();}//数据源事务配置@Beanpublic 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);}
}
8. 在服务层 测试使用
最后,在你的服务层方法上使用 @DataSource
注解来指定要使用的数据源。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class MyService {@Autowiredprivate MyMapper myMapper; // 假设MyMapper是一个Mapper接口// 使用第一个数据源@DataSource(value = DataSourceType.DB1)@Transactionalpublic void useDb1() {// 执行数据库操作myMapper.someMethod();}// 使用第二个数据源@DataSource(value = DataSourceType.DB2)@Transactionalpublic 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启动器、数据库驱动等。例如:
<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 配置文件
# 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%2B8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
3. DataSourceConfig1.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
类似,但它是为第二个数据源配置的。
// 使用@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接口的服务类
// 使用@Service注解表明这是一个服务类
@Service
public class MyService {// 注入第一个数据源的Mapper接口@Autowiredprivate Db1Mapper db1Mapper;// 注入第二个数据源的Mapper接口@Autowiredprivate Db2Mapper db2Mapper;// 使用第一个数据源的方法public void useDb1() {// 调用db1Mapper的方法执行数据库操作}// 使用第二个数据源的方法public void useDb2() {// 调用db2Mapper的方法执行数据库操作}
}
总结
通过上述配置,您可以在Spring Boot项目中配置多个数据源,并通过不同的Mapper接口来操作这些数据源。
每个数据源都有自己的配置类、数据源对象、SqlSessionFactory对象和Mapper接口。
这样,您就可以在同一个项目中方便地访问多个数据库了。
参考文章
【1】Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换