Spring Boot 3.0动态多数据源切换实战教程

在现代企业级应用中,数据源切换是应对读写分离、多租户架构或分库分表等场景的常见需求。本教程将详细介绍如何在 Spring Boot 3.0 中,通过整合 dynamic-datasource-spring-boot3-starter 这一成熟的开源方案,以最小的代码量实现优雅、高效的多数据源动态切换。

一、为什么选择 dynamic-datasource-spring-boot3-starter

虽然 Spring 提供了 AbstractRoutingDataSource 作为底层支持,但手动实现一套完整的动态数据源切换机制(包括 AOP 切面、ThreadLocal 上下文管理等)较为繁琐。dynamic-datasource 项目正是为了解决这一痛点而生,它基于 Spring Boot 的自动装配机制,提供了一套开箱即用的解决方案。

核心优势

  • 零配置启动 :通过简单的 application.yml 配置即可定义多个数据源。
  • 注解驱动 :使用 @DS 注解即可在 Service 层或 Mapper 层轻松切换数据源。
  • 功能丰富:原生支持读写分离、多数据源组、SpEL 表达式等高级特性。
  • Spring Boot 3 原生支持dynamic-datasource-spring-boot3-starter 专门为 Spring Boot 3.x 设计,完美兼容 Jakarta EE 规范。
二、项目依赖与配置

1、添加 Maven 依赖

在项目的 pom.xml 文件中,添加 dynamic-datasource-spring-boot3-starter 依赖。请确保你的 Spring Boot 版本为 3.x。

复制代码
<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 动态数据源 Starter for Spring Boot 3 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
        <version>4.3.0</version> <!-- 请使用最新稳定版本 -->
    </dependency>

    <!-- 数据库驱动,以 MySQL 为例 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- MyBatis-Plus Starter (可选,用于简化数据访问层) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.5</version>
    </dependency>
</dependencies>

2、配置多数据源

src/main/resources/application.yml 中进行配置。dynamic 前缀下的 primary 属性用于指定默认数据源,datasource 下则定义具体的数据源连接信息。

复制代码
spring:
  datasource:
    dynamic:
      # 设置默认数据源,未指定 @DS 注解时将使用此数据源
      primary: master
      # 开启 SQL 日志打印,方便调试
      p6spy: true
      datasource:
        # 主库配置,用于写操作
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/db_master?useSSL=false&serverTimezone=UTC
          username: root
          password: your_password
        # 从库配置,用于读操作
        slave:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/db_slave?useSSL=false&serverTimezone=UTC
          username: root
          password: your_password
三、核心代码实现

1、定义数据实体与 Mapper

首先,创建一个简单的用户实体类 User

复制代码
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String email;
}

接着,创建对应的 MyBatis-Plus Mapper 接口。

复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承 BaseMapper 后,已拥有基础的 CRUD 方法
}

2、在 Service 层使用 @DS 注解
@DS 注解是实现数据源切换的关键。它可以标注在类或方法上,方法上的注解优先级更高。

复制代码
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class UserService {

    private final UserMapper userMapper;

    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 写操作,使用默认的 master 数据源。
     * 由于 application.yml 中 primary 设置为 master,因此此处可不加 @DS 注解。
     */
    @Transactional
    public void addUser(User user) {
        userMapper.insert(user);
    }

    /**
     * 读操作,通过 @DS("slave") 明确指定使用 slave 数据源。
     * 这实现了读写分离,减轻主库压力。
     */
    @DS("slave")
    public List<User> getAllUsers() {
        return userMapper.selectList(null);
    }

    /**
     * 也可以在类级别上使用 @DS 注解,
     * 那么该类下所有方法默认都会使用指定的数据源。
     */
}

3、编写 Controller 进行测试

创建一个简单的 REST Controller 来暴露接口,方便测试。

复制代码
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public void createUser(@RequestBody User user) {
        userService.addUser(user);
    }

    @GetMapping
    public List<User> listUsers() {
        // 调用此方法将查询从库
        return userService.getAllUsers();
    }
}
四、高级特性与最佳实践

1、读写分离与负载均衡
dynamic-datasource 支持更复杂的读写分离配置。你可以定义一个数据源组,其中包含一个主库和多个从库。当进行读操作时,它会自动在从库间进行负载均衡(默认轮询)。

复制代码
spring:
  datasource:
    dynamic:
      primary: mydb # 默认数据源组
      datasource:
        mydb: # 定义一个名为 mydb 的组
          master: # 组内的主库
            url: jdbc:mysql://localhost:3306/db_master...
            username: root
            password: password
          slave1: # 组内的从库1
            url: jdbc:mysql://localhost:3306/db_slave1...
            username: root
            password: password
          slave2: # 组内的从库2
            url: jdbc:mysql://localhost:3306/db_slave2...
            username: root
            password: password

使用方式不变,@DS("mydb") 进行写操作时会自动路由到 master,进行读操作时则会在 slave1slave2 之间负载均衡。

2、多数据源事务管理

这是一个需要特别注意的要点。@Transactional 注解是基于数据源绑定的。当一个方法被 @DS@Transactional 同时修饰时,事务将作用于 @DS 指定的数据源。

重要提醒 :应避免在单个 @Transactional 方法内跨越多个数据源进行操作。Spring 的声明式事务管理器无法保证跨库操作的原子性。如果需要处理跨库业务,应考虑使用分布式事务框架(如 Seata),或将业务逻辑拆分为多个独立的、各自管理单一数据源的事务方法。

3、运行时动态管理数据源

对于多租户 SaaS 应用,可能需要根据租户 ID 动态创建和销毁数据源。dynamic-datasource 提供了 DynamicDataSourceService 接口来实现这一功能。

复制代码
import com.baomidou.dynamic.datasource.DynamicDataSourceService;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import org.springframework.stereotype.Service;

@Service
public class TenantDataSourceService {

    private final DynamicDataSourceService dynamicDataSourceService;
    private final DefaultDataSourceCreator dataSourceCreator;

    public TenantDataSourceService(DynamicDataSourceService dynamicDataSourceService,
                                   DefaultDataSourceCreator dataSourceCreator) {
        this.dynamicDataSourceService = dynamicDataSourceService;
        this.dataSourceCreator = dataSourceCreator;
    }

    // 为指定租户添加数据源
    public void addTenantDataSource(String tenantId, String jdbcUrl, String username, String password) {
        DataSourceProperty property = new DataSourceProperty();
        property.setPoolName(tenantId); // 连接池名称
        property.setUrl(jdbcUrl);
        property.setUsername(username);
        property.setPassword(password);
        property.setDriverClassName("com.mysql.cj.jdbc.Driver");
        
        // 创建并添加数据源
        dynamicDataSourceService.addDataSource(dataSourceCreator.createDataSource(property));
    }

    // 移除指定租户的数据源
    public void removeTenantDataSource(String tenantId) {
        dynamicDataSourceService.removeDataSource(tenantId);
    }
}

通过以上步骤,你可以在 Spring Boot 3.0 项目中快速、稳定地实现多数据源动态切换,满足各种复杂的业务场景需求。

相关推荐
代码漫谈2 小时前
微服务 vs 单体架构:架构选型、实战拆解与决策指南
java·微服务·springboot·springcloud
神龙斗士2402 小时前
第一个Spring Boot程序
java·spring boot·java-ee·tomcat
gelald2 小时前
Spring Boot - 配置加载
java·spring boot·后端·spring
中国胖子风清扬2 小时前
基于GPUI框架构建现代化待办事项应用:从架构设计到业务落地
java·spring boot·macos·小程序·rust·uni-app·web app
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第十六期 - 迭代器模式】迭代器模式 —— 统一遍历实现、优缺点与适用场景
java·后端·设计模式·迭代器模式·软件工程
Sirens.2 小时前
七大经典排序算法:原理、实现与复杂度分析
java·数据结构·算法·排序算法
万邦科技Lafite2 小时前
通过淘宝关键词API接口批量获取商品信息指南
java·前端·javascript
Seven972 小时前
【从0到1构建一个ClaudeAgent】规划与协调-任务系统
java
算.子2 小时前
【Spring AI 实战】四、OpenAI / Anthropic / Azure——多模型适配与自动配置原理
后端·ai编程