🚀 Spring Boot整合多数据源:分库业务的标准做法
文章目录
- 🚀 Spring Boot整合多数据源:分库业务的标准做法
- 🔍 一、为什么需要多数据源支持?
- 💡 典型业务场景
- ⚙️ 二、多数据源集成方案对比
- 💡 主流方案分析
- 🔧 方案选型建议
- 🔄 三、动态数据源切换原理
- 💡 核心架构
- ⚙️ 核心代码实现
- 🚀 使用示例
- 🧩 四、多数据源事务管理
- 💣 单事务管理器问题
- 💡 解决方案一:独立事务管理器
- 💡 解决方案二:分布式事务
- ⚠️ 事务管理最佳实践
- 🚀 五、主从读写分离实战
- 💡 架构设计
- ⚙️ 配置示例
- 🔧 动态路由配置
- ⚡️ 读写分离策略
- 🧪 六、整合MyBatis-Plus多数据源
- 💡 官方推荐方案
- ⚙️ 配置示例
- 🚀 注解使用
- 💎 七、最佳实践总结
- 🏆 核心实施步骤
- ⚠️ 避坑指南
- 🛠 推荐工具栈
- 🌟 建议
🔍 一、为什么需要多数据源支持?
在实际业务开发中,随着系统规模的扩大,一个单一的数据源很难满足以下典型场景:
- 分库分表:将用户表、订单表、日志表拆分到不同数据库,降低单库压力。
- 读写分离:主库用于写操作,从库用于读操作,提高查询性能。
- 多租户架构:为不同客户接入独立的数据源,保障数据隔离。
如果仍使用单数据源架构,将面临以下问题:
问题 | 表现 |
---|---|
单点瓶颈 | 数据库连接数受限,性能下降 |
数据隔离困难 | 各业务之间相互影响,风险扩大化 |
扩展困难 | 无法灵活配置租户或业务线的数据库策略 |
案例分享:在电商平台中,我们将用户、订单、日志分离到不同数据库集群,使QPS提升300%,故障恢复时间缩短70%
💡 典型业务场景
⚙️ 二、多数据源集成方案对比
💡 主流方案分析
方案 | 优点 | 缺点 | 场景适配 |
---|---|---|---|
@Primary + @Qualifier | 简单明了,配置直观 | 不支持动态切换 | 静态业务分离 |
AbstractRoutingDataSource | 支持动态路由,结合 ThreadLocal 使用 | 配置复杂,事务处理较麻烦 | 动态切换读/写、多租户 |
Dynamic Datasource 中间件 | 快速集成、支持注解 + AOP + 多事务 | 引入依赖,需理解封装逻辑 | 推荐使用,兼容性强 |
🔧 方案选型建议
🔄 三、动态数据源切换原理
💡 核心架构
⚙️ 核心代码实现
// 1. 继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceHolder.getDataSourceKey();}
}// 2. 数据源上下文持有器
public class DataSourceHolder {private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();public static void setDataSourceKey(String key) {CONTEXT.set(key);}public static String getDataSourceKey() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();}
}// 3. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {String value() default "master";
}// 4. AOP切面
@Aspect
@Component
public class DSAspect {@Around("@annotation(ds)")public Object around(ProceedingJoinPoint pjp, DS ds) throws Throwable {String oldKey = DataSourceHolder.getDataSourceKey();DataSourceHolder.setDataSourceKey(ds.value());try {return pjp.proceed();} finally {DataSourceHolder.setDataSourceKey(oldKey);}}
}
🚀 使用示例
@Service
public class UserService {// 使用主库@DS("master")public void createUser(User user) {userMapper.insert(user);}// 使用从库@DS("slave")public User getUser(Long id) {return userMapper.selectById(id);}
}
🧩 四、多数据源事务管理
💣 单事务管理器问题
@Service
public class OrderService {// 跨数据源操作将失效!@Transactionalpublic void createOrder(Order order) {orderMapper.insert(order); // 订单库userMapper.updatePoints(order.getUserId()); // 用户库}
}
💡 解决方案一:独立事务管理器
// 配置主库事务管理器
@Bean
public PlatformTransactionManager masterTxManager(DataSource masterDataSource) {return new DataSourceTransactionManager(masterDataSource);
}// 配置从库事务管理器
@Bean
public PlatformTransactionManager slaveTxManager(DataSource slaveDataSource) {return new DataSourceTransactionManager(slaveDataSource);
}// 使用指定事务管理器
@Service
public class UserService {@Transactional(transactionManager = "masterTxManager")public void updateUser(User user) {// ...}
}
💡 解决方案二:分布式事务
// 使用Seata分布式事务
@GlobalTransactional
public void crossDbOperation() {serviceA.update();serviceB.update();
}
⚠️ 事务管理最佳实践
1.避免跨库事务:尽量在单个数据源内完成事务
2.补偿机制:无法避免时实现最终一致性
3.超时控制:设置合理的事务超时时间
4.监控告警:实现事务失败实时告警
🚀 五、主从读写分离实战
💡 架构设计
⚙️ 配置示例
spring:datasource:master:url: jdbc:mysql://master:3306/dbusername: rootpassword: master_pwddriver-class-name: com.mysql.cj.jdbc.Driverslave1:url: jdbc:mysql://slave1:3306/dbusername: read_userpassword: read_pwddriver-class-name: com.mysql.cj.jdbc.Driverslave2:url: jdbc:mysql://slave2:3306/dbusername: read_userpassword: read_pwddriver-class-name: com.mysql.cj.jdbc.Driver
🔧 动态路由配置
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave1")public DataSource slave1DataSource() {return DruidDataSourceBuilder.create().build();}@Beanpublic DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master,@Qualifier("slave1DataSource") DataSource slave1) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", master);targetDataSources.put("slave1", slave1);DynamicDataSource ds = new DynamicDataSource();ds.setDefaultTargetDataSource(master);ds.setTargetDataSources(targetDataSources);return ds;}
}
⚡️ 读写分离策略
// 读操作切面
@Aspect
@Component
public class ReadOnlyAspect {@Around("@annotation(org.springframework.transaction.annotation.Transactional)")public Object around(ProceedingJoinPoint pjp) throws Throwable {TransactionDefinition td = ((MethodInvocationProceedingJoinPoint) pjp).getTransactionAttribute();if (td != null && td.isReadOnly()) {DataSourceHolder.setDataSourceKey("slave");}try {return pjp.proceed();} finally {DataSourceHolder.clear();}}
}
🧪 六、整合MyBatis-Plus多数据源
💡 官方推荐方案
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.1</version>
</dependency>
⚙️ 配置示例
spring:datasource:dynamic:primary: masterstrict: truedatasource:master:url: jdbc:mysql://master:3306/dbusername: rootpassword: master_pwdslave:url: jdbc:mysql://slave:3306/dbusername: read_userpassword: read_pwd
🚀 注解使用
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {@DS("slave") // 从库查询public User getBySlave(Long id) {return getById(id);}@DS("master") // 主库写入public void saveToMaster(User user) {save(user);}
}
💎 七、最佳实践总结
🏆 核心实施步骤
1.数据源规划:按业务划分数据源边界
2.路由策略:设计合理的数据源切换规则
3.事务管理:明确事务边界与处理方案
4.性能优化:连接池配置与监控
5.故障隔离:避免跨数据源故障扩散
⚠️ 避坑指南
问题 | 解决方案 |
---|---|
主从延迟 | 关键业务强制读主库 |
跨库事务 | 使用Seata分布式事务 |
连接泄露 | 严格使用try-with-resources |
配置错误 | 多环境配置分离 |
监控缺失 | 集成Druid监控 |
🛠 推荐工具栈
1.数据源管理:dynamic-datasource-spring-boot-starter
2.分布式事务:Seata
3.连接池:Druid
4.监控工具:Prometheus + Grafana
5.分库分表:ShardingSphere
🌟 建议
在系统中,我们采用以下策略保障数据安全:
1.写操作:强制主库+分布式事务
2.读操作:从库负载均衡+失败降级主库
3.数据校验:夜间对账任务
4.熔断机制:从库故障自动切换
最后结语:多数据源架构是应对业务增长的必经之路。合理的设计能显著提升系统性能与可用性,但需警惕过度设计带来的复杂度。记住:技术服务于业务,而非相反!