当前位置: 首页 > news >正文

手写MyBatis第41弹:MyBatis动态代理黑魔法:MapperProxy如何智能处理增删改的返回值?

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。 

目录

一、问题根源:JDBC执行结果与Java方法签名的鸿沟

二、解决方案:在MapperProxy.invoke中实现返回值适配

三、深入思考:这种灵活性的价值与延伸

四、总结与启示


  1. MyBatis动态代理黑魔法:MapperProxy如何智能处理增删改的返回值?

  2. 手写MyBatis(五):MapperProxy的返回值适配策略,让接口设计更灵活

  3. 从void到boolean:深度解析MyBatis Mapper接口返回值类型的兼容性设计

  4. 不只是查询!MapperProxy如何处理增删改操作的多种返回值类型?


在前面的系列文章中,我们成功实现了MyBatis的Mapper动态代理,让一个简单的Java接口能够神奇地执行SQL查询。然而,细心的读者可能会发现,我们之前的实现主要聚焦于SELECT查询操作。当我们转向INSERTUPDATEDELETE等写操作时,一个新的挑战出现了:如何让Mapper接口中的增删改方法支持不同的返回值类型?

在MyBatis的实际使用中,我们经常会看到这样灵活的写法:

public interface UserMapper {void insertUser(User user); // 不关心返回值int updateUser(User user);  // 想要知道影响了多少行boolean deleteUser(Long id); // 只关心是否成功Integer insertUserAndReturnCount(User user); // 包装类型也可以}

这种设计给予了开发者极大的便利。今天,我们就来深入剖析MapperProxy如何通过巧妙的返回值类型适配策略,实现这种灵活性。

一、问题根源:JDBC执行结果与Java方法签名的鸿沟

要理解这个问题的本质,我们需要回到最基础的JDBC层面。无论是Statement还是PreparedStatement,执行增删改操作的方法都是executeUpdate(),它的返回值是一个int类型,表示受影响的行数(affected rows)

然而,在Java接口设计中,开发者可能有着不同的意图:

  • 不关心结果:只想执行操作,不需要知道结果,适合返回void

  • 想知道具体影响:需要确切知道修改了多少行数据,适合返回int

  • 只关心成败:只想知道操作是否成功(通常认为影响行数大于0就成功),适合返回boolean

MapperProxy的核心任务,就是架起一座桥梁,将JDBC返回的统一的int类型,智能地转换适配成Mapper接口方法所声明的各种返回类型

二、解决方案:在MapperProxy.invoke中实现返回值适配

我们的改造主要在MapperProxyinvoke方法中进行。我们需要在执行SQL之后,根据方法返回类型的不同,对SqlSession返回的原始int值进行二次加工。

步骤一:判断SQL命令类型

首先,我们需要确定当前调用的方法是增删改还是查询。这可以通过MappedStatement中的信息获取(通常SQL命令类型保存在其中)。

 public class MapperProxy implements InvocationHandler {// ... 其他字段private final SqlSession sqlSession;private final Configuration configuration;​@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// ... 获取MappedStatement的逻辑MappedStatement ms = configuration.getMappedStatement(statementId);SqlCommandType sqlCommandType = ms.getSqlCommandType();​// 根据SQL命令类型分支处理if (sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.DELETE) {// 处理增删改return handleUpdate(method, ms, args);} else {// 处理查询(原有逻辑)return handleQuery(method, ms, args);}}}

步骤二:执行并转换返回值(核心)

handleUpdate方法是实现灵活性的关键。在这里,我们执行操作并完成类型转换。

private Object handleUpdate(Method method, MappedStatement ms, Object[] args) {// 1. 执行原始的增删改操作,获取受影响行数int affectedRows = sqlSession.update(ms.getId(), args[0]); // 简化处理,假设只有一个参数​// 2. 获取Mapper接口方法的实际返回类型Class<?> returnType = method.getReturnType();​// 3. 根据返回类型进行适配转换if (returnType == void.class || returnType == Void.class) {return null; // 返回void的方法,直接忽略返回值} else if (returnType == int.class || returnType == Integer.class) {return affectedRows; // 返回int或Integer,直接返回行数} else if (returnType == boolean.class || returnType == Boolean.class) {return affectedRows > 0; // 返回boolean,判断是否有影响} else if (returnType == long.class || returnType == Long.class) {return (long) affectedRows; // 支持Long类型} else {// 其他不支持的类型,可以抛出异常或进行其他处理throw new RuntimeException("Unsupported return type for update operation: " + returnType);}}

通过这段代码,我们可以看到MyBatis如何优雅地解决了返回值适配的问题。这种设计体现了框架设计的用户友好性原则:框架应该去适应开发者的习惯,而不是让开发者来适应框架的限制。

三、深入思考:这种灵活性的价值与延伸

1. 为什么MyBatis要提供这种灵活性?

这背后是深刻的API设计哲学:

  • 意图导向:方法的返回值应该清晰表达开发者的意图。void insert(...)表示“执行插入,我不关心结果”;boolean delete(...)表示“执行删除,告诉我成功与否”;int update(...)表示“执行更新,告诉我具体影响了多少行”。这让代码更具可读性。

  • 减少冗余代码:如果没有这种适配,开发者需要在业务代码中手动进行判断和转换,例如int rows = sqlSession.update(...); return rows > 0;。MapperProxy将这步操作内置,消除了模板代码。

  • 保持接口简洁性:Mapper接口本身非常干净,不需要任何额外的注解来指定返回值处理方式,框架通过方法签名自动推断。

2. 如何获取自增主键?@Options注解的机制

返回值适配解决了“影响行数”的问题,但增删改操作还有一个常见需求:获取数据库自动生成的主键(如MySQL的AUTO_INCREMENT)。

这在MyBatis中是通过@Options注解或<insert>标签的useGeneratedKeyskeyProperty属性实现的。它的原理是:

  1. 框架层面:MyBatis在执行PreparedStatement时,会通过Statement.RETURN_GENERATED_KEYS参数告知JDBC驱动:“请返回生成的主键”。

  2. JDBC驱动:驱动在执行插入后,会通过statement.getGeneratedKeys()方法返回一个包含生成主键的ResultSet。

  3. 结果处理:MyBatis拿到这个ResultSet后,会使用ResultSetHandler将值解析出来。

  4. 属性注入:最关键的一步,MyBatis通过反射,将解析出的主键值,设置到参数对象的keyProperty指定的属性中。

 // 示例用法@Options(useGeneratedKeys = true, keyProperty = "id")int insertUser(User user);​// 调用后,user对象的id属性会被自动赋值为数据库生成的主键userMapper.insertUser(user);System.out.println("Generated ID: " + user.getId()); // 这里可以取到值

值得注意的是,获取自增主键与Mapper方法的返回值是两个独立的过程。方法返回值仍然是受影响行数(适配后的值),而生成的主键被直接回填到了参数对象中。这种设计非常巧妙,同时满足了获取行数和获取主键两个需求。

四、总结与启示

通过对MapperProxy返回值适配机制的深入剖析,我们看到了一个优秀框架在细节处的深思熟虑。它不仅仅是将JDBC操作简单封装,更是从开发者体验出发,提供了高度灵活和直观的API设计。

这种设计模式的精髓在于:在统一的底层实现(JDBC返回int)之上,构建一个能够理解用户意图并进行智能适配的中间层。这为我们设计自己的API和框架提供了宝贵的经验:

  1. 面向接口设计:从用户的使用场景和意图出发,设计最自然的接口。

  2. 提供适配层:在内部实现中,通过适配器模式消化底层差异,向用户提供一致的体验。

  3. 保持灵活性:通过反射等机制,实现运行时的动态决策,而不是在编译时写死逻辑。

现在,我们的手写MyBatis框架不仅能够执行CRUD,更能以多种形式向用户反馈结果,向着更加成熟和完善的方向迈进了一步。


💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

http://www.dtcms.com/a/357477.html

相关文章:

  • 【完整源码+数据集+部署教程】胚胎发育阶段检测系统源码和数据集:改进yolo11-SCConv
  • 如何从 iCloud 存储中删除消息的 4 种方法
  • ubuntu24.04 QT中配置opencv4.12
  • 引力场能量为负,物质能量为正,这是在存在物质的空间中说的,如果是空无一物的空间呢,引力场能量还是负吗(或者说引力场还存在吗)
  • 2025年09月计算机二级Java选择题每日一练——第十一期
  • Vue3 kkfileview 的使用
  • Hal aidl 模板
  • Django开发规范:构建可维护的AWS资源管理应用
  • 第八章 惊喜01 测试筹备会
  • 【Flask】测试平台开发,产品管理实现编辑功能-第六篇
  • 对接连连支付(七)-- 退款查询
  • CSS设置滚动条显示时机及样式
  • R 语言 + 卒中 Meta 分析(续):机器学习 Meta 与结构方程 Meta 完整实现
  • STM32之IIC详解
  • GY-BMP280压强传感器完整工程stm32控制
  • 嵌入式滤波算法模块
  • 换公司如何快速切入软件项目工程
  • vant Overlay 遮罩层内元素无法滚动解决方案
  • 命令扩展与重定向
  • 【完整源码+数据集+部署教程】硬币分类与识别系统源码和数据集:改进yolo11-SWC
  • 【序列晋升】20 Spring Cloud Function 函数即服务(FaaS)
  • 明远智睿 RK3568 核心板:以硬核性能解锁多领域应用新可能
  • java_web 日志配置
  • KNN算法(K近邻算法)
  • leetcode 191 位1的个数
  • Maven 从 0 到 1:安装、配置与依赖管理一站式指南
  • Ubuntu下的压缩及解压缩
  • 基于SpringBoot的高校科研项目管理系统【2026最新】
  • 《生成式AI消费级应用Top 100——第五版》| a16z
  • Redis-分布式缓存