1.5 Mapper接口与XML的绑定原理


文档摘要

1.5 Mapper 接口与 XML 的绑定原理 本节摘要:Mapper 接口与 XML 的绑定靠三根缝合线:namespace 等于接口全限定名、语句 id 等于方法名、参数与返回类型由代理负责适配。运行期 JDK 动态代理生成 MapperProxy 实现接口,把方法调用翻译成 SqlSession 的执行。本节用一次 BindingException 的排错过程验证这套机制,并对比注解映射的适用边界。 第 1 章收尾的一节,也是全册地基的一节:接口没有实现类却能执行 SQL,这句话每个用过 MyBatis 的人都说过,但能讲清机制的不多。 绑定的三根缝合线 回看已经写过的两块代码: 第三根缝合线看不见:方法参数怎么进 SQL、返回结果怎么变回对象,由运行期的代理对象负责。

1.5 Mapper 接口与 XML 的绑定原理

本节摘要:Mapper 接口与 XML 的绑定靠三根缝合线:namespace 等于接口全限定名、语句 id 等于方法名、参数与返回类型由代理负责适配。运行期 JDK 动态代理生成 MapperProxy 实现接口,把方法调用翻译成 SqlSession 的执行。本节用一次 BindingException 的排错过程验证这套机制,并对比注解映射的适用边界。

第 1 章收尾的一节,也是全册地基的一节:接口没有实现类却能执行 SQL,这句话每个用过 MyBatis 的人都说过,但能讲清机制的不多。

绑定的三根缝合线

回看已经写过的两块代码:

// 接口:包名 com.example.mapper,接口名 UserMapper package com.example.mapper; public interface UserMapper { User findById(int id); List<User> findByStatus(String status); }
<mapper namespace="com.example.mapper.UserMapper"> <!-- 缝合线一:namespace 必须等于接口全限定名 --> <select id="findById" parameterType="int" resultType="com.example.entity.User"> SELECT id, username, email FROM users WHERE id = #{id} <!-- 缝合线二:id 必须等于方法名 --> </select> <select id="findByStatus" resultType="com.example.entity.User"> SELECT id, username, email FROM users WHERE status = #{status} </select> </mapper>

第三根缝合线看不见:方法参数怎么进 SQL、返回结果怎么变回对象,由运行期的代理对象负责。前两根线由人维护,任何一根断开,绑定就失败。

运行期发生了什么

getMapper 拿到的不是你写的类,而是 JDK 动态代理在内存里生成的代理对象。把这条链路逐步展开:

运行期发生了什么

用一段"手写版代理"把机制摊平——下面这段代码与真实实现相比只省略了缓存与异常包装,逻辑同构:

// 手写版 MapperProxy:揭开代理的面纱(示意实现) public class MyMapperProxy implements InvocationHandler { private final SqlSession session; private final Class<?> iface; MyMapperProxy(SqlSession session, Class<?> iface) { this.session = session; this.iface = iface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 核心:接口全限定名 + 方法名 = XML 中的语句标识 String statement = iface.getName() + "." + method.getName(); // 按返回类型选择执行方式 if (method.getReturnType() == List.class) { return session.selectList(statement, args.length > 0 ? args[0] : null); } return session.selectOne(statement, args.length > 0 ? args[0] : null); } } // 使用 JDK 动态代理生成接口实例 UserMapper mapper = (UserMapper) Proxy.newProxyInstance( UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new MyMapperProxy(session, UserMapper.class)); User u = mapper.findById(1); // 实际进入 invoke 方法

读这段代码能解释很多"玄学":为什么方法名拼错要到运行期才报错(拼接发生在运行期);为什么返回 List 与单个对象用同一个 XML 都行(由代理按方法签名选择 selectList 或 selectOne);为什么接口可以随便加 default 方法之外的任何签名(代理不在乎签名,只在乎名字)。

病例:一次 BindingException 的排错

背景:新同事把 UserMapper 从 com.example.mapper 挪到 com.example.dao,改了 Java 代码但忘了动 XML。启动正常,一调用 findById 就抛异常。

症状

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.dao.UserMapper.findById

操作:报错信息里的语句全名是排查的钥匙——它就是代理拼出来的"接口全限定名 + 方法名"。拿着这个名字去注册表里找:XML 的 namespace 还是旧包名 com.example.mapper.UserMapper,两边对不上。

结果:把 namespace 改成 com.example.dao.UserMapper 后恢复。

解读:这类异常的排查顺序固定为三步——查 namespace 是否等于接口全限定名;查语句 id 是否等于方法名;查该 XML 是否真的被 mappers 配置注册(没注册的文件等于不存在)。九成的"not found"出在前两步,剩下一成是注册问题(比如 XML 没被打进包,1.2 节的资源目录坑)。

变式:方法重载在这里行不通。同一接口写 findById(int) 与 findById(long) 两个方法,代理拼出的语句全名相同,无法对应两条不同语句——MyBatis 的映射世界里,方法名就是唯一标识。

注解映射:另一条腿

简单语句可以完全不写 XML,注解直接放在接口方法上:

public interface UserMapper { @Select("SELECT id, username, email FROM users WHERE id = #{id}") User findById(int id); @Insert("INSERT INTO users(username, email) VALUES(#{username}, #{email})") int insert(User user); }

两种风格的边界很清晰:

维度 XML 映射 注解映射
动态 SQL 标签完整支持 需要脚本标记包裹,冗长
SQL 长度 集中管理,长语句可读 挤在方法签名上,长语句难维护
修改生效 改 XML 即可 改 Java 需重新编译
适用 生产项目主力 短小语句、快速原型、工具类

⚠️ 常见坑:注解与 XML 同时定义同一个方法(同 id 同 namespace),启动时直接冲突报错,MyBatis 不做覆盖,只做拒绝。

💡 关键直觉:注解不是"更现代的 XML",而是同一套注册表的两个入口。无论哪条路进来,最终都变成同一个 MappedStatement 对象挂在配置里——所以两种风格可以共存,但不能重叠。

本节要点回顾

  • 三根缝合线:namespace 对全限定名、id 对方法名、参数与返回由代理适配,前两根靠人维护
  • 代理机制:MapperProxy 在运行期把方法调用翻译成"语句全名 + 参数"的会话调用,与手写 JDK 代理同构
  • BindingException 排查三步:namespace、id、注册情况,顺序固定
  • 重载不可用:方法名即语句标识,同名不同参无法区分
  • 注解与 XML 是两个入口一张表:按语句复杂度分工,禁止重叠定义

第 1 章到此收官:器械认全、通道打通、绑定机制拆开。从下一章起,镜头移到手术的入口端,看参数如何装配进 SQL。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U