跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring AOP 实现基于业务逻辑的动态数据源切换

读写分离在现代架构中更多承担数据安全备份职责,而非单纯性能提升。利用 Spring AOP 结合自定义注解与 AbstractRoutingDataSource,可在方法执行前依据业务逻辑动态切换主从数据源。核心方案包含 ThreadLocal 上下文管理、拦截器逻辑及枚举类型定义,实现业务代码无侵入,同时保障系统扩展性与维护便利性。

leon发布于 2025/1/19更新于 2026/10/688 浏览
Spring AOP 实现基于业务逻辑的动态数据源切换

为什么需要读写分离?

为了承载更多用户、提升响应速度或分摊数据库压力,这些回答都不算错。但「读写分离」并非万能药,性能提升往往有限。从单库到读写分离,理论上服务器压力减半,但应用层真的需要这一倍提升吗?在硬件性能廉价且缓存系统(如 Redis)能带来几十倍提升的今天,读写分离更多是为了数据安全备份。

更准确地说,这应被称为「主从分离」。读操作连接从库,写操作连接主库,这是基础原则。

基于 AOP 的动态数据源切换方案

直接在代码中硬编码切换逻辑虽然简单,但缺乏扩展性。有追求的开发者倾向于利用 Spring AOP 的特性,在方法执行前根据业务动态切换数据源。这种方式无需侵入业务代码,未来若取消读写分离,只需移除 AOP 配置即可,维护成本更低。

核心思路是利用 ThreadLocal 存储当前线程的数据源标识,配合 AbstractRoutingDataSource 进行路由分发,并通过自定义注解标记需要特定数据源的方法。

核心实现组件

JdbcContextHolder 上下文管理

使用 ThreadLocal 确保同一请求线程内数据源标识的一致性。

public class JdbcContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setJdbcType(String jdbcType) {
        contextHolder.set(jdbcType);
    }

    public static void setSlave() {
        setJdbcType("slave");
    }

    public static void setMaster() {
        clearJdbcType();
    }

    public static String getJdbcType() {
        return contextHolder.get();
    }

    public static void clearJdbcType() {
        contextHolder.remove();
    }
}
DynamicDataSource 路由策略

继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法以获取当前线程的数据源标识。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import cn.mayongfa.common.JdbcContextHolder;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return JdbcContextHolder.getJdbcType();
    }
}
枚举与注解定义

通过枚举明确数据源类型,避免字符串硬编码。

public enum DataSourceType {
    Master("master"),
    Slave("slave");

    private final String name;

    DataSourceType(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

自定义注解用于标记方法所需的数据源,默认值为 Master。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.Master;
}
切面拦截逻辑

通过 AOP 拦截带有 @DataSource 注解的方法,在方法执行前设置数据源上下文。

import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import cn.mayongfa.common.JdbcContextHolder;

public class DataSourceChoose {

    public void before(JoinPoint point) {
        Object target = point.getTarget();
        String methodName = point.getSignature().getName();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes();

        try {
            if (interfaces.length > 0) {
                Method m = interfaces[0].getMethod(methodName, parameterTypes);
                if (m != null && m.isAnnotationPresent(DataSource.class)) {
                    DataSource data = m.getAnnotation(DataSource.class);
                    JdbcContextHolder.clearJdbcType();
                    JdbcContextHolder.setJdbcType(data.value().getName());
                }
            }
        } catch (Exception e) {
            // 处理异常,建议记录日志
        }
    }
}

注意:上述反射逻辑假设目标对象实现了接口。若直接操作类,需调整 getInterfaces() 为 getMethods() 遍历查找。实际运行时请确保注解位置正确,并在切面配置中引用该拦截器。

目录

  1. 为什么需要读写分离?
  2. 基于 AOP 的动态数据源切换方案
  3. 核心实现组件
  4. JdbcContextHolder 上下文管理
  5. DynamicDataSource 路由策略
  6. 枚举与注解定义
  7. 切面拦截逻辑

更多推荐文章

查看全部
  • C++ STL vector 底层原理与模拟实现
  • C++ 多态底层原理:V-Table 机制与常见陷阱
  • C++ STL 有序关联容器详解:set、map 及其变体用法
  • Java 虚拟线程:协程概念与性能提升详解
  • C++ unordered_map 与 unordered_set 核心原理及模拟实现
  • Linux 多线程开发:线程创建、终止、等待与分离实战
  • 基于 Python + Flask 的黑龙江旅游景点数据分析系统
  • MATLAB 图像处理:冈萨雷斯 DIPUM 工具箱功能详解与实战
  • Rust 嵌入式开发实战:从 ARM 裸机编程到 RTOS 应用
  • llama.cpp 多 GPU 分布式计算优化实践指南
  • AI 辅助游戏开发:基于 DeepSeek 构建贪吃蛇游戏
  • AI 辅助生成万字长篇小说工具使用指南
  • AM32 固件深度解析:无人机电调配置与性能优化
  • GraphRAG:基于 PolarDB、通义千问和 LangChain 的知识图谱与大模型融合方案
  • 基于 Go 的免疫治疗门诊离散事件仿真与 ResusBay 挤兑建模
  • 基于 LangGraph 构建带记忆与人工干预的智能搜索机器人
  • FPGA FIR 滤波器设计中的时序艺术:从使能打拍到流水线优化
  • AI 辅助 C++ STL set 容器高效使用指南
  • AI 普及时代,普通人如何建立商业竞争力?
  • 一切皆是映射:深入理解 DQN 的稳定性与收敛性

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online