开发者

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

目录
  • 方案一:静态方式(按包路径隔离)
    • 1. 添加依赖 (pom.XML)
    • 2. 配置文件 (application.yml)
    • 3. 创建数据源配置类
    • 4. 创建 Mapper 接口
    • 5. 使用
  • 方案二:动态方式(AOP + 自定义注解)
    • 1. 配置文件 (application.yml)
    • 2. 创建自定义注解
    • 3. 创建数据源上下文持有者
    • 4. 创建动态数据源类
    • 5. 创建AOP切面
    • 6. 整合数据源配置
    • 7. 使用
  • 重要提醒:关于事务
    • 总结

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

      • 读写分离:一个主数据库(Master)负责写操作,一个或多个从数据库(Slave)负责读操作,以提高性能和可用性。
      • 业务拆分:不同的业务模块使用不同的数据库(例如,用户库、订单库、商品库)。
      • 连接异构数据库:同时连接 mysql、PostgreSQL 等不同类型的数据库。

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

      • 静态方式(推荐用于业务隔离场景):通过包路径区分不同的数据源,配置简单,结构清晰。
      • 动态方式(推荐用于读写分离场景):使用 AOP 和自定义注解,在方法级别动态切换数据源,更灵活。

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

      这种方式的核心思想是为每个数据源创建一套独立的配置(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 接口放到各自的包下。

      • com.example.mapper.master -> UserMasterMapper.java
      • com.example.mapper.slave -> OrderSlaveMapper.java

      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 = orderSlaveMappwww.devze.comer.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,重写 determineCurrentLookuhttp://www.devze.compKey 方法,从 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.dynandroidamic.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.Pwww.devze.comrimary;
      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 DataSourphpceBuilder.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);
          }
      }
      

      重要提醒:关于事务

      • 单数据源事务:在以上两种方案中,只要 DataSourceTransactionManagerSqlSessionFactory 绑定的是同一个 DataSource@Transactional 注解就能正常工作。在动态方案中,事务管理器绑定的是 DynamicDataSource,它能确保事务在当前线程选择的数据源上生效。
      • 跨数据源事务(分布式事务):标准的 @Transactional 无法管理跨多个数据源的事务。如果你需要在同一个方法中对 masterslave 都进行写操作,并保证它们的原子性,你需要引入 JTA(Java Transaction API)事务管理器,例如 AtomikosNarayana。这会增加配置的复杂度。

      总结

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

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

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

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜