1. 多数据源配置的现实需求
在真实的企业级应用开发中,单一数据源往往无法满足业务需求。我经历过一个电商项目,需要同时连接订单库、用户库和商品库,这三个库分别部署在不同的MySQL实例上。传统的单数据源配置根本无法应对这种场景,这就是为什么我们需要掌握多数据源配置技术。
MyBatis-Plus作为MyBatis的增强工具,提供了优雅的多数据源解决方案。与原生MyBatis繁琐的配置相比,MyBatis-Plus通过dynamic-datasource-spring-boot-starter组件,真正实现了"开箱即用"的体验。下面我将分享在实际项目中的完整配置流程和踩坑经验。
2. 环境准备与依赖配置
2.1 必备组件清单
首先确保你的Spring Boot项目是2.x版本(我用的2.7.15),然后需要添加以下核心依赖:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
注意:dynamic-datasource-spring-boot-starter已经包含了mybatis-plus-boot-starter,无需重复引入
2.2 配置文件关键参数
在application.yml中配置多数据源(以两个数据源为例):
yaml复制spring:
datasource:
dynamic:
primary: master # 设置默认数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
3. 核心实现与注解使用
3.1 数据源切换原理
MyBatis-Plus通过AOP切面实现数据源动态切换。核心注解是@DS,它可以标注在方法或类上:
java复制@Service
@DS("slave") // 类级别默认数据源
public class UserServiceImpl implements UserService {
@Override
@DS("master") // 方法级别覆盖类级别配置
public void addUser(User user) {
// 操作master数据源
}
public User getUser(Long id) {
// 操作slave数据源
}
}
3.2 事务处理的特殊要求
在多数据源环境下,事务管理需要特别注意:
- 同一个事务中不能切换数据源
- 需要禁用原生事务管理器:
java复制@SpringBootApplication
@MapperScan("com.example.mapper")
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
然后在配置类中禁用DataSourceTransactionManagerAutoConfiguration:
java复制@EnableAutoConfiguration(exclude = {DataSourceTransactionManagerAutoConfiguration.class})
public class DataSourceConfig {
// 其他配置...
}
4. 高级配置与性能优化
4.1 多数据源连接池配置
建议为每个数据源单独配置连接池参数:
yaml复制spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
slave:
hikari:
maximum-pool-size: 15
minimum-idle: 3
4.2 读写分离实践
结合@DS注解可以轻松实现读写分离:
java复制@DS("slave")
public List<User> queryUsers() {
// 读操作走从库
}
@DS("master")
public void updateUser(User user) {
// 写操作走主库
}
5. 常见问题排查指南
5.1 数据源切换失效场景
- 自调用问题:同一个类中A方法调用B方法,B方法的@DS注解会失效
- 事务问题:方法上有@Transactional注解时,@DS可能不生效
解决方案:将需要切换数据源的方法抽取到单独的Service中
5.2 多数据源事务处理
如果需要跨数据源事务,可以考虑:
- 使用JTA分布式事务(性能较差)
- 采用最终一致性方案(推荐)
java复制// 伪代码示例
public void crossDataSourceOperation() {
try {
// 操作数据源A
dataSourceAService.update();
// 操作数据源B
dataSourceBService.update();
} catch(Exception e) {
// 记录日志,启动补偿机制
compensationService.save(e);
throw e;
}
}
6. 生产环境最佳实践
6.1 监控与健康检查
建议添加以下监控配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics
health:
db:
enabled: true
6.2 动态添加数据源
运行时动态添加数据源的代码示例:
java复制@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
public void addDynamicDataSource(String poolName, DataSourceProperty property) {
DataSource dataSource = dynamicDataSourceCreator.createDataSource(property);
dynamicRoutingDataSource.addDataSource(poolName, dataSource);
}
我在实际项目中用这个功能实现了租户系统的动态数据源注册,每个新租户入驻时自动创建并注册其专属数据源。
7. 性能压测数据参考
在我的MacBook Pro (M1 Pro)上对双数据源系统进行JMeter压测,结果如下:
| 场景 | 线程数 | 平均响应时间(ms) | 吞吐量(req/s) |
|---|---|---|---|
| 单数据源 | 100 | 45 | 2200 |
| 双数据源 | 100 | 52 | 2100 |
| 单数据源 | 500 | 78 | 6400 |
| 双数据源 | 500 | 85 | 6200 |
可以看到,多数据源带来的性能损耗在可接受范围内(约5-8%),远低于业务收益。
8. 扩展思考:多数据源与微服务
虽然多数据源技术很强大,但在微服务架构下,我建议:
- 尽量保持一个服务一个主数据源
- 特殊场景(如报表查询)才使用多数据源
- 跨服务数据交互通过API进行
最近在重构一个老系统时,我就把原来的5个数据源拆分成了3个微服务,每个服务只保留必要的数据源,系统可维护性大幅提升。