java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot多数据源配置

SpringBoot项目中多数据源配置方法与使用场景

作者:冰糖心书房

在 Spring Boot 中配置多数据源是一个非常常见的需求,本文将为大家详细介绍两种主流的实现方式与使用场景,感兴趣的小伙伴可以跟随小编一起学习一下

在 Spring Boot 中配置多数据源是一个非常常见的需求,主要用于以下场景:

下面我将详细介绍两种主流的实现方式:

方案一:静态方式(按包路径隔离)

这种方式的核心思想是为每个数据源创建一套独立的配置(DataSource, SqlSessionFactory, TransactionManager),并使用 @MapperScan 注解扫描不同包路径下的 Mapper 接口,将它们绑定到对应的数据源上。

1. 添加依赖 (pom.xml)

确保有以下依赖。通常 Spring Boot Starter 会包含大部分。

<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- MyBatis-Plus Starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version> <!-- 请使用较新版本 -->
    </dependency>
    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Connection Pool (HikariCP is default) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

2. 配置文件 (application.yml)

为不同的数据源定义各自的连接信息,并用不同的前缀区分。

spring:
  datasource:
    # 主数据源配置 (master)
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/db_master?serverTimezone=UTC
      username: root
      password: your_password
      type: com.zaxxer.hikari.HikariDataSource # 指定连接池类型

    # 从数据源配置 (slave)
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3307/db_slave?serverTimezone=UTC
      username: root
      password: your_password
      type: com.zaxxer.hikari.HikariDataSource

3. 创建数据源配置类

为每个数据源创建一个 Java 配置类。

主数据源配置 (MasterDataSourceConfig.java)

package com.example.config.datasource;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;

@Configuration
// 扫描 Master 库的 Mapper 接口
@MapperScan(basePackages = "com.example.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    @Primary // 标记为主数据源
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "masterSqlSessionFactory")
    @Primary
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 如果有 XML 文件,指定位置
        // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "masterTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "masterSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

从数据源配置 (SlaveDataSourceConfig.java)

package com.example.config.datasource;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;

@Configuration
// 扫描 Slave 库的 Mapper 接口
@MapperScan(basePackages = "com.example.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class SlaveDataSourceConfig {

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "slaveTransactionManager")
    public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "slaveSqlSessionTemplate")
    public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

4. 创建 Mapper 接口

将不同数据源的 Mapper 接口放到各自的包下。

5. 使用

现在你可以在 Service 中直接注入并使用对应的 Mapper,Spring 会自动为它们关联正确的数据源。

@Service
public class MyService {
    @Autowired
    private UserMasterMapper userMasterMapper; // 操作 master 库

    @Autowired
    private OrderSlaveMapper orderSlaveMapper; // 操作 slave 库

    public void doSomething() {
        // ...
        userMasterMapper.insert(someUser); // 写入主库
        Order order = orderSlaveMapper.selectById(1); // 从从库读取
    }
}

优点:配置隔离,结构非常清晰,不会混淆。

缺点:如果一个 Service 方法需要同时操作两个库,代码会稍微复杂,且默认的 @Transactional 不能跨数据源生效。

方案二:动态方式(AOP + 自定义注解)

这种方式更灵活,适用于读写分离等需要在同一个 Service 中切换数据源的场景。

1. 配置文件 (application.yml)

与方案一相同。

2. 创建自定义注解

创建一个注解,用于标记方法应该使用哪个数据源。

package com.example.config.dynamic;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "master"; // 默认使用 master 数据源
}

3. 创建数据源上下文持有者

使用 ThreadLocal 来存储当前线程需要使用的数据源 Key。

package com.example.config.dynamic;

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }
}

4. 创建动态数据源类

继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法,从 DataSourceContextHolder 获取当前数据源 Key。

package com.example.config.dynamic;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}

5. 创建AOP切面

创建一个切面,拦截 @DataSource 注解,在方法执行前设置数据源 Key,在方法执行后清除它。

package com.example.config.dynamic;

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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
@Order(1) // 保证该AOP在@Transactional之前执行
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.config.dynamic.DataSource)")
    public void dsPointCut() {}

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        
        // 设置数据源
        if (dataSource != null) {
            DataSourceContextHolder.setDataSourceKey(dataSource.value());
        }

        try {
            return point.proceed();
        } finally {
            // 清除数据源,防止内存泄漏
            DataSourceContextHolder.clearDataSourceKey();
        }
    }
}

6. 整合数据源配置

创建一个统一的配置类来管理所有数据源。

package com.example.config;

import com.example.config.dynamic.DynamicDataSource;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary // 必须!将动态数据源设置为主数据源
    public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 设置默认数据源
        return dynamicDataSource;
    }
}

注意:这种方式下,SqlSessionFactoryTransactionManager 只需要配置一个,并让它们使用这个 @PrimaryDynamicDataSource 即可。Spring Boot 会自动配置好。

7. 使用

在 Service 方法上添加 @DataSource 注解来切换数据源。

@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    // 默认不加注解,使用 master 数据源(因为我们配置了默认值)
    @Transactional // 事务仍然有效
    public void addProduct(Product product) {
        productMapper.insert(product);
    }

    // 显式指定使用 slave 数据源
    @DataSource("slave")
    public Product getProductById(Integer id) {
        return productMapper.selectById(id);
    }
}

重要提醒:关于事务

总结

如果你的业务模块和数据库绑定关系固定,方案一(静态方式) 更简单、更直观。

如果你需要实现读写分离,或者在业务逻辑中频繁切换数据源,方案二(动态方式) 提供了更高的灵活性。

到此这篇关于SpringBoot项目中多数据源配置方法与使用场景的文章就介绍到这了,更多相关SpringBoot多数据源配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文