若依框架多数据源实战:如何用@DataSource注解轻松切换MySQL主从库
若依框架多数据源实战用DataSource注解实现MySQL主从库智能切换当系统流量逐渐攀升数据库的读写压力开始显现时很多开发者都会面临一个关键决策如何在保证数据一致性的前提下有效分散数据库负载若依框架提供的DataSource注解方案可能是你在Java项目中实现读写分离最优雅的解决方案。1. 环境准备与基础配置在开始编码前我们需要先搭建好基础环境。假设你已经有一个基于Spring Boot的若依项目现在要为其添加多数据源支持。首先在application.yml中配置主从数据源信息spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主库配置 master: url: jdbc:mysql://master-db:3306/ruoyi?useSSLfalse username: admin password: master123 initial-size: 5 max-active: 20 # 从库配置 slave: enabled: true # 必须显式开启 url: jdbc:mysql://slave-db:3306/ruoyi?useSSLfalse username: readonly password: slave123 initial-size: 5 max-active: 15关键配置说明master节点配置了完整的连接信息作为默认数据源slave节点需要显式设置enabled: true才会生效建议为从库配置专门的只读账号避免误操作2. 核心组件实现原理2.1 数据源类型枚举首先定义数据源类型的枚举类这个枚举会贯穿整个切换逻辑public enum DataSourceType { MASTER, // 主库标识 SLAVE // 从库标识 }2.2 动态数据源配置类DruidConfig类负责初始化数据源并设置路由规则Configuration public class DruidConfig { Bean ConfigurationProperties(spring.datasource.druid.master) public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } Bean ConditionalOnProperty(prefix spring.datasource.druid.slave, name enabled, havingValue true) ConfigurationProperties(spring.datasource.druid.slave) public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } Primary Bean(name dynamicDataSource) public DynamicDataSource dynamicDataSource( Qualifier(masterDataSource) DataSource master, Qualifier(slaveDataSource) DataSource slave) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(DataSourceType.MASTER.name(), master); targetDataSources.put(DataSourceType.SLAVE.name(), slave); return new DynamicDataSource(master, targetDataSources); } }2.3 动态数据源路由DynamicDataSource继承自Spring的AbstractRoutingDataSource负责实际的路由选择public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }3. 注解驱动切换实现3.1 自定义DataSource注解定义注解时需要注意继承性和优先级Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Inherited public interface DataSource { DataSourceType value() default DataSourceType.MASTER; }3.2 切面编程实现通过AOP拦截带有DataSource注解的方法Aspect Component Order(-1) // 确保在事务切面之前执行 public class DataSourceAspect { Pointcut(annotation(com.ruoyi.common.annotation.DataSource) || within(com.ruoyi.common.annotation.DataSource)) public void dataSourcePointCut() {} Around(dataSourcePointCut()) public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource getDataSource(point); if (dataSource ! null) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { DynamicDataSourceContextHolder.clearDataSourceType(); } } private DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature (MethodSignature) point.getSignature(); // 方法级注解优先 DataSource methodAnnotation AnnotationUtils.findAnnotation( signature.getMethod(), DataSource.class); if (methodAnnotation ! null) { return methodAnnotation; } // 类级注解次之 return AnnotationUtils.findAnnotation( signature.getDeclaringType(), DataSource.class); } }4. 实战应用与优化建议4.1 基础使用示例在Service层方法上直接使用注解Service public class UserServiceImpl implements UserService { // 默认使用主库 Override public void updateUser(User user) { userMapper.updateById(user); } // 显式指定从库 Override DataSource(DataSourceType.SLAVE) public User getUserById(Long id) { return userMapper.selectById(id); } }4.2 类级别注解如果整个Service都需要使用从库可以在类上添加注解Service DataSource(DataSourceType.SLAVE) public class ReportServiceImpl implements ReportService { // 所有方法默认使用从库 // 个别方法可以通过方法级注解覆盖 }4.3 性能优化建议连接池配置主库连接数通常需要设置得比从库更大监控Druid的统计信息根据实际使用情况调整事务处理带有Transactional的方法默认会使用主库只读事务可以显式指定从库DataSource(DataSourceType.SLAVE) Transactional(readOnly true) public ListUser getAllUsers() { return userMapper.selectList(null); }监控与故障转移实现健康检查机制自动剔除不可用的从库考虑添加重试逻辑当从库查询失败时自动切换到主库5. 常见问题排查5.1 注解不生效的可能原因配置检查确保spring.datasource.druid.slave.enabledtrue检查从库连接信息是否正确包扫描问题确认切面类被Spring管理检查ComponentScan是否包含相关包AOP代理问题同类内部调用不会触发AOP解决方法通过AopContext.currentProxy()获取代理对象5.2 事务与数据源切换的冲突当同时使用Transactional和DataSource时需要注意事务管理器会在AOP切面之前初始化数据源解决方法确保DataSourceAspect的Order值小于事务切面或者在事务注解中也指定数据源DataSource(DataSourceType.SLAVE) Transactional(readOnly true) public ListData getReportData() { // ... }5.3 多线程环境下的数据源污染由于我们使用ThreadLocal存储数据源信息在异步场景下需要特别注意Async public void asyncProcess() { // 这里会丢失原始线程的数据源信息 // 需要重新设置或使用主库 }解决方法异步方法显式指定数据源实现AsyncConfigurer自定义线程池传递上下文信息6. 高级应用场景6.1 多从库负载均衡当有多个从库时可以扩展实现负载均衡public enum DataSourceType { MASTER, SLAVE1, SLAVE2, SLAVE3 } // 在路由时随机选择 protected Object determineCurrentLookupKey() { String type DynamicDataSourceContextHolder.getDataSourceType(); if (type.startsWith(SLAVE)) { // 随机选择从库 int slaveNum ThreadLocalRandom.current().nextInt(1, 4); return SLAVE slaveNum; } return type; }6.2 基于分库分表的数据源路由结合分库分表策略可以实现更复杂的路由逻辑DataSource(user_db_1) public User getUserFromShard(Long userId) { // 根据分片规则自动路由到对应数据库 }6.3 动态添加数据源运行时动态注册新数据源public void addNewDataSource(String name, String url, String username, String password) { DruidDataSource ds new DruidDataSource(); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); // 其他配置... DynamicDataSource dynamicDataSource (DynamicDataSource) SpringUtils.getBean(dynamicDataSource); dynamicDataSource.addTargetDataSource(name, ds); }
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2463870.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!