Spring Boot 整合 MyBatis-Plus 实现多数据源配置
介绍在 Spring Boot 项目中整合 MyBatis-Plus 实现多数据源配置的实战方案。通过定义枚举、ThreadLocal 上下文管理及继承 AbstractRoutingDataSource 完成动态路由。涵盖环境准备、配置文件编写、核心代码实现及测试示例。同时分析了事务失效、循环依赖等常见问题并提供解决方案,最后给出生产级优化建议如注解 AOP 自动切换与连接池调优。

介绍在 Spring Boot 项目中整合 MyBatis-Plus 实现多数据源配置的实战方案。通过定义枚举、ThreadLocal 上下文管理及继承 AbstractRoutingDataSource 完成动态路由。涵盖环境准备、配置文件编写、核心代码实现及测试示例。同时分析了事务失效、循环依赖等常见问题并提供解决方案,最后给出生产级优化建议如注解 AOP 自动切换与连接池调优。

在真实项目中,多数据源几乎是标配。
不同系统隔离,避免单库过大。
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
public enum DataSourceType {
MASTER, SLAVE
}
public class DataSourceContextHolder {
private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
public static void set(DataSourceType type) {
CONTEXT.set(type);
}
public static DataSourceType get() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
继承 AbstractRoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 核心:返回当前线程的数据源标识
return DataSourceContextHolder.get();
}
}
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
// 主数据源
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return new HikariDataSource();
}
// 从数据源
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return new HikariDataSource();
}
// 动态数据源
@Primary
@Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource());
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
dynamicDataSource;
}
DataSourceTransactionManager {
(dynamicDataSource);
}
}
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisPlusConfig {}
@Data
@TableName("user")
public class User {
private Long id;
private String name;
}
public interface UserMapper extends BaseMapper<User> {}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 写操作(主库)
public void saveUser(User user) {
DataSourceContextHolder.set(DataSourceType.MASTER);
userMapper.insert(user);
DataSourceContextHolder.clear();
}
// 读操作(从库)
public List<User> listUser() {
DataSourceContextHolder.set(DataSourceType.SLAVE);
List<User> list = userMapper.selectList(null);
DataSourceContextHolder.clear();
return list;
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/save")
public String save() {
User user = new User();
user.setName("test");
userService.saveUser(user);
return "ok";
}
@GetMapping("/list")
public List<User> list() {
return userService.listUser();
}
}
Spring 事务基于 AOP 代理。
如果:
this.saveUser();
会导致事务失效。
AbstractRoutingDataSource 在获取连接时就确定数据源。
如果:
@Transactional
public void test() {
setSlave();
select();
setMaster();
insert();
}
⚠️ 会全部使用同一个数据源。
如果配置错误:
dynamicDataSource() 里又注入自己
会导致循环依赖。
让 Spring 自动注入参数:
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource)
如果忘记:
DataSourceContextHolder.clear();
可能导致线程复用时污染数据源。
建议使用 AOP 自动切换(生产推荐)。
定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
DataSourceType value();
}
通过切面自动设置数据源,比手动 set 更优雅。
spring:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 600000
避免连接池爆掉。
实现多数据源的核心原理只有一句话:
利用 AbstractRoutingDataSource + ThreadLocal 实现动态路由。
完整流程:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online