介绍
在现代企业应用中,可能会涉及到多个数据库的使用,如不同模块或服务使用不同的数据库,或者在同一应用中同时连接多个数据库以处理不同的业务需求。为了应对这种需求,Spring Boot 提供了灵活的多数据源配置机制。本文将详细介绍如何在 Spring Boot 中配置多数据源,并通过代码示例来展示具体的实现步骤。
依赖配置
首先,确保项目中引入了必要的依赖。以使用 SQL Server 数据库为例,可以通过以下依赖来启动 JDBC 支持:
<!--mybatis-plus 持久层-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--SQL Server 数据库JDBC驱动程序-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>9.2.1.jre8</version>
</dependency>
<!--MySQL 数据库JDBC驱动程序-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
多数据源配置
在 Spring Boot 中配置多数据源的关键在于为每个数据源创建独立的 DataSource
实例,并通过自定义的动态数据源类来管理和切换不同的数据源。
配置文件设置
首先,在 application.properties
文件中为每个数据源定义相关的配置参数:
##以下是SQLserver配置
# DevOps 数据源配置
spring.datasource.devops.jdbc-url=jdbc:sqlserver://localhost:1433;databaseName=DevOps;
spring.datasource.devops.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.devops.username=sa
spring.datasource.devops.password=123456
# HNSX 数据源配置
spring.datasource.hnsx.jdbc-url=jdbc:sqlserver://localhost:1433;databaseName=HNSX;
spring.datasource.hnsx.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.hnsx.username=sa
spring.datasource.hnsx.password=123456
##以下是MySQL配置
# DevOps 数据源配置
spring.datasource.devops.jdbc-url=jdbc:mysql://localhost:3306/DevOps
spring.datasource.devops.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.devops.username=root
spring.datasource.devops.password=123456
# HNSX 数据源配置
spring.datasource.hnsx.jdbc-url=jdbc:mysql://localhost:3306/HNSX
spring.datasource.hnsx.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hnsx.username=root
spring.datasource.hnsx.password=123456
以上配置定义了两个独立的数据源 DevOps
和 HNSX
,并为每个数据源指定了 JDBC URL、驱动类、用户名和密码。
配置多数据源
接下来,通过配置类来创建多个 DataSource
实例,并将这些数据源注册到 DynamicDataSource
中:
@Data
@Configuration
public class DataSourceConfig {
// 创建 DevOps 数据源
@Bean(name = "DevOps")
@ConfigurationProperties(prefix = "spring.datasource.devops")
public DataSource dataSourceDevOps() {
return DataSourceBuilder.create().build();
}
// 创建 HNSX 数据源
@Bean(name = "HNSX")
@ConfigurationProperties(prefix = "spring.datasource.hnsx")
public DataSource dataSourceHNSX() {
return DataSourceBuilder.create().build();
}
// 配置动态数据源
@Bean
@Primary // @Primary 注解指定一个默认数据源。
@DependsOn({"DevOps", "HNSX"}) // 指定加载顺序
public DynamicDataSource dataSource(@Qualifier("HNSX") DataSource dataSourceHNSX,
@Qualifier("DevOps") DataSource dataSourceDevOps) {
// 多个数据源注册到 targetDataSources 中
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("DevOps", dataSourceDevOps);
targetDataSources.put("HNSX", dataSourceHNSX);
DynamicDataSource dataSource = new DynamicDataSource();
// 设置所有目标数据源
dataSource.setTargetDataSources(targetDataSources);
// 设置默认的数据源,在没有明确指定数据源时使用。
dataSource.setDefaultTargetDataSource(dataSourceDevOps);
return dataSource;
}
}
@ConfigurationProperties
注解用于从配置文件(如 application.properties
或 application.yml
)中加载属性值并将其映射到 Java 类的字段中。这个注解必须与配置文件中对应的数据源前缀一致,例如 prefix = "spring.datasource.devops"
表示将配置文件中以 spring.datasource.devops
开头的所有属性映射到 DataSource
实例的相应属性中。因此,如果配置文件中是以 spring.datasource.devops
为前缀配置的数据库连接信息,Spring Boot 会自动将这些配置值注入到 dataSourceDevOps
方法中创建的 DataSource
实例中。
自定义动态数据源实现类
在多数据源配置中,需要一个机制来根据业务逻辑动态选择并切换不同的数据源。为此,可以通过扩展 AbstractRoutingDataSource
类来自定义动态数据源实现类:
public class DynamicDataSource extends AbstractRoutingDataSource {
// 存储当前线程的数据源标识,确保每个线程都有自己的独立副本
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源
public static void setDataSource(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
// 清除数据源
public static void clearDataSource() {
contextHolder.remove();
}
// 确定当前数据源
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
}
示例场景
假设有两个数据源 DevOps
和 HNSX
,分别用于管理不同的业务数据。根据业务请求的不同,应用需要从不同的数据源中获取数据。
1. 创建 User
实体类
public class User {
private Long id;
private String name;
private String email;
// Getters and Setters
}
2. 创建一个服务接口 UserService
public interface UserService {
List<User> getUsersFromDevOps();
List<User> getUsersFromHNSX();
}
3. 实现 UserService
接口
@Service
public class UserServiceImpl implements UserService {
//简化数据库操作的工具类
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<User> getUsersFromDevOps() {
// 切换到 DevOps 数据源
DynamicDataSource.setDataSource("DevOps");
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
//清除数据源
DynamicDataSource.clearDataSource();
}
@Override
public List<User> getUsersFromHNSX() {
// 切换到 HNSX 数据源
DynamicDataSource.setDataSource("HNSX");
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
//清除数据源
DynamicDataSource.clearDataSource();
}
}
在每次数据库操作之前,通过调用 DynamicDataSource.setDataSource("DevOps")
或 DynamicDataSource.setDataSource("HNSX")
方法,将当前线程的数据源标识设置为对应的数据源名称(如 "DevOps" 或 "HNSX")。这个标识会存储在 ThreadLocal
中,以确保每个线程在执行数据库操作时使用正确的数据源。
为了避免数据源的错误延续,每次数据库操作完成后,必须调用 DynamicDataSource.clearDataSource()
方法来清除当前线程的数据源设置。这个方法会从 ThreadLocal
中移除当前线程的数据源标识,以确保后续的数据库操作不会错误地使用前一次操作的数据库连接。
如果不清除数据源可能引发的后果
-
数据源错误延续:如果某个线程在一次操作中设置了数据源
DevOps
,但没有清除该数据源,那么在下一次该线程执行数据库操作时,仍会使用DevOps
数据源,即使在新的业务逻辑中本应该使用HNSX
数据源。这会导致数据不一致或访问错误的数据。 -
线程间数据源污染:虽然
ThreadLocal
是线程隔离的,但在高并发环境下,线程池中的线程可能会被重复使用。如果某个线程未清除数据源标识,该线程在被重复利用时仍会持有上一次操作的数据源设置,导致数据源配置错误,最终引发难以调试的业务错误。