Spring数据访问模块设计
前面我们已经完成了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. 返回结果/行数。 处理所有异常转换! |
TypeHandler | Java类型 <-> 数据库类型 转换器。 (如 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;@Overridepublic TransactionStatus getTransaction(TransactionDefinition definition) {Connection conn = DataSourceUtils.getConnection(dataSource);conn.setAutoCommit(false); // 开启事务return new DefaultTransactionStatus(conn, true);}@Overridepublic 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;@Overridepublic 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> {@Overridepublic 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 模块,事务管理器也可以看作一种特殊代理)。在
Repository
或Service
方法上添加@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
),框架搞定一切繁琐。”
相关文章:

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

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

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

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

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...

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

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...