Mybatis15-Mapper接口的代理+MappedStatement
一、Mapper接口的代理对象的意义第一步没有Mapper接口时我们怎么写代码在MyBatis早期或者说如果你不用Mapper接口你的代码长这样// 1. 拿到SqlSession SqlSession session sqlSessionFactory.openSession(); // 2. 直接调用CRUD方法 // 参数1SQL的ID字符串参数2传入的参数 User user session.selectOne(com.example.mapper.UserMapper.selectById, 1);这段代码能跑但有几个很严重的设计缺陷弊端1字符串硬编码编译器不检查com.example.mapper.UserMapper.selectById是一个字符串。如果你手滑写错了比如把selectById写成了selectByID编译时完全不会报错只有运行到这行代码时才会抛出异常。弊端2参数类型不安全selectOne的第二个参数是Object类型。你可以传一个String进去也可以传一个Date进去编译器都不会阻止你。但如果XML里期望的是int运行时就可能类型转换失败。弊端3返回类型需要强制转换selectOne返回的是Object你需要自己强转成User。如果XML里配置的返回类型和你要转的类型不匹配又是运行时才能发现。弊端4IDE无法提供有效支持因为一切都是字符串和Object你的IDE无法做方法跳转、参数提示、重构重命名。你想改个SQL的ID全局搜索替换很容易漏改或改错。第二步于是Mapper接口被设计出来了为了解决上面的问题MyBatis引入了Mapper接口public interface UserMapper { User selectById(int id); ListUser selectAll(); int insertUser(User user); }这个接口定义了方法名、参数类型、返回值类型。但这里立刻出现了一个关键问题接口只有定义没有实现。UserMapper是一个接口你不能new UserMapper()。那谁来执行真正的SQL逻辑呢第三步如果让你写实现类问题又回来了假设MyBatis要求你自己写实现类public class UserMapperImpl implements UserMapper { private SqlSession sqlSession; public UserMapperImpl(SqlSession sqlSession) { this.sqlSession sqlSession; } Override public User selectById(int id) { // 绕了一圈又回到了字符串调用 return sqlSession.selectOne( com.example.mapper.UserMapper.selectById, id); } Override public ListUser selectAll() { return sqlSession.selectList( com.example.mapper.UserMapper.selectAll); } // ... 每个方法都如此 }你发现了吗如果让你手写实现类之前所有弊端一个都没解决——你还是在写字符串、还是在做类型转换、代码还是臃肿。而且每增加一个Mapper接口你就要多写一个实现类全是样板代码Boilerplate。所以MyBatis的设计者想既然每个实现类的逻辑都是固定的——根据方法名找到SQL执行返回结果——那为什么不交给框架自动生成呢第四步代理对象登场——框架帮你实现接口这就是sqlSession.getMapper(UserMapper.class)做的事情。它不会返回null也不会返回一个你手写的UserMapperImpl。它返回的是一个代理对象Proxy Object。这个代理对象在运行时被动态创建它实现了UserMapper接口所以你完全可以把它当成UserMapper来用UserMapper mapper sqlSession.getMapper(UserMapper.class); // 这行代码能编译通过因为返回的对象确实实现了UserMapper接口 User user mapper.selectById(1);核心问题代理对象里并没有真正的业务逻辑那selectById(1)是怎么执行的第五步JDK动态代理 MapperProxy 的工作机制MyBatis使用的是JDK动态代理。要理解它你需要知道三个角色1. 接口UserMapper定义了长什么样——有哪些方法参数和返回值是什么。2. 代理对象运行时动态生成的类它实现了UserMapper接口所以你可以把它赋值给UserMapper类型的变量。3. 调用处理器MapperProxy这是真正干活的人。它实现了InvocationHandler接口。当你调用mapper.selectById(1);实际上JVM并没有执行某个类里写好的selectById方法而是把这个调用拦截下来转交给了MapperProxy的invoke方法。MapperProxy.invoke里大概做了这些事获取方法信息知道你调用的是selectById参数是1解析SQL定位根据接口全限定名 方法名找到XML或注解中对应的SQL语句调用SqlSession最终还是会走到sqlSession.selectOne(...)但这个过程对你是透明的结果映射把查询结果转换成User类型返回// 伪代码帮你理解流程 public Object invoke(Object proxy, Method method, Object[] args) { // 1. 拿到方法名selectById String methodName method.getName(); // 2. 拿到接口名com.example.mapper.UserMapper String interfaceName method.getDeclaringClass().getName(); // 3. 组合成SQL IDcom.example.mapper.UserMapper.selectById String statementId interfaceName . methodName; // 4. 根据返回类型决定调用selectOne还是selectList if (method.getReturnType() List.class) { return sqlSession.selectList(statementId, args[0]); } else { return sqlSession.selectOne(statementId, args[0]); } }第六步为什么必须用代理不用行不行不用代理你有两个选择但都有致命缺陷方案缺陷直接调用SqlSession字符串硬编码、无类型安全、无IDE支持手写实现类样板代码爆炸、维护困难、没有解决本质问题代理模式是唯一的出路因为它同时解决了类型安全你操作的是UserMapper接口方法参数和返回值都是强类型的编译器会检查。无字符串硬编码方法名就是SQL ID通过反射自动获取写错方法名编译期就能发现。零样板代码你不需要写实现类框架动态生成。IDE友好你可以Ctrl点击跳转可以安全重构重命名。第七步为什么是JDK动态代理JDK动态代理有一个硬性要求被代理的必须是接口。MyBatis的Mapper天然就是接口所以完美契合。它的本质是java.lang.reflect.Proxy.newProxyInstance(...)在运行时生成一个类大概长这样伪代码// 这是JVM在内存中动态生成的类你看不到源码 public class $Proxy0 implements UserMapper { private InvocationHandler handler; // 这就是MapperProxy public User selectById(int id) { // 所有方法调用都转发给handler return (User) handler.invoke(this, selectById方法对象, new Object[]{id}); } }$Proxy0这个类是运行时临时生成的字节码它实现了UserMapper并把每个方法调用都委托给MapperProxy。总结逻辑链条因为直接调用SqlSession有字符串硬编码、类型不安全、维护困难等弊端所以引入了Mapper接口利用Java的类型系统提供编译期检查但是接口不能实例化需要有人来实现接口里的方法如果让开发者手写实现类会写大量样板代码且本质上还是调用SqlSession没有解决问题因此MyBatis使用JDK动态代理由框架在运行时自动生成接口的实现代理对象最终MapperProxy拦截所有方法调用自动解析方法名、找到SQL、执行并返回结果。你拿到的mapper对象本质上是一个由MyBatis自动生成的、会帮你转发请求到SqlSession的接口实现。你表面上在调用接口方法实际上MyBatis在背后帮你完成了找SQL → 传参数 → 执行 → 转结果的一整套流程。这就是代理的意义让你用优雅的方式调用接口方法去做原本繁琐且易错的事情操作SqlSession。二、MappedStatement 类的讲解第一步如果没有MappedStatement执行SQL会面临什么困境假设MyBatis没有MappedStatement这个概念最原始的执行方式可能是这样// 伪代码直接传SQL字符串 session.execute(SELECT * FROM user WHERE id ?, 1);但这远远不够。一条SQL要正确执行并返回正确结果框架至少需要知道参数类型?对应的Java类型是什么是int、String还是User对象这决定了JDBC的setXxx方法用哪个。返回类型查询结果要封装成User对象还是Map还是ListStringSQL命令类型这是SELECT还是INSERT如果是INSERT可能需要获取数据库自增的主键如果是SELECT需要返回结果集。结果映射规则数据库字段名叫user_nameJava属性叫userName这个映射关系怎么告诉框架动态SQL这条SQL可能包含if标签需要根据参数动态拼接不是固定的字符串。缓存配置这条SQL的结果要不要走二级缓存执行这条SQL前要不要清空缓存超时时间这条SQL最多执行多久Statement类型用普通的Statement、预编译的PreparedStatement还是存储过程的CallableStatement如果把这些信息都作为execute方法的参数API会变成这样session.execute( SELECT * FROM user WHERE id ?, // SQL 1, // 参数 Integer.class, // 参数类型 User.class, // 返回类型 resultMap, // 结果映射规则 SqlCommandType.SELECT, // 命令类型 true, // 是否使用缓存 false, // 是否刷新缓存 5000, // 超时毫秒 StatementType.PREPARED // Statement类型 );弊端非常明显每次执行SQL都要传一堆参数极易遗漏、顺序搞错这些信息其实是固定不变的写在XML里就不会变但每次执行都要重复传递动态SQL的解析逻辑无处安放不可能每次执行都重新解析XML第二步一条SQL的所有信息本质上是一个整体让我们换个角度思考你在UserMapper.xml里写的每一个select、insert、update、delete标签其实都在描述同一件事——如何执行一条特定的SQL。比如select idselectById parameterTypeint resultTypeUser useCachetrue timeout5000 SELECT * FROM user WHERE id #{id} /select这个标签里包含了idselectById唯一标识SQL文本SELECT * FROM user WHERE id ?参数类型int返回类型User缓存配置useCachetrue超时配置timeout5000这些信息是内聚的——它们只和根据ID查询用户这一条SQL相关。既然它们是内聚的代码设计上就应该把它们封装成一个对象。这就是MappedStatement的设计来源一个MappedStatement对象 一条SQL语句 执行这条SQL所需的全部元信息第三步MappedStatement里面到底装了什么MappedStatement是一个普通的Java类虽然名字叫Statement但它不是JDBC的Statement核心属性如下public final class MappedStatement { private String id; // 唯一标识格式namespace . id private SqlSource sqlSource; // SQL源封装了动态SQL解析逻辑 private SqlCommandType sqlCommandType; // SELECT / INSERT / UPDATE / DELETE private Class? parameterType; // 参数Java类型 private ListResultMap resultMaps; // 结果映射配置 private boolean flushCacheRequired; // 执行前是否清空二级缓存 private boolean useCache; // 是否使用二级缓存 private Integer timeout; // 超时时间 private StatementType statementType; // STATEMENT / PREPARED / CALLABLE // ... 还有其他属性 }注意这里有一个关键设计sqlSource的类型不是String而是SqlSource。因为MyBatis支持动态SQLif、foreach等SQL文本不是固定的。SqlSource的职责是根据运行时传入的参数生成最终可执行的SQL生成一个叫BoundSql的对象。所以MappedStatement不仅封装了静态配置还封装了动态SQL的解析逻辑。第四步MappedStatement解决了哪些具体问题1. 信息聚合告别散弹枪式传参以前执行SQL需要传10个分散的参数现在只需要传一个MappedStatement对象。所有和这条SQL相关的配置都在它内部调用方和执行方都轻松。2. 启动时解析运行时复用MappedStatement在MyBatis初始化阶段解析XML或扫描注解时就被创建好了然后存入内存。运行时执行SQL时直接取出来用不需要重复解析XML性能上完全可控。3. 统一执行器的调度入口SqlSession底层委托给Executor执行。Executor的所有执行方法都接收MappedStatement作为参数// Executor接口定义 E ListE query(MappedStatement ms, Object parameter, ...); int update(MappedStatement ms, Object parameter);Executor拿到MappedStatement后就能独立完成全部操作调用ms.getSqlSource().getBoundSql(parameter)→ 得到最终SQL查看ms.getSqlCommandType()→ 知道是查询还是更新查看ms.getResultMaps()→ 知道怎么把ResultSet转成Java对象查看ms.getStatementType()→ 知道创建哪种JDBC StatementMappedStatement让执行器具备了自解释的能力——执行器不需要外部再告诉它任何额外信息。4. 动态SQL的天然载体如果没有MappedStatement动态SQL的逻辑放在哪里MappedStatement内部的SqlSource完美解决了这个问题。它把根据参数生成SQL的逻辑封装了起来对外只暴露一个getBoundSql(parameter)方法。第五步MappedStatement在MyBatis中的完整生命周期阶段1解析阶段应用启动时当你写了一个Mapper XMLmapper namespacecom.example.mapper.UserMapper select idselectById resultTypeUser SELECT * FROM user WHERE id #{id} /select /mapperMyBatis的XMLMapperBuilder会解析这个select标签// 伪代码 MappedStatement.Builder builder new MappedStatement.Builder( configuration, // 全局配置 com.example.mapper.UserMapper.selectById, // id sqlSource, // 解析出的SQL源 SqlCommandType.SELECT // 命令类型 ); builder.resultType(User.class); // ... 设置其他属性 MappedStatement ms builder.build(); configuration.addMappedStatement(ms); // 注册到Configuration阶段2存储阶段在Configuration中public class Configuration { // key: namespace.id value: MappedStatement protected final MapString, MappedStatement mappedStatements new StrictMapMappedStatement(); }所有解析好的MappedStatement都存在这个Map里。这就是为什么SQL ID不能重复——它是Map的key。阶段3执行阶段运行时当你调用User user mapper.selectById(1);内部的调用链是这样的MapperProxy.invoke()拦截到selectById方法调用组合出IDcom.example.mapper.UserMapper.selectById从Configuration.mappedStatements中根据ID取出对应的MappedStatement调用sqlSession.selectOne(mappedStatement, 1)——注意这里传的是对象不是字符串Executor从MappedStatement中提取所有信息完成JDBC操作第六步为什么不能直接用字符串ID非要包装成对象你可能会问既然最终也是根据namespace.id找到SQL为什么不能直接传字符串ID让框架每次执行时去XML里查答案是三个层面的问题层面原因性能每次执行都解析XML是不可接受的。MappedStatement在启动时解析一次后续内存中直接复用。信息完整性字符串ID只能定位到SQL文本但无法携带参数类型、返回类型、缓存配置等元信息。动态SQL动态SQL需要根据参数实时生成最终SQL这个逻辑封装在MappedStatement持有的SqlSource中不是简单的字符串查找。总结逻辑链条因为执行一条SQL需要大量元信息参数类型、返回类型、SQL类型、缓存配置、超时设置等如果分散传递会导致API臃肿、调用混乱、极易出错所以MyBatis设计了MappedStatement把一条SQL的所有执行元信息封装成一个对象因为这些信息是固定不变的可以在应用启动时解析XML/注解一次性准备好所以MappedStatement被创建后存入Configuration的Map中以namespace.id为键运行时直接复用因为执行时只需要一个MappedStatement对象就能拿到SQL文本、参数映射、结果映射、缓存策略等全部信息所以SqlSession和Executor的执行方法都接收MappedStatement作为核心参数内部从中提取所有配置完成完整的数据库操作。MappedStatement就是MyBatis中一条SQL的完整执行档案。它让SQL的执行从临时拼凑参数变成了有备而来、一应俱全。

相关新闻

想找靠谱的中国谷歌SEO公司?大鱼营销用真实数据帮你提升海外排名。

想找靠谱的中国谷歌SEO公司?大鱼营销用真实数据帮你提升海外排名。

在全球化数字营销的浪潮中,许多中国外贸企业都面临同一个难题:如何找到一家真正靠谱、能带来真实效果的谷歌SEO公司?市场上有太多的服务商,但往往缺乏透明度、数据支撑,或者优化手法落后,导致企业投入了大量…

2026/7/24 0:38:12阅读更多 →
设计EDA研发总监——八维度综合人才评估报告

设计EDA研发总监——八维度综合人才评估报告

评估岗位:设计EDA研发总监(高层技术管理岗) 评估定位:公司EDA技术一号位、部门负责人;主打战略规划、体系搭建、团队经营、技术壁垒构建、重大攻关决策、跨事业部顶层协同。统筹全公司EDA研发体系、平台建设、国产化替代、先进工艺预研、技术人才梯队与部门经营指标,管辖…

2026/7/24 0:36:11阅读更多 →
【经济、大数据、人工智能可投、EI稳定检索】第五届信息经济、数据建模与云计算国际学术会议(ICIDC2026)

【经济、大数据、人工智能可投、EI稳定检索】第五届信息经济、数据建模与云计算国际学术会议(ICIDC2026)

第五届信息经济、数据建模与云计算国际学术会议(ICIDC 2026)定于2026年8月28-30日在中国-哈尔滨举行。会议将继续围绕信息经济、数据建模和云计算等研究领域展开讨论。会议旨在为从事经济、数据建模和云计算的专家学者提供一个共享科研成果和前沿技术,了解学术发展趋…

2026/7/24 0:36:11阅读更多 →
TSB43Cx43A芯片实现S/PDIF音频在IEEE 1394总线上的协议转换与同步传输

TSB43Cx43A芯片实现S/PDIF音频在IEEE 1394总线上的协议转换与同步传输

1. 项目概述与核心价值在专业音频制作、广播系统或高端家庭影院搭建中,我们常常会遇到一个经典问题:如何将一台设备上的S/PDIF数字音频信号,稳定、低延迟地传输到另一台设备,尤其是当这两台设备物理距离较远,或者需要融…

2026/7/24 2:08:29阅读更多 →
从DRV2667EVM-CT评估板解析压电触觉驱动硬件设计要点

从DRV2667EVM-CT评估板解析压电触觉驱动硬件设计要点

1. 项目概述与核心价值如果你正在为你的下一个消费电子或工业设备项目寻找一种能够提供细腻、精准触觉反馈的解决方案,那么基于压电执行器的触觉驱动方案绝对值得你深入研究。传统的偏心转子马达(ERM)和线性谐振致动器(LRA&#x…

2026/7/24 2:08:29阅读更多 →
OpenClaw:开源AI助手的十大核心技能与应用场景

OpenClaw:开源AI助手的十大核心技能与应用场景

1. OpenClaw:重新定义个人AI助手的边界第一次在Telegram里收到OpenClaw自动整理的会议纪要时,我盯着手机屏幕愣了三秒——这个运行在我旧Mac mini上的开源项目,刚刚完成了本该由人类助理耗时半小时的工作。作为一款能真正执行任务而非仅对话的…

2026/7/24 2:08:29阅读更多 →
【2026年百度暑期实习/春招- 7月23日-算法岗-第三题- 格子行走翻转】(题目+思路+JavaC++Python解析+在线测试)

【2026年百度暑期实习/春招- 7月23日-算法岗-第三题- 格子行走翻转】(题目+思路+JavaC++Python解析+在线测试)

题目内容 有一条从 111 开始编号的无限长一维格子,初始时,所有编号为质数的格子为黑格子,其余格子为白格子。共有 nnn 个人依次出发,且都从格子 111

2026/7/24 2:08:29阅读更多 →
【2026年百度暑期实习/春招- 7月23日-算法岗-第二题- 魔法藤蔓】(题目+思路+JavaC++Python解析+在线测试)

【2026年百度暑期实习/春招- 7月23日-算法岗-第二题- 魔法藤蔓】(题目+思路+JavaC++Python解析+在线测试)

题目内容 Tk有一个魔法藤蔓,这个藤蔓有 nnn 个魔法段,这个魔法藤蔓从头到尾均有一个魔法硬度 { a1,a2,…,an}\{a_1,a_2,\dots,a_n\}{ a

2026/7/24 2:08:29阅读更多 →
STGCN时空图卷积网络:交通预测核心技术解析

STGCN时空图卷积网络:交通预测核心技术解析

1. STGCN论文核心思想解析 STGCN(Spatio-Temporal Graph Convolutional Network)是2018年AAAI会议上提出的时空图卷积网络模型,专门用于处理交通预测等时空序列数据。这个模型创新性地将图卷积网络(GCN)与时间卷积网络…

2026/7/24 2:06:29阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 0:58:53阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 0:58:53阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 0:58:53阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:06阅读更多 →
【LeetCode 54】螺旋矩阵

【LeetCode 54】螺旋矩阵

问题描述: 解法: 1、模拟(参考自【LeetCode 54】螺旋矩阵-CSDN博客) int *spiralOrder(int **matrix, int matrixSize, int *matrixColSize, int *returnSize) {static const int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, …

2026/7/24 0:00:06阅读更多 →
2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

知春路不相信模型领先今年WAIC大会,昔日AI六小龙来了五家,分别是Kimi、阶跃星辰、Minimax、百川智能、零一万物。连放弃基模的百川和零一万物都来了,唯一缺席的竟是近几个月来风光无限的智谱。(DeepSeek一直不参加)WAI…

2026/7/24 0:00:06阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/23 22:58:43阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →