SpringBoot项目里,用dynamic-datasource搞定多库读写分离和事务,保姆级避坑指南
SpringBoot多数据源实战dynamic-datasource深度整合与避坑手册当你的应用流量开始突破单库性能瓶颈或是业务需要接入多个异构数据库时如何优雅管理数据源连接就成了必须面对的工程难题。最近在重构公司订单系统时我亲历了从单数据源到多数据源的完整迁移过程其中dynamic-datasource的表现令人惊喜——这个轻量级组件仅用三天就帮我们实现了读写分离、多租户数据隔离和跨库事务控制。本文将分享实战中积累的配置技巧和那些只有踩过坑才知道的细节。1. 为什么选择dynamic-datasource在技术选型阶段我们对比了三种主流方案方案学习成本功能完整性侵入性事务支持原生Spring多数据源高低强仅单库ShardingSphere中高弱分布式事务dynamic-datasource低中高弱本地/分布式事务最终选择dynamic-datasource的关键因素在于近乎零改造与MyBatis/MyBatis-Plus天然融合已有DAO层代码几乎无需修改灵活的分组策略通过db1、db2_slave这样的命名即可自动建立主从关系事务增强DSTransactional注解完美解决跨库事务难题运行时动态调整支持不停机增减数据源这对需要动态扩容的SaaS应用至关重要实际踩坑提示如果项目已使用HikariCP连接池注意排除Druid的自动配置spring.autoconfigure.excludecom.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure2. 快速搭建多数据源环境2.1 基础配置实战新建一个SpringBoot 2.7项目引入关键依赖dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version3.5.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency配置文件采用分组模式实现一主两从架构spring: datasource: dynamic: primary: master # 默认数据源 strict: true # 启用严格模式 datasource: master: url: jdbc:mysql://127.0.0.1:3306/master?useSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave_1: url: jdbc:mysql://127.0.0.1:3307/slave1?useSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave_2: url: jdbc:mysql://127.0.0.1:3308/slave2?useSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver2.2 注解驱动开发在Service层通过注解实现动态路由Service DS(master) // 类级别默认数据源 public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Override DS(slave) // 方法级别覆盖 public Order getById(Long id) { return orderMapper.selectById(id); } Override DSTransactional // 多数据源事务 public void createOrder(Order order) { orderMapper.insert(order); inventoryService.reduceStock(order.getProductId()); // 跨服务调用 } }关键注意事项避免在私有方法上使用DS注解Spring AOP无法代理私有方法嵌套服务调用时内层方法会继承外层的数据源上下文除非显式声明新注解使用DSTransactional时务必关闭原生Transactional3. 高级特性实战3.1 智能读写分离通过自定义拦截器实现零侵入读写分离Intercepts({ Signature(typeExecutor.class, methodupdate, args{MappedStatement.class,Object.class}), Signature(typeExecutor.class, methodquery, args{MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}) }) public class ReadWriteInterceptor implements Interceptor { private Random random new Random(); Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms (MappedStatement) invocation.getArgs()[0]; if (ms.getSqlCommandType() SqlCommandType.SELECT) { // 随机选择从库 int slaveIndex random.nextInt(2) 1; DynamicDataSourceContextHolder.push(slave_ slaveIndex); } else { DynamicDataSourceContextHolder.push(master); } try { return invocation.proceed(); } finally { DynamicDataSourceContextHolder.clear(); } } }在配置类中注册拦截器Configuration public class MybatisConfig { Bean public ReadWriteInterceptor readWriteInterceptor() { return new ReadWriteInterceptor(); } }3.2 动态数据源管理运行时动态增减数据源适用于多租户场景RestController RequestMapping(/datasource) public class DataSourceController { Autowired private DataSourceCreator dataSourceCreator; PostMapping(/add) public String addDataSource(RequestBody DataSourceDTO dto) { DataSourceProperty property new DataSourceProperty(); property.setPoolName(dto.getName()); property.setUrl(dto.getUrl()); property.setUsername(dto.getUsername()); property.setPassword(dto.getPassword()); DynamicRoutingDataSource ds (DynamicRoutingDataSource) DynamicDataSourceAutoConfiguration.dynamicDataSource(); ds.addDataSource(dto.getName(), dataSourceCreator.createDataSource(property)); return 数据源添加成功; } DeleteMapping(/remove/{name}) public String removeDataSource(PathVariable String name) { DynamicRoutingDataSource ds (DynamicRoutingDataSource) DynamicDataSourceAutoConfiguration.dynamicDataSource(); ds.removeDataSource(name); return 数据源移除成功; } }4. 生产环境避坑指南4.1 事务混用陷阱致命错误示范Transactional // 原生事务 DS(slave) public void faultyMethod() { // 跨库操作 orderDao.update(order); logDao.insert(log); }正确做法应该是完全禁用Transactional注解统一使用DSTransactional管理事务对于需要分布式事务的场景集成Seataspring: datasource: dynamic: seata: true # 开启seata seata-mode: AT # 使用AT模式4.2 连接池配置优化建议为不同特性的数据源设置独立连接池参数master: url: jdbc:mysql://127.0.0.1:3306/master hikari: maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 slave_1: url: jdbc:mysql://127.0.0.1:3307/slave1 hikari: maximum-pool-size: 30 connection-timeout: 60000 read-only: true4.3 监控与健康检查暴露数据源健康端点management: endpoints: web: exposure: include: health,info,metrics health: db: enabled: true dynamic-datasource: enabled: true通过Micrometer监控关键指标Bean public MeterBindersInitializer meterBinders(DataSource dataSource) { return new MeterBindersInitializer() { Override public void afterPropertiesSet() { Metrics.addDataSourceMetrics((DynamicRoutingDataSource) dataSource); } }; }在Kubernetes环境中建议配置如下存活探针livenessProbe: httpGet: path: /actuator/health/dynamic-datasource port: 8080 initialDelaySeconds: 60 periodSeconds: 30
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2543091.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!