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的执行从临时拼凑参数变成了有备而来、一应俱全。

相关新闻

影刀RPA 自动化简历筛选系统:HR提效实战

影刀RPA 自动化简历筛选系统:HR提效实战

影刀RPA 自动化简历筛选系统:HR提效实战 作者:林焱 写在前面 招聘旺季,HR每天要看几百份简历,光靠人工筛选效率极低。影刀可以帮HR搭一套自动简历筛选系统:批量解析PDF/Word简历 → 按岗位要求评分 → 自动分类 → 生…

2026/10/17 8:33:28 阅读更多 →
影刀RPA 自动化测试入门:用RPA做冒烟测试

影刀RPA 自动化测试入门:用RPA做冒烟测试

title: “影刀RPA 自动化测试入门:用RPA做冒烟测试” date: 2026-07-01 author: 林焱 影刀RPA 自动化测试入门:用RPA做冒烟测试 每次发版后要手工点一遍主流程确认没问题,费时费力还容易漏测。用影刀做冒烟测试,让机器人替你点一…

2026/10/17 8:14:56 阅读更多 →
设计EDA研发总监——八维度综合人才评估报告

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

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

2026/10/17 8:37:08 阅读更多 →

最新新闻

免费解锁 Wand(WeMod) 的 Pro 功能与手机远程面板:Wand-Enhancer 从构建到打补丁完整教程

免费解锁 Wand(WeMod) 的 Pro 功能与手机远程面板:Wand-Enhancer 从构建到打补丁完整教程

免费解锁 Wand(WeMod) 的 Pro 功能与手机远程面板:Wand-Enhancer 从构建到打补丁完整教程 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer …

2026/10/18 4:44:34 阅读更多 →
python项目管理

python项目管理

一、中医学习工具 1.项目名称:TCM 2.定稿时间:2026-09-22 3.定搞文件:t59.py 4.开发历程:中医辩证窗口实现 - DeepSeek 5.项目文件: 开发文件夹:D:\My Program\HaiSuan\ChinaYixue备份文件:D:\海算档案\01知识产…

2026/10/18 4:44:34 阅读更多 →
RTSP客户端源码拆解:状态机、RTP/RTCP与音画同步机制

RTSP客户端源码拆解:状态机、RTP/RTCP与音画同步机制

简介:这是一份面向C/C开发者的RTSP客户端源码与测试例程包,基于标准RTSP协议实现,可应用于IP监控、在线教育、直播等领域,用于测试和验证服务器交互能力,适合需要学习协议控制、搭建客户端或调试服务器的开发者。资源共…

2026/10/18 4:44:34 阅读更多 →
容器中ROS节点连接宿主机roscore:网络原理与bridge模式配置指南

容器中ROS节点连接宿主机roscore:网络原理与bridge模式配置指南

算下来我在容器里跑 ROS 节点已经折腾过好几个年头了,从早期的 melodic 到现在的 noetic,最常被问到的一个问题就是:宿主机上有 roscore,容器里的节点怎么连过去?标题里那个[特殊字符]标记得挺有意思,实际开…

2026/10/18 4:44:33 阅读更多 →
JavaWeb电子商城毕设:JSP+Servlet+JDBC+MySQL8实战

JavaWeb电子商城毕设:JSP+Servlet+JDBC+MySQL8实战

简介:面向毕业设计的JavaWeb电子商城系统源码与数据库配套包,采用JSPServletJSTLELJDBC经典技术栈,数据库为MySQL8,开发环境为EclipseTomcat7JDK10,适合计算机专业学生用于课程设计、毕业设计参考、功能扩展或二次开发…

2026/10/18 4:44:33 阅读更多 →
x3650 M5服务器部署与维护实战:从IMM2到RAID配置

x3650 M5服务器部署与维护实战:从IMM2到RAID配置

简介:《Lenovo System x3650 M5-5462 安装维护指南》是一份面向服务器管理员、运维工程师和IT技术支持人员的官方设备手册,围绕该机型从部署、配置到售后维护的完整链路展开。内容先解析前后面板、操作员信息面板、LCD显示面板、导轨与机架规格以及服务器…

2026/10/18 4:43:33 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →