除了AOP切面,还有哪些更灵活的数据源切换策略?比如基于注解或自定义路由规则

2025-12-04 0 226

在Spring Boot中实现数据源切换,除了AOP切面,还有几种非常灵活的策略。下面我用一个表格对比各种方案,然后重点介绍两种最实用的方法

策略类型 实现方式 灵活性 代码侵入性 适用场景
注解驱动 使用@DS等注解标记方法/类 精确到方法级别的数据源控制
手动编程 在代码中调用API动态切换 最高 复杂业务逻辑、同一方法内多数据源
请求特征路由 基于HTTP头、参数等自动路由 中高 多租户、按客户分库等场景
方法规则路由 基于方法名约定自动切换 有固定命名规范的读写分离

注解驱动切换

这是目前最流行且优雅的方案,MyBatis-Plus的动态数据源组件提供了开箱即用的支持。

配置依赖

<dependency>
    <groupId>com.baomidou</groupId>
-   <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
-   <version>3.5.0</version>
</dependency>

配置文件

spring:
  datasource:
    dynamic:
      primary: master  # 默认数据源
      strict: false    # 是否严格匹配数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master
          username: root
          password: master_password
        slave:
          url: jdbc:mysql://localhost:3306/slave
          username: root
          password: slave_password

使用@DS注解

@Service
public class UserService {
    
    // 默认使用主库(写操作)
    public void createUser(User user) {
        userMapper.insert(user);
    }
    
    // 显式指定从库(读操作)
    @DS(\"slave\")
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
    
    // 在Mapper层直接指定数据源
    @DS(\"slave\")
    public List<User> findActiveUsers() {
        return userMapper.selectActiveUsers();
    }
}

优点:声明式配置,代码侵入性低,支持类级别和方法级别的精细控制。

️ 手动编程切换

对于需要在同一方法内使用多个数据源的复杂场景,手动控制提供了最大灵活性。

基本API使用

@Service
public class OrderService {
    
    public void processOrder(Long orderId) {
        // 第一阶段:从主库读取订单
        DynamicDataSourceContextHolder.push(\"master\");
        try {
            Order order = orderMapper.selectById(orderId);
            // 业务处理...
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
        
        // 第二阶段:写入从库进行数据分析
        DynamicDataSourceContextHolder.push(\"slave\");
        try {
            analysisMapper.insertOrderAnalysis(order);
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }
}

基于HTTP请求的自动切换

通过过滤器或拦截器实现基于请求特征的路由:

@Component
@WebFilter(urlPatterns = \"/*\")
public class DataSourceFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 从请求头获取数据源标识(如多租户场景)
        String tenantId = httpRequest.getHeader(\"X-Tenant-Id\");
        // 或从请求参数判断业务类型
        String businessType = httpRequest.getParameter(\"bizType\");
        
        String dataSourceKey = determineDataSource(tenantId, businessType);
        
        DynamicDataSourceContextHolder.push(dataSourceKey);
        try {
            chain.doFilter(request, response);
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }
    
    private String determineDataSource(String tenantId, String businessType) {
        // 复杂的路由逻辑
        if (\"report\".equals(businessType)) {
            return \"report_db\";
        } else if (tenantId != null) {
            return \"tenant_\" + tenantId;
        }
        return \"master\"; // 默认数据源
    }
}

自定义路由规则

通过继承AbstractRoutingDataSource并重写determineCurrentLookupKey()方法,可以实现基于业务逻辑的复杂路由

基于方法名的路由示例

public class BusinessRoutingDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        // 获取当前方法名进行路由决策
        String methodName = getCurrentMethodName();
        
        if (methodName.startsWith(\"find\") || methodName.startsWith(\"query\") 
            || methodName.startsWith(\"select\") || methodName.startsWith(\"get\")) {
            return \"slave\"; // 读操作路由到从库
        } else if (methodName.startsWith(\"insert\") || methodName.startsWith(\"update\") 
                 || methodName.startsWith(\"delete\") || methodName.startsWith(\"save\")) {
            return \"master\"; // 写操作路由到主库
        }
        
        // 默认路由策略
        return isReadOperation(methodName) ? \"slave\" : \"master\";
    }
}

基于参数值的路由

public class ParameterBasedRouter extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        // 从线程上下文或参数中获取路由信息
        String businessUnit = RequestContextHolder.getBusinessUnit();
        String dataType = RequestContextHolder.getDataType();
        
        // 组合路由逻辑
        if (\"finance\".equals(businessUnit) && \"sensitive\".equals(dataType)) {
            return \"finance_secure_db\";
        } else if (\"report\".equals(dataType)) {
            return \"reporting_db\";
        }
        
        return \"default_db\";
    }
}

️ 实战注意事项

  1. 事务管理:在手动切换数据源时,确保同一个事务内使用同一数据源,避免跨数据源事务导致的数据不一致问题。
  2. 资源清理:使用try-finally确保每次数据源切换后都能正确清理上下文,防止内存泄漏数据源污染
  3. 性能考量:频繁的数据源切换会带来性能开销,在高性能场景下应考虑批量操作连接复用
  4. 降级策略:始终配置合理的主从延迟处理故障降级机制,确保主库不可用时系统的可用性。

总结

选择合适的数据源切换策略需要权衡灵活性和复杂性。对于大多数场景,注解驱动方案提供了最佳实践;对于复杂业务逻辑,手动编程控制不可或缺;而对于有固定路由规则的场景,自定义路由策略能够提供最优雅的解决方案。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 开发教程 除了AOP切面,还有哪些更灵活的数据源切换策略?比如基于注解或自定义路由规则 https://www.zuozi.net/3598.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务