在现代企业级应用中,数据源切换是应对读写分离、多租户架构或分库分表等场景的常见需求。本教程将详细介绍如何在 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,进行读操作时则会在 slave1 和 slave2 之间负载均衡。
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 项目中快速、稳定地实现多数据源动态切换,满足各种复杂的业务场景需求。