Spring数据访问模块设计

news2025/6/12 9:32:36

前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。

一、核心设计理念

1、痛点在哪

  • 应用离不开数据(数据库、NoSQL、文件等)。但直接写 JDBC/Nosql Client 代码太原始了(码友要优雅):

    • 样板代码多: 开连接、关连接、异常处理… 写吐了,还容易漏。
    • 资源管理难: 连接池怎么管理?线程安全怎么保证?全靠开发者手动,容易出错。
    • SQL/命令与Java代码耦合: SQL 硬编码在 Java 里,改个 SQL 得重新编译,不方便维护和优化。
    • 结果集映射繁琐: 把数据库查出来的一行行数据 (ResultSet/Document/Row) 手动转成 Java 对象 (User, Order),又累又容易出错。
    • 事务管理复杂: 手动控制事务的开始、提交、回滚,尤其是在跨多个操作时,代码会变得非常混乱且容易出错。
    • 缺乏抽象: 不同数据库(MySQL, PostgreSQL)或数据源(DB, Redis)的访问方式有差异,业务代码最好不关心底层细节。
  • 已有模块的支撑: 你已经有了强大的 IoC 容器!它能管理对象生命周期和依赖关系。数据访问对象 (DAO/Repository`) 本身就是对象,非常适合由 IoC 容器托管。Web 模块处理请求,最终往往需要访问数据,两者需要无缝集成

2、解决方案(设计目标)

  • 消灭样板代码: 连接管理、资源释放、异常转换这些通用的、繁琐的事情,模块帮你搞定。

  • 职责分离:

    • 业务逻辑 (Service) 关注做什么 (比如:下单)。
    • 数据访问逻辑 (DAO/Repository) 关注怎么做 (比如:保存订单数据到 DB)。
    • SQL/命令 最好与 Java 代码分离(虽然不一定强制 XML,但提供灵活配置)。
  • 面向接口编程: 业务代码 (Service) 只依赖 UserRepository 接口,不关心具体实现是 JDBC、MyBatis 还是 JPA。模块负责在运行时提供具体实现(动态代理是秘密武器!)。

  • 模板方法模式: 定义一个操作(执行 SQL/命令)的骨架,将某些步骤(如获取连接、设置参数、处理结果)延迟到子类或回调中。核心执行流程固化,可变部分开放扩展。

  • 异常体系统一化: 把五花八门的底层数据访问异常(SQLException, RedisException)转换成模块定义的一套清晰的、非检查型(RuntimeException)异常。业务层无需处理大量底层异常细节。

  • 无缝集成 IoC: 数据访问对象 (DAO/Repository) 本身就是 Bean,由 IoC 容器创建、注入依赖、管理生命周期。

  • 非侵入性: 尽量减少对业务代码的污染。通过注解、接口、少量配置来定义数据访问行为。

二、核心组件与职责划分

组件核心职责数据流中的角色
DataSource连接的工厂 (含连接池)。 提供物理数据库连接。起点Executor 执行操作时向其索取 Connection
PlatformTransactionManager抽象事务管理 (开始、提交、回滚、状态)。 实现事务ACID。事务协调者。为 @Transactional 方法管理连接 (绑定到线程) 和事务边界。协调 Executor 使用正确连接。
Repository (接口)定义数据操作契约 (如 findById,** save)。** 业务层入口。触发点。业务层 (Service) 调用其方法 → 触发动态代理 → 委托给 Executor
Mapper / @SQL承载 SQL/命令 和 映射规则。 定义要执行的语句及参数/结果如何与Java交互。执行蓝图Executor 根据调用的 Repository 方法找到对应的 Mapper,作为执行的“指令手册”。
Executor (核心)执行引擎!应用模板方法模式。核心枢纽 & 苦力: 1. 拿连接 (DataSource / 事务绑定) 2. 根据 Mapper 创建语句 3. 绑定参数 (调用 TypeHandler) 4. 执行 (查询/更新) 5. 处理结果 (映射,调用 TypeHandler/RowMapper) 6. 清理资源 (关语句/结果集,还连接) 7. 返回结果/行数。 处理所有异常转换!
TypeHandlerJava类型 <-> 数据库类型 转换器。 (如 Date<->TIMESTAMP, Enum<->String)转换小助手。在 Executor 绑定参数映射结果时被调用。
SqlSession / Template提供编程式API,管理一次“数据访问会话”。内部使用 Executor替代或辅助 Repository 接口,提供更集中控制。

三、关键设计模式应用

1、模板方法模式:JdbcTemplate 执行流程

核心思想:固定数据库操作流程(获取连接→执行 SQL→处理结果→释放资源),可变部分(SQL 执行、结果映射)通过回调接口扩展。
Spring 源码参考org.springframework.jdbc.core.JdbcTemplate

public class SimpleJdbcTemplate {
    private DataSource dataSource;

    // 模板方法:定义执行骨架
    public <T> T execute(ConnectionCallback<T> action) {
        Connection conn = DataSourceUtils.getConnection(dataSource);
        try {
            return action.doInConnection(conn); // 回调可变逻辑
        } catch (SQLException ex) {
            throw translateException("Connection callback failed", ex);
        } finally {
            DataSourceUtils.releaseConnection(conn, dataSource);
        }
    }

    // 查询模板
    public <T> T query(String sql, ResultSetExtractor<T> rse) {
        return execute(conn -> {
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                try (ResultSet rs = ps.executeQuery()) {
                    return rse.extractData(rs); // 结果集映射回调
                }
            }
        });
    }
}

// 回调接口(策略模式结合点)
@FunctionalInterface
public interface ResultSetExtractor<T> {
    T extractData(ResultSet rs) throws SQLException;
}

2、策略模式:事务管理

核心思想PlatformTransactionManager 抽象事务操作(begin/commit/rollback),不同数据源通过具体策略实现(如 DataSourceTransactionManager)。
Spring 源码参考org.springframework.transaction.PlatformTransactionManager

// 事务管理器接口(策略抽象)
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition);
    void commit(TransactionStatus status);
    void rollback(TransactionStatus status);
}

// JDBC 事务策略实现
public class DataSourceTransactionManager implements PlatformTransactionManager {
    private DataSource dataSource;

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) {
        Connection conn = DataSourceUtils.getConnection(dataSource);
        conn.setAutoCommit(false); // 开启事务
        return new DefaultTransactionStatus(conn, true);
    }

    @Override
    public void commit(TransactionStatus status) {
        Connection conn = status.getConnection();
        conn.commit(); // 提交策略
    }
}

3、动态代理:声明式事务(AOP 代理)

AOP我们还没有设计,此处就把AOP当成一个“黑盒”就好,

Spring AOP 是用于通过运行时动态代理技术实现模块化横切关注点(如日志、事务管理)的核心模块,它通过在方法执行前后插入增强逻辑(Advice)来实现功能。

核心思想:通过代理对象在方法调用前后注入事务逻辑(begin/commit)。Spring 优先用 JDK 动态代理(基于接口),缺接口时用 CGLIB。
Spring 源码参考org.springframework.aop.framework.DefaultAopProxyFactory

// 事务代理创建工厂
public class TransactionProxyFactory {
    public Object createProxy(Object target, PlatformTransactionManager txManager) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                TransactionStatus status = txManager.getTransaction(null);
                try {
                    Object result = method.invoke(target, args); // 执行业务方法
                    txManager.commit(status); // 提交事务
                    return result;
                } catch (Exception ex) {
                    txManager.rollback(status); // 回滚策略
                    throw ex;
                }
            }
        );
    }
}

// 使用示例
UserService proxy = (UserService) new TransactionProxyFactory()
    .createProxy(userService, transactionManager);
proxy.saveUser(); // 代理方法调用

4、工厂模式:Repository 实例创建

核心思想:通过 BeanFactory 动态创建 Repository 代理对象,解耦业务层与数据访问层。
Spring 源码参考org.springframework.beans.factory.FactoryBean

public class RepositoryFactoryBean<T> implements FactoryBean<T> {
    private Class<T> repositoryInterface;

    @Override
    public T getObject() {
        // 动态生成 Repository 代理(结合模板方法+策略)
        return (T) Proxy.newProxyInstance(
            repositoryInterface.getClassLoader(),
            new Class[]{repositoryInterface},
            (proxy, method, args) -> {
                JdbcTemplate template = new JdbcTemplate(dataSource);
                String sql = parseSqlFromAnnotation(method); // 解析 @Select 等注解
                return template.query(sql, new BeanPropertyRowMapper<>(method.getReturnType()));
            }
        );
    }
}

// 配置声明
@Bean
public RepositoryFactoryBean<UserRepository> userRepository() {
    return new RepositoryFactoryBean<>(UserRepository.class);
}

5、结果集映射:RowMapper 策略

Spring 源码实现BeanPropertyRowMapper 通过反射将 ResultSet 列名映射到 JavaBean 属性

public class UserRowMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id")); // 列名→属性
        user.setName(rs.getString("name"));
        return user;
    }
}

// 在模板方法中使用
List<User> users = jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());

四、高级特性及扩展机制设计

1. 声明式事务管理

  • 实现: 依赖 IoC 的 AOP 能力(即使你还没做 AOP 模块,事务管理器也可以看作一种特殊代理)。在 RepositoryService 方法上添加 @Transactional 注解。

  • 流程:

    • 代理拦截被 @Transactional 标记的方法调用。
    • 获取 PlatformTransactionManager
    • 开启事务(获取连接,设置 autoCommit=false,绑定连接到当前线程)。
    • 执行业务方法(其中包含的数据库操作会使用当前线程绑定的连接)。
    • 业务方法成功执行完 -> 提交事务。
    • 业务方法抛出异常 -> 回滚事务。
    • 最终清理线程绑定资源。
  • 好处: 事务控制与业务代码完全分离,配置化。

2. 多数据源支持

  • 实现: 在 IoC 容器中配置多个 DataSource Bean (如 primaryDataSource, secondaryDataSource)。需要实现:

    • AbstractRoutingDataSource:** 一个动态 DataSource 实现,根据某种“键”(Lookup Key)路由到真正的目标 DataSource
    • 确定“键”的策略: 通常基于注解(@DataSource("slave"))或线程上下文变量。Repository 方法执行前设置“键”,AbstractRoutingDataSource 据此选择 DataSource
  • 挑战: 需要与事务管理器配合,确保同一事务内的操作使用同一个数据源。

3. 注解支持

  • @Repository:** 标记一个接口是数据访问仓库,由 IoC 管理并需要生成代理。
  • @Select / @Insert / @Update / @Delete:** 直接在 Repository 接口方法上声明 SQL。
  • @Param:** 为方法参数命名,用于在 SQL 中引用 (#{userId})。
  • @Transactional:** 声明事务属性 (传播行为、隔离级别、超时、只读等)。

4. 结果映射扩展

  • 自定义 RowMapper / ResultHandler:** 允许用户编写代码精细控制如何将一行数据映射到一个对象。
  • 嵌套结果映射: 处理复杂的“一对一”、“一对多”关联关系(需要递归映射)。
  • 自动驼峰命名映射: user_name 列自动映射到 userName 属性。

5. 插件机制 (Interceptor)

  • 实现:Executor 执行流程的关键点(执行前、执行后、处理参数、处理结果)插入拦截逻辑。类似责任链模式。
  • 用途: SQL 日志记录、性能监控、分页逻辑自动添加、权限过滤、数据加解密等。极大增强灵活性。

6. 简单缓存集成

  • 实现:Repository 代理层或 Executor 层,对方法调用结果进行缓存(例如基于方法名和参数生成缓存 Key)。下次相同查询直接返回缓存结果。
  • 注意: 需要处理缓存一致性(更新操作需清除相关缓存)。

五、核心流程解析

以下是 Spring Framework 数据访问模块的核心数据流程图及详细说明:

在这里插入图片描述

六、设计总结

1. 效率飙升 (开发体验)

  • 消灭样板: 连接、语句、资源释放、基础异常处理全自动化。
  • 专注核心: 开发者只需写 SQL/命令 和定义 对象映射 (Mapper / 注解)。
  • 接口清爽: Repository 接口明确职责,业务代码 (Service) 依赖清晰。

2. 健壮可靠 (运行质量)

  • 资源无忧: 严格的连接池管理、资源释放,杜绝泄漏。
  • 异常清晰: 统一转换底层异常为可读的 RuntimeException,业务层少踩坑。
  • 事务优雅: @Transactional 声明式事务,复杂提交回滚一行注解搞定,与业务解耦。

3. 灵活扩展 (设计优势)

  • 接口抽象: Repository 接口隔离技术细节,轻松切换实现 (JDBC, JPA等)。
  • 插件友好: 拦截器 (Interceptor) 机制方便扩展 (SQL日志、分页、监控)。
  • IoC 融合: 作为一等公民 Bean,依赖注入无缝衔接,生命周期受控。

一句话精髓: “通过动态代理声明意图 (Repository),模板方法固化流程 (Executor),让开发者只关注数据本身 (Mapper),框架搞定一切繁琐。”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2407467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…