Spring Boot 多数据源配置详解

介绍

在现代企业应用中,可能会涉及到多个数据库的使用,如不同模块或服务使用不同的数据库,或者在同一应用中同时连接多个数据库以处理不同的业务需求。为了应对这种需求,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

以上配置定义了两个独立的数据源 DevOpsHNSX,并为每个数据源指定了 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.propertiesapplication.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();
    }
}

示例场景

假设有两个数据源 DevOpsHNSX,分别用于管理不同的业务数据。根据业务请求的不同,应用需要从不同的数据源中获取数据。

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 是线程隔离的,但在高并发环境下,线程池中的线程可能会被重复使用。如果某个线程未清除数据源标识,该线程在被重复利用时仍会持有上一次操作的数据源设置,导致数据源配置错误,最终引发难以调试的业务错误。

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值