资讯详情

CodeGuide 深度解析:MyBatis Mapper 接口没有实现类,动态代理源码链路全拆解

📅 2026/9/24 16:44:37 | 华诺云谱 👁 阅读
CodeGuide 深度解析:MyBatis Mapper 接口没有实现类,动态代理源码链路全拆解
文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载本文基于 CodeGuide 仓库中《面经手册 · 第35篇》的完整论述展开并辅以仓库内《Mybatis 手撸专栏》第2章映射器代理工厂、第3章映射器注册与《Mybatis 接口没有实现类为什么可以执行增删改查》源码分析等资料进行交叉印证。读完本文你将能说清getMapper到PreparedStatement.execute之间的每一层职责、MapperMethod如何路由 SQL 类型、Mapper 方法为何不能重载以及 namespace 的三重身份从知道动态代理升级为真懂动态代理。一、从一段平平无奇的代码说起如果你用过 MyBatis一定写过这样的代码UserMapper userMapper sqlSession.getMapper(UserMapper.class); User user userMapper.findById(1L);看起来平平无奇但仔细想想——UserMapper是一个接口没有实现类findById方法也没有方法体那这行代码到底是怎么执行的谁来实现了这个接口方法调用怎么就变成了 SQL 执行这就是 MyBatis 最巧妙的设计用 JDK 动态代理为 Mapper 接口生成了实现类让你以面向接口的方式操作数据库而不需要写任何实现代码。但面试官不会只问动态代理四个字就放过你。MapperProxy 是什么时候创建的invoke 方法里做了什么MapperMethod 又是怎么路由到 SqlSession 的方法能不能重载namespace 到底绑定了什么——这些才是区分背过八股和真看过源码的分水岭。二、面试题谢飞机的再战谢飞机小记上次背了#{}和${}的区别这次信心满满又来了。面试官谢飞机MyBatis 的 Mapper 接口没有实现类它是怎么工作的谢飞机动态代理MyBatis 用 JDK 动态代理给 Mapper 接口生成了代理对象。面试官嗯那代理对象的 invoke 方法里做了什么谢飞机呃... 好像是调了 SqlSession 的方法面试官中间还有 MapperMethod你知道它的作用吗谢飞机MapperMethod... 就是封装了一下面试官封装了什么SqlSession 那么多方法它怎么知道该调 select 还是 insert谢飞机这个... 根据方法名面试官Mapper 接口的方法能重载吗为什么谢飞机应该... 不能因为... 同名方法会冲突面试官冲突在哪你说清楚。谢飞机我... 再见ヾ(▽)这轮对话暴露出的四个追问点——代理对象怎么创建、invoke 里做了什么、MapperMethod 怎么路由、为什么不能重载——正是本文要逐一拆解的核心。三、Mapper 接口工作原理全链路1. 从 getMapper 说起一切从sqlSession.getMapper(UserMapper.class)开始SqlSession.getMapper(UserMapper.class) → Configuration.getMapper(UserMapper.class, sqlSession) → MapperRegistry.getMapper(UserMapper.class, sqlSession) → MapperProxyFactory.newInstance(sqlSession) → Proxy.newProxyInstance(...) ← JDK 动态代理创建代理对象最终返回的不是UserMapper的实现类实例而是一个JDK 动态代理对象它的 InvocationHandler 是MapperProxy。这条链路在仓库的源码分析资料中被完整还原过DefaultSqlSession.getMapper→Configuration.getMapper→MapperRegistry.getMapper→MapperProxyFactory.newInstance可参见 Mybatis 接口没有实现类为什么可以执行增删改查。2. 调用链路总览代理对象拿到手之后每次调用接口方法都会走这样一条调用链userMapper.findById(1L) → MapperProxy.invoke() ← 代理拦截 → MapperMethod.execute() ← 方法路由 → SqlSession.selectOne() ← 具体 SQL 执行 → Executor.query() ← 执行器 → StatementHandler.query() ← 语句处理器 → PreparedStatement.execute() ← JDBC 执行一句话概括接口方法调用 → 代理拦截 → 方法路由 → SqlSession 执行 SQL。四、源码追踪从注册到执行1. MapperRegistry — Mapper 的注册中心MyBatis 启动时会解析所有的 Mapper 接口和 XML 映射文件把每个接口注册到MapperRegistry中// org.apache.ibatis.binding.MapperRegistry public class MapperRegistry { // 核心接口 → MapperProxyFactory 的映射 private final MapClass?, MapperProxyFactory? knownMappers new HashMap(); public T void addMapper(ClassT type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException(Type type is already known to the MapperRegistry.); } try { // 为每个接口创建一个 MapperProxyFactory knownMappers.put(type, new MapperProxyFactory(type)); } catch (Exception e) { throw new BindingException(Error adding mapper., e); } } } SuppressWarnings(unchecked) public T T getMapper(ClassT type, SqlSession sqlSession) { final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type); if (mapperProxyFactory null) { throw new BindingException(Type type is not known to the MapperRegistry.); } try { // 通过工厂创建代理实例 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException(Error getting mapper instance., e); } } }关键发现MapperRegistry内部维护了一个knownMappers集合Key 是 Mapper 接口的 Class 对象Value 是MapperProxyFactory。每个 Mapper 接口对应一个工厂由工厂负责创建代理对象。注意addMapper中还有一层守护逻辑只有type.isInterface()为 true 的接口才会被注册这从注册源头就锁定了 Mapper 必须是接口的事实。注册的触发入口有两种XML 方式XMLMapperBuilder.bindMapperForNamespace()在解析完 XML 后尝试用 namespace 反查接口类若存在则调用configuration.addMapper(boundType)完成注册详见 源码分析文档 中的bindMapperForNamespace片段包扫描方式mapperspackage name...//mappers或 Spring 的MapperScannerConfigurer扫描指定包逐个调用addMapper。2. MapperProxyFactory — 代理对象的工厂// org.apache.ibatis.binding.MapperProxyFactory public class MapperProxyFactoryT { private final ClassT mapperInterface; private final MapMethod, MapperMethod methodCache new ConcurrentHashMap(); public MapperProxyFactory(ClassT mapperInterface) { this.mapperInterface mapperInterface; } SuppressWarnings(unchecked) protected T newInstance(MapperProxyT mapperProxy) { // JDK 动态代理创建实现 mapperInterface 接口的代理对象 return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy ); } public T newInstance(SqlSession sqlSession) { // 创建 MapperProxyInvocationHandler final MapperProxyT mapperProxy new MapperProxy(sqlSession, mapperInterface, methodCache); // 创建代理对象 return newInstance(mapperProxy); } }关键发现methodCache是一个ConcurrentHashMap缓存了Method → MapperMethod的映射。同一个方法不会重复创建MapperMethod对象。Proxy.newProxyInstance()是 JDK 动态代理的核心 API它要求目标必须是接口这正是 Mapper 只能是接口的原因。这里还藏着一个工厂方法模式的应用newInstance(SqlSession)负责组装MapperProxy再委托给newInstance(MapperProxy)创建代理对象。仓库的《Mybatis 手撸专栏》第2章正是围绕这一对类展开的简化实现可以对照阅读 第2章创建简单的映射器代理工厂。3. MapperProxy — 代理拦截的核心// org.apache.ibatis.binding.MapperProxy public class MapperProxyT implements InvocationHandler, Serializable { private final SqlSession sqlSession; private final ClassT mapperInterface; private final MapMethod, MapperMethod methodCache; public MapperProxy(SqlSession sqlSession, ClassT mapperInterface, MapMethod, MapperMethod methodCache) { this.sqlSession sqlSession; this.mapperInterface mapperInterface; this.methodCache methodCache; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果调用的是 Object 的方法toString、hashCode、equals 等直接执行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 从缓存获取或创建 MapperMethod然后执行 return cachedMapperMethod(method).execute(sqlSession, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k - new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }关键发现invoke是代理拦截的入口。每次调用 Mapper 接口的方法都会走进这个方法。如果调用的是Object类的方法如toString()不走代理逻辑直接反射调用。在更高版本源码中还额外增加了对接口 default 方法的判断isDefaultMethodMethodHandles.Lookup调用这一细节在仓库的 源码分析文档 的MapperProxy完整代码中有体现。核心逻辑就一行cachedMapperMethod(method).execute(sqlSession, args)——找到对应的MapperMethod把SqlSession和参数传给它执行。MapperMethod有缓存computeIfAbsent原子写入不会每次调用都新建。4. MapperMethod — 方法路由器// org.apache.ibatis.binding.MapperMethod public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class? mapperInterface, Method method, Configuration config) { this.command new SqlCommand(config, mapperInterface, method); this.method new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: Object param method.convertArgsToSqlCommandParam(args); result sqlSession.insert(command.getName(), param); break; case UPDATE: param method.convertArgsToSqlCommandParam(args); result sqlSession.update(command.getName(), param); break; case DELETE: param method.convertArgsToSqlCommandParam(args); result sqlSession.delete(command.getName(), param); break; case SELECT: if (method.returnsVoid() method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result null; } else if (method.returnsMany()) { result executeForMany(sqlSession, args); } else if (method.returnsMap()) { result executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result executeForCursor(sqlSession, args); } else { // 默认返回单个对象 param method.convertArgsToSqlCommandParam(args); result sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result sqlSession.flushStatements(); break; default: throw new BindingException(Unknown execution method for: command.getName()); } // 返回值校验 if (result null method.getReturnType().isPrimitive() !method.returnsVoid()) { throw new BindingException(Mapper method command.getName() attempted to return null from a method with a primitive return type ( method.getReturnType() ).); } return result; } }关键发现MapperMethod内部有两个重要组件SqlCommandSQL 命令信息和MethodSignature方法签名信息。execute方法根据command.getType()决定调用SqlSession的哪个方法select/insert/update/delete。对于 SELECT还要根据返回值类型单个对象、列表、Map、游标、ResultHandler做进一步路由returnsMany走executeForManyreturnsMap走executeForMapreturnsCursor走executeForCursor都对应SqlSession上不同的查询 API。command.getName()返回的是namespace.id格式的全限定名这就是定位MappedStatement的唯一标识。execute 末尾还有一道返回值防线如果方法返回基本类型如int但结果为 null直接抛BindingException避免拆箱 NPE 出现在业务层。这里注意一个细节差异INSERT/UPDATE/DELETE 的SqlSession.insert/update/delete返回的是影响行数int而 MapperMethod 中部分版本还会用rowCountResult做包装将行数转换为方法声明的返回类型Integer/Long/Boolean 均可说明路由层对返回值做了弹性适配。5. SqlCommand — SQL 命令的元信息// org.apache.ibatis.binding.MapperMethod.SqlCommand public static class SqlCommand { private final String name; // MappedStatement 的 ID namespace . 方法名 private final SqlCommandType type; // SQL 类型SELECT / INSERT / UPDATE / DELETE public SqlCommand(Configuration configuration, Class? mapperInterface, Method method) { final String methodName method.getName(); final String declaringClass method.getDeclaringClass().getName(); String statementName declaringClass . methodName; // ← 关键全限定名 if (configuration.hasStatement(statementName)) { this.name statementName; } else { // 兜底用接口全限定名 方法名 String debugStatementName mapperInterface.getName() . methodName; this.name debugStatementName; } this.type configuration.getMappedStatement(this.name).getSqlCommandType(); } }关键发现statementName的构造方式是接口全限定名 . 方法名这正好对应 XML 中select idfindById和namespacecom.example.UserMapper的组合。这就是 Mapper 接口方法和 XML 映射 SQL 的绑定纽带。SqlCommandType枚举SELECT/INSERT/UPDATE/DELETE/FLUSH 等是在解析 XML 时根据标签类型写入MappedStatement的execute的 switch 分支正是依据它完成方法调用 → SqlSession API的路由分发。五、方法重载问题1. 为什么 Mapper 接口方法不能重载Java 允许接口方法重载public interface UserMapper { ListUser findByIds(ListLong ids); ListUser findByIds(Long[] ids); // 编译通过但运行时会出问题 }但 MyBatis 中不允许方法重载。原因要从MappedStatement的 ID 构造规则说起。2. 根因分析MappedStatement的唯一标识 namespace . 方法名。接口com.example.UserMapper 方法1ListUser findByIds(ListLong ids) → MappedStatement ID: com.example.UserMapper.findByIds 方法2ListUser findByIds(Long[] ids) → MappedStatement ID: com.example.UserMapper.findByIds ↑ 两个方法生成了相同的 IDMyBatis 在解析 XML 映射文件时每个select/insert/update/delete标签都会生成一个MappedStatement对象以namespace.id作为唯一 Key 存入Configuration的mappedStatements集合StrictMap// org.apache.ibatis.session.Configuration protected final MapString, MappedStatement mappedStatements new StrictMap(Mapped Statements collection); public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); // 如果 ID 重复StrictMap 会抛异常 }StrictMap不允许重复 Key// org.apache.ibatis.session.Configuration.StrictMap Override public V put(String key, V value) { if (containsKey(key)) { throw new IllegalArgumentException(name already contains value for key); } return super.put(key, value); }两种冲突场景XML 方式两个重载方法对应同一个select idfindByIdsID 重复 → 启动时抛异常。注解方式两个重载方法都加了Select生成的MappedStatementID 相同 → 启动时抛异常。仓库的面经手册第34篇在总结设计模式与绑定规则时也特别点出namespace 方法名例如com.example.UserMapper.findById是 MyBatis 定位语句的 ID 规则这也是 Mapper 接口方法不能重载的原因——ID 必须唯一见 第34篇《MyBatis 工作原理是什么》。3. 结论Mapper 接口方法不能重载因为MappedStatement的 ID 只由namespace 方法名组成不包含参数签名。重载方法同名会导致 ID 冲突。六、接口绑定的两种方式1. XML 绑定通过 XML 映射文件的namespace指定 Mapper 接口的全限定名!-- UserMapper.xml -- mapper namespacecom.example.mapper.UserMapper select idfindById resultTypeUser SELECT * FROM user WHERE id #{id} /select insert idinsert INSERT INTO user(name, age) VALUES(#{name}, #{age}) /insert /mapper// UserMapper.java public interface UserMapper { User findById(Long id); int insert(User user); }绑定规则namespace 接口全限定名id 方法名。2. 注解绑定通过在接口方法上添加注解直接编写 SQL// UserMapper.java public interface UserMapper { Select(SELECT * FROM user WHERE id #{id}) User findById(Long id); Insert(INSERT INTO user(name, age) VALUES(#{name}, #{age})) int insert(User user); Update(UPDATE user SET name #{name} WHERE id #{id}) int update(User user); Delete(DELETE FROM user WHERE id #{id}) int delete(Long id); }四个核心注解注解对应 SQL 类型示例SelectSELECTSelect(SELECT * FROM user WHERE id #{id})InsertINSERTInsert(INSERT INTO user(name) VALUES(#{name}))UpdateUPDATEUpdate(UPDATE user SET name #{name})DeleteDELETEDelete(DELETE FROM user WHERE id #{id})注解方式还支持Options配置主键回写等和ResultMap引用结果映射。注解方式的解析入口是MapperAnnotationBuilderMapperRegistry.addMapper在为接口创建MapperProxyFactory后紧接着就会用MapperAnnotationBuilder.parse()解析接口上的注解 SQL注册对应的MappedStatement。3. 注解 vs XML怎么选对比项注解方式XML 方式SQL 可见性SQL 和接口在一起一目了然SQL 集中在 XML需要切换查看动态 SQL支持script标签但很丑天然支持if/choose/foreach/where 等复杂查询多表关联、嵌套查询写起来痛苦动态 SQL 标签灵活组合可读性好SQL 长度短 SQL 简洁长 SQL 臃肿长短都无影响维护性改 SQL 需改 Java 文件重新编译改 SQL 只改 XML热更新方便调试不方便断点可以查看解析后的完整 SQL实际项目推荐简单的单表 CRUD → 可以用注解减少 XML 文件数量。复杂的多表关联、动态条件查询 → 必须用 XML。企业项目中绝大多数用 XML因为业务 SQL 通常较复杂动态 SQL 是刚需。注解中的动态 SQL不推荐但确实支持Select(script SELECT * FROM user where if testname ! nullAND name #{name}/if if testage ! nullAND age #{age}/if /where /script) ListUser findUsers(Param(name) String name, Param(age) Integer age);这种写法可读性极差不如直接用 XML。七、namespace 的作用1. namespace 的三重身份namespace 不仅仅是一个命名空间它在 MyBatis 中扮演三个重要角色角色一SQL 隔离不同 namespace 下可以有同名的 SQL ID互不冲突!-- UserMapper.xml -- mapper namespacecom.example.mapper.UserMapper select idfindByIdSELECT * FROM user WHERE id #{id}/select /mapper !-- OrderMapper.xml -- mapper namespacecom.example.mapper.OrderMapper select idfindByIdSELECT * FROM order WHERE id #{id}/select /mapper两个findById不冲突因为完整 ID 分别是com.example.mapper.UserMapper.findById和com.example.mapper.OrderMapper.findById。角色二绑定 Mapper 接口当 namespace 等于某个 Mapper 接口的全限定名时XML 中的 SQL 就和该接口的方法绑定namespace com.example.mapper.UserMapper id findById → MappedStatement ID com.example.mapper.UserMapper.findById → 对应 UserMapper.findById() 方法如果 namespace 写错了和接口全限定名不一致启动不会报错但运行时getMapper调用方法会抛BindingException找不到对应的MappedStatement。从源码看bindMapperForNamespace()用Resources.classForName(namespace)反查接口类时若找不到类会静默忽略ClassNotFoundException被 catch 掉这就是启动不报错、运行才炸的根源。角色三唯一标识 MappedStatementConfiguration中的mappedStatements集合以namespace.id作为 Keynamespace 保证了全局唯一性。2. namespace 的常见错误!-- ❌ 错误namespace 和接口全限定名不匹配 -- mapper namespacecom.example.dao.UserDao select idfindById.../select /mapper !-- 接口是 com.example.mapper.UserMapper -- !-- 调用 userMapper.findById() → 找不到 MappedStatement → BindingException --!-- ❌ 错误namespace 重复 -- !-- 两个 XML 文件 namespace 相同ID 重复会报错 --!-- ✅ 正确namespace 接口全限定名 -- mapper namespacecom.example.mapper.UserMapper select idfindById.../select /mapper3. namespace 与跨 namespace 引用MyBatis 支持跨 namespace 引用结果映射和 SQL 片段!-- CommonMapper.xml -- mapper namespacecom.example.mapper.CommonMapper sql iduserColumnsid, name, age, email/sql /mapper !-- UserMapper.xml -- mapper namespacecom.example.mapper.UserMapper select idfindAll resultTypeUser SELECT include refidcom.example.mapper.CommonMapper.userColumns/ FROM user /select /mapper跨 namespace 引用使用完整的namespace.sqlId格式。八、用手写 MyBatis验证核心思想如果你觉得直接啃官方源码还是有距离CodeGuide 仓库中的《Mybatis 手撸专栏》用渐进式方式重现了这一整套设计非常适合对照验证第2章创建简单的映射器代理工厂 用最小的代码量实现了MapperProxy实现InvocationHandler与MapperProxyFactory封装Proxy.newProxyInstance。其invoke中那句你的被代理了 sqlSession.get(mapperInterface.getName() . method.getName())正是对接口全限定名 方法名作为 Key 定位操作这一核心思想的直白演示——先用 Map 模拟 SqlSession后续章节再逐步换成真实数据库操作。第3章实现映射器的注册和使用 补上了MapperRegistryknownMappersMapClass?, MapperProxyFactory?缓存接口与代理工厂的映射addMapper注册、getMapper获取代理DefaultSqlSession.getMapper直接委托给mapperRegistry.getMapper(type, this)——与官方MapperRegistry的结构如出一辙。对照这三章代码你会发现官方源码里注册中心 代理工厂 代理拦截的三层结构其实每层都只做一件事注册时记录接口 → 工厂获取时用工厂 → 代理调用时由代理 → 路由 → 执行。九、常见面试追问Q1Mapper 接口能通过 new 创建实例吗不能。Mapper 接口是纯接口没有实现类new UserMapper()会编译报错。只能通过sqlSession.getMapper()或AutowiredSpring 集成后获取代理对象。如果强行 new 了一个实现类那也不是 MyBatis 管理的无法执行 SQL。Q2Mapper 和 MapperScan 有什么区别Mapper标注在单个 Mapper 接口上告诉 MyBatis-Spring 这个接口需要注册为 Bean。每个接口都要加接口多了很繁琐。Mapper public interface UserMapper { ... }MapperScan标注在 Spring Boot 启动类或配置类上指定包路径自动扫描该包下所有 Mapper 接口并注册。推荐方式一个注解搞定所有 Mapper。MapperScan(com.example.mapper) SpringBootApplication public class Application { ... }本质MapperScan是批量版的Mapper底层都调用了MapperFactoryBean来创建代理对象并注册到 Spring 容器。展开说MapperScan通过Import(MapperScannerRegistrar.class)引入MapperScannerRegistrar它创建ClassPathMapperScanner扫描指定包将每个接口的 BeanDefinition 替换为MapperFactoryBean最终MapperFactoryBean.getObject()返回getSqlSession().getMapper(this.mapperInterface)得到的代理对象——也就是走回MapperRegistry.getMapper → MapperProxyFactory.newInstance这条老路。完整的 Spring 整合分析可参考 面经手册 · 第43篇《MyBatis 和 Spring 怎么整合》。Q3一个 Mapper 接口能对应多个 XML 文件吗不能。MyBatis 不允许同一个 namespace 出现在多个 XML 文件中。如果两个 XML 的 namespace 相同启动时会因为MappedStatementID 冲突报错。如果一个接口的 SQL 太多建议按业务拆分成多个接口如UserReadMapper和UserWriteMapper而不是一个接口对应多个 XML。Q4MapperProxy 为什么用 JDK 动态代理而不是 CGLIB因为 Mapper 是接口JDK 动态代理专门用于接口代理是 Java 原生支持的方案不需要额外依赖。CGLIB 用于代理类代理没有接口的类而 Mapper 接口本身没有实现类CGLIB 反而用不上。MyBatis 的设计理念就是面向接口编程所以 JDK 动态代理是最自然的选择。Q5getMapper 每次调用都创建新的代理对象吗是的。每次调用sqlSession.getMapper()都会通过MapperProxyFactory.newInstance()创建一个新的代理对象。但MapperMethod有缓存methodCache不会重复创建。在 Spring 集成中MapperFactoryBean是单例的Spring 容器中每个 Mapper 只有一个代理实例。十、总结记住三个核心要点 1. Mapper 接口的执行链路 接口方法调用 → MapperProxy.invoke()JDK动态代理 → MapperMethod.execute()方法路由根据SQL类型分发 → SqlSession.selectXxx/insert/update/delete执行SQL 2. Mapper 接口方法不能重载 MappedStatement 的 ID namespace 方法名 重载方法同名 → ID 冲突 → 启动报错 3. namespace 的三重作用 SQL 隔离不同namespace可以有同名ID 绑定接口namespace 接口全限定名 唯一标识namespace.id 是 MappedStatement 的全局唯一Key面试回答模板Mapper 接口没有实现类MyBatis 通过 JDK 动态代理为它生成代理对象。调用流程是SqlSession.getMapper()从MapperRegistry中找到对应的MapperProxyFactory由工厂创建MapperProxy代理实例。当调用接口方法时MapperProxy.invoke()拦截调用根据 Method 对象从缓存获取或创建MapperMethodMapperMethod内部的SqlCommand持有 SQL 类型和MappedStatement的 IDexecute方法根据 SQL 类型路由到SqlSession对应的方法执行。Mapper 接口方法不能重载因为MappedStatement的 ID 只由 namespace 方法名组成不包含参数签名重载方法会生成相同 ID 导致冲突。接口绑定有 XML 和注解两种方式XML 通过 namespace 绑定接口注解通过Select/Insert等直接在方法上定义 SQL。实际项目推荐 XML因为动态 SQL 支持更好复杂查询更易维护。延伸阅读本文只拆到了MapperMethod路由这一层。再往下走SqlSession的实现类DefaultSqlSession会把请求交给Executor执行器经过StatementHandler语句处理器、ParameterHandler参数处理器、ResultSetHandler结果集处理器最终落到 JDBC 的PreparedStatement.execute()。如果你想把这条链路继续看完可以在仓库中按顺序研读《面经手册》第34篇《MyBatis 工作原理是什么从 SqlSessionFactory 到 SqlSession 全链路解析》与《Mybatis 手撸专栏》后续章节执行器、参数处理器、结果集处理器、缓存、插件那将是一条从会用到会写的完整进阶路线。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐MyBatis binding 模块源码解析Mapper 接口动态代理与 SQL 绑定的底层实现MyBatis binding 模块源码解析Mapper 接口动态代理与 SQL 绑定的底层实现 导读 本文围绕 MyBatis 基础支持层中的 bindin文档教程知识库Workflow Designer企业级可视化流程设计工具如何提升业务效率5倍Workflow Designer企业级可视化流程设计工具如何提升业务效率5倍 Workflow Designer 是一款基于 React 和 AntV GMyBatis 初始化原理全解析从 SqlSessionFactoryBuilder 到 Mapper 绑定的启动链路MyBatis 初始化原理全解析从 SqlSessionFactoryBuilder 到 Mapper 绑定的启动链路 导读 与 Spring 框架的 IoC文档教程知识库上一篇3分钟快速导出QQ空间历史说说的完整指南GetQzonehistory免费工具使用教程下一篇5分钟搭建缠论分析系统免费开源ChanlunX通达信插件终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。