恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

面试官:MyBatis你只写了接口为啥就能执行SQL啊?

  • 首页
  • 资讯中心
  • /
  • 面试官:MyBatis你只写了接口为啥就能执行SQL啊?

相关资讯

LM Studio 下不了模型?可能是卡巴斯基在“搞鬼” 2026/10/7 21:30:38
PHP7.4和8.3在Laravel框架里兼容性怎么样 2026/10/7 21:30:38
Java基础学习笔记 04、数组与Arrays工具类 2026/10/7 21:30:38

最新资讯

Agent Platform超时故障根因与高可用改造实践
FLIP动画技术解析:用transform优化布局动画,告别掉帧卡顿
ArcGIS Portal接入天地图底图:WMTS配置与排查实操
ArcGIS Portal接入天地图底图:密钥申请、WMTS配置与代理注入全攻略
Linux常用命令与操作详解:从排障到脚本的体系化实战
2026企业网盘安全横评:谁真能防删库跑路与勒索病毒?

今日推荐

SSD不认盘怎么修?金士顿SV300板级排查与短接ROM进工厂模式
Unity 3D RPG开发:C#状态机与物理更新时机实战指南
AIoT开发工程师岗位全景:从嵌入式Linux到边缘计算与端侧AI部署

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

面试官:MyBatis你只写了接口为啥就能执行SQL啊?

发布时间:2026/10/7 21:30:38
面试官:MyBatis你只写了接口为啥就能执行SQL啊? 一、面试现场一个让无数人卡壳的问题1.1 面试对话还原面试官翻了翻简历看到熟练掌握 MyBatis抬起头问了一句你说你熟悉 MyBatis那我问你你的UserMapper只是一个接口没有实现类为什么调用userMapper.selectById(1L)就能执行 SQL 呢候选人开始回忆因为……MyBatis 用了动态代理面试官追问什么动态代理JDK 还是 CGLIB代理对象是什么时候生成的selectById又是怎么映射到 XML 里的 SQL 的候选人这个……应该是启动的时候扫描 XML 吧……面试官那你说说如果 Mapper 接口有两个同名方法MyBatis 能区分吗如果 XML 的 namespace 写错了会怎样到这里很多候选人已经答不上来了。这个问题看似简单实则是一道分水岭题——它能一次性考察你对JDK 动态代理、MyBatis 初始化流程、MapperRegistry 注册机制、MapperProxy 调用链、MapperMethod 方法映射、SqlSession 执行流程、Executor 执行器体系的理解深度。1.2 为什么面试官爱问这道题这道题的价值在于看你是不是只会用很多人用 MyBatis 几年但从来没想过接口为什么能执行 SQL看你能不能讲清原理从接口到代理从代理到 SQL这条链路涉及多个核心组件看你能不能延伸到实战Mapper 重载为什么不支持、namespace 写错会怎样、二级缓存与代理的关系看你能不能对比技术和 Spring Data JPA 的接口自动实现有什么区别本文结构一句话结论从使用场景到实现原理JDK 动态代理基础MyBatis 初始化流程Mapper 是怎么被注册的MapperProxyFactory代理工厂MapperProxy方法调用的拦截器MapperMethod方法到 SQL 的映射SqlSession 与 ExecutorSQL 真正执行的地方为什么 Mapper 接口不支持方法重载实战问题排查高频追问与回答模板总结二、一句话结论MyBatis 在启动时通过 JDK 动态代理为每个 Mapper 接口生成了一个代理对象。你调用的userMapper.selectById(1L)实际上调用的是这个代理对象的invoke()方法。代理对象内部根据接口全限定名 方法名找到对应的 SQL 语句MappedStatement然后交给 SqlSession 执行。一句话总结接口不能执行 SQL能执行 SQL 的是 MyBatis 生成的代理对象。三、从使用场景到实现原理3.1 一个最简单的 MyBatis 使用例子javapublic interface UserMapper { User selectById(Long id); }xmlmapper namespacecom.example.mapper.UserMapper select idselectById resultTypecom.example.entity.User SELECT * FROM user WHERE id #{id} /select /mapperjavaSqlSession sqlSession sqlSessionFactory.openSession(); UserMapper userMapper sqlSession.getMapper(UserMapper.class); User user userMapper.selectById(1L);疑惑UserMapper只是一个接口没有实现类userMapper.selectById(1L)却可以执行 SQL。这是为什么3.2 核心答案动态代理sqlSession.getMapper(UserMapper.class)返回的并不是UserMapper的某个实现类而是MyBatis 通过 JDK 动态代理生成的一个代理对象。这个代理对象实现了UserMapper接口因此可以赋值给UserMapper类型的变量。但它所有的方法调用都会被拦截到MapperProxy.invoke()方法中在invoke()里完成找到 SQL、执行 SQL、返回结果的全流程。四、JDK 动态代理基础4.1 什么是 JDK 动态代理JDK 动态代理是 Java 原生提供的代理机制位于java.lang.reflect包下。它要求目标对象必须实现接口这是 JDK 动态代理的硬性要求代理对象与目标对象实现相同的接口方法调用会被InvocationHandler.invoke()拦截4.2 JDK 动态代理的核心 APIjavapublic interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } public static Object newProxyInstance( ClassLoader loader, Class?[] interfaces, InvocationHandler h)4.3 一个简单的 JDK 动态代理示例javapublic interface UserService { void sayHello(String name); } public class UserServiceImpl implements UserService { Override public void sayHello(String name) { System.out.println(Hello, name); } } public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(before method: method.getName()); Object result method.invoke(target, args); System.out.println(after method: method.getName()); return result; } } public class Main { public static void main(String[] args) { UserService target new UserServiceImpl(); UserService proxy (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class?[]{UserService.class}, new MyInvocationHandler(target) ); proxy.sayHello(张三); } }输出textbefore method: sayHello Hello, 张三 after method: sayHello关键点代理对象实现了UserService接口但它的所有方法调用都会先进入InvocationHandler.invoke()。4.4 MyBatis 是怎么用这套机制的MyBatis 生成的 Mapper 代理和上面的例子类似但有两点不同目标对象不存在MyBatis 没有为接口写实现类所以代理对象不需要委托给 target拦截逻辑不同MapperProxy.invoke()不是简单地前后加日志而是根据方法名找到 SQL 并执行换句话说MyBatis 的代理对象就是一个空壳它唯一的作用就是把接口方法调用转换成 SQL 执行。五、MyBatis 初始化流程Mapper 是怎么被注册的5.1 从 SqlSessionFactory 的构建说起MyBatis 启动时会解析mybatis-config.xml和所有 Mapper XML构建一个Configuration对象并封装成SqlSessionFactory。核心流程textSqlSessionFactoryBuilder.build() → XMLConfigBuilder.parse() → Configuration 初始化 → mapperElement() 解析 mappers 标签 → MapperRegistry.addMapper()5.2 解析 mappers 标签mybatis-config.xml中配置xmlmappers mapper resourcemapper/UserMapper.xml/ package namecom.example.mapper/ /mappersXMLConfigBuilder.mapperElement()会遍历这些配置逐一处理。以package为例它会扫描包下所有.class文件找出所有接口不是类、不是枚举、不是注解为每个接口调用configuration.addMapper(interface)5.3 MapperRegistry接口注册中心Configuration内部维护了一个MapperRegistryjavapublic class MapperRegistry { private final Configuration config; 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.); } boolean loadCompleted false; try { // 核心为接口创建 MapperProxyFactory knownMappers.put(type, new MapperProxyFactory(type)); // 解析 XML如果存在同名的 .xml 文件 MapperAnnotationBuilder parser new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } }关键knownMappers的 key 是接口的Class对象value 是MapperProxyFactory只有接口才会被注册type.isInterface()注册后会立即解析注解和 XML把MappedStatement放进Configuration5.4 XML 的 namespace 必须等于接口全限定名这一点非常关键MyBatis 在解析 XML 时会读取mapper namespace...然后尝试通过Resources.classForName(namespace)加载这个类如果成功调用configuration.addMapper(namespaceClass)如果失败比如 namespace 拼写错误不会抛出异常但Mapper 接口和 XML 就没有关联这就是为什么很多初学者遇到Invalid bound statement (not found)报错——十有八九是 namespace 写错了。六、MapperProxyFactory代理工厂6.1 作用MapperProxyFactoryT是一个泛型工厂类负责为指定的 Mapper 接口生成代理对象。它被注册在MapperRegistry.knownMappers中等待getMapper调用。6.2 核心源码javapublic class MapperProxyFactoryT { private final ClassT mapperInterface; private final MapMethod, MapperMethodInvoker methodCache new ConcurrentHashMap(); public MapperProxyFactory(ClassT mapperInterface) { this.mapperInterface mapperInterface; } public ClassT getMapperInterface() { return mapperInterface; } public MapMethod, MapperMethodInvoker getMethodCache() { return methodCache; } // 核心方法生成代理对象 protected T newInstance(MapperProxyT mapperProxy) { return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxyT mapperProxy new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }关键点newInstance(SqlSession)中创建了一个MapperProxy然后把SqlSession、接口 Class、方法缓存传进去Proxy.newProxyInstance传入的接口数组只有mapperInterface因此代理对象只实现这一个接口代理对象不委托任何 target因为 MyBatis 没有实现类代理对象自己就是实现6.3 methodCache为什么需要缓存methodCache是一个ConcurrentHashMapMethod, MapperMethodInvoker它缓存了方法 - MapperMethod的映射。为什么需要缓存每次调用mapper.selectById(1L)时MapperProxy.invoke()需要根据Method找到对应的MapperMethod如果不缓存每次都要重新解析注解、拼接 statementId性能会受影响Method对象在同一个类中是唯一的作为 key 很合适用ConcurrentHashMap保证线程安全6.4 代理对象的生命周期代理对象是SqlSession级别的还是应用级别的答代理对象由MapperProxyFactory.newInstance(sqlSession)生成每次调用sqlSession.getMapper()都会生成一个新的代理对象。但MapperProxyFactory和methodCache是应用级别的由Configuration持有所以代理对象每次getMapper都新建但 JVM 会对相同接口的代理类进行复用代理 Class 只会生成一次methodCache应用级别共享方法映射只解析一次七、MapperProxy方法调用的拦截器7.1 作用MapperProxyT实现了InvocationHandler是所有 Mapper 方法调用的统一入口。7.2 核心源码javapublic class MapperProxyT implements InvocationHandler, Serializable { private final SqlSession sqlSession; private final ClassT mapperInterface; private final MapMethod, MapperMethodInvoker methodCache; public MapperProxy(SqlSession sqlSession, ClassT mapperInterface, MapMethod, MapperMethodInvoker methodCache) { this.sqlSession sqlSession; this.mapperInterface mapperInterface; this.methodCache methodCache; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 1. 如果调用的是 Object 类的方法toString、hashCode、equals直接执行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 2. 如果调用的是接口默认方法Java 8走默认实现 if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 3. 核心获取或创建 MapperMethod然后调用 final MapperMethodInvoker mapperMethod cachedMapperMethod(method); return mapperMethod.invoke(proxy, method, args, sqlSession); } private MapperMethodInvoker cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k - new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()))); } }关键点Object 方法toString、hashCode、equals直接被代理对象自己处理默认方法Java 8 的接口默认方法走MethodHandles调用不进入 SQL 执行流程普通方法走MapperMethod执行 SQLcachedMapperMethod用computeIfAbsent保证MapperMethod只被创建一次7.3 PlainMethodInvoker 与 DefaultMethodInvokerMyBatis 3.5.x 引入了MapperMethodInvoker接口有两个实现PlainMethodInvoker普通方法走 SQL 执行DefaultMethodInvoker默认方法走 Java 默认实现这种设计让默认方法和接口方法的执行路径分离代码更清晰。7.4 一个完整调用链textuserMapper.selectById(1L) → 代理对象.selectById(1L) → MapperProxy.invoke(proxy, selectById, [1L]) → cachedMapperMethod(selectById) // 从 methodCache 获取或创建 → PlainMethodInvoker.invoke() → MapperMethod.execute(sqlSession, args) → SqlSession.selectOne(statementId, args) → Executor.query(...) → JDBC 执行 SQL八、MapperMethod方法到 SQL 的映射8.1 MapperMethod 的组成MapperMethod是方法到 SQL 的桥梁它由两部分组成javapublic 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); } }SqlCommand负责找到对应的 SQL 语句MethodSignature负责解析方法的返回值、参数等信息8.2 SqlCommand从方法到 statementIdjavapublic static class SqlCommand { private final String name; // statementId private final SqlCommandType type; // SELECT / INSERT / UPDATE / DELETE public SqlCommand(Configuration configuration, Class? mapperInterface, Method method) { // 1. 拼接 statementId 接口全限定名 . 方法名 final String methodName method.getName(); final Class? declaringClass method.getDeclaringClass(); MappedStatement ms resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms null) { throw new BindingException(Invalid bound statement (not found): mapperInterface.getName() . methodName); } this.name ms.getId(); this.type ms.getSqlCommandType(); } }关键statementId 接口全限定名 . 方法名例如com.example.mapper.UserMapper.selectById用这个 statementId 去Configuration.mappedStatements中查找MappedStatement找不到就抛出BindingException这就是Invalid bound statement的由来8.3 MethodSignature方法签名解析MethodSignature负责解析返回值类型void、Object、List、Map、Cursor等参数位置Param注解、RowBounds、ResultHandler是否是MapKey方法返回Map时用哪个字段作为 keyjavapublic static class MethodSignature { private final boolean returnsMany; // 是否返回集合 private final boolean returnsMap; // 是否返回 Map private final boolean returnsVoid; // 是否返回 void private final boolean returnsCursor; // 是否返回 Cursor private final Class? returnType; // 返回类型 private final String mapKey; // MapKey 的值 private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; }8.4 execute 方法根据 SQL 类型分发javapublic Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param method.convertArgsToSqlCommandParam(args); result rowCountResult(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 { Object param method.convertArgsToSqlCommandParam(args); result sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() (result null || !method.getReturnType().equals(result.getClass()))) { result Optional.ofNullable(result); } } 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; }关键根据SqlCommandType决定调用sqlSession.insert、update、delete还是selectOneSelect、Insert、Update、Delete注解也是在这一步被识别的九、SqlSession 与 ExecutorSQL 真正执行的地方9.1 SqlSession用户接口SqlSession是 MyBatis 对外的核心接口封装了所有数据库操作javapublic interface SqlSession extends Closeable { T T selectOne(String statement); T T selectOne(String statement, Object parameter); E ListE selectList(String statement); E ListE selectList(String statement, Object parameter); int insert(String statement); int update(String statement); int delete(String statement); // ... }它的默认实现是DefaultSqlSession内部持有一个Executor。9.2 Executor执行器体系Executor是 MyBatis 的核心执行器采用装饰器模式组织textExecutor接口 ├── BaseExecutor抽象类实现模板方法 │ ├── SimpleExecutor简单执行器每次新建 Statement │ ├── ReuseExecutor复用执行器缓存 Statement │ └── BatchExecutor批量执行器批量提交 ├── CachingExecutor二级缓存装饰器 └── ...当SqlSession.selectOne()被调用时执行流程是textDefaultSqlSession.selectOne() → DefaultSqlSession.selectList() → CachingExecutor.query() // 如果开启二级缓存 → BaseExecutor.query() → 一级缓存查询PerpetualCache → 缓存未命中 → queryFromDatabase() → SimpleExecutor.doQuery() → StatementHandler.prepare() → StatementHandler.parameterize() → StatementHandler.query() → ResultSetHandler.handleResultSets() → 返回结果9.3 StatementHandlerSQL 语句处理器StatementHandler是真正和 JDBC 打交道的地方有四种实现SimpleStatementHandler普通 StatementPreparedStatementHandler预编译 Statement默认CallableStatementHandler存储过程RoutingStatementHandler根据 MappedStatement 的 statementType 路由9.4 ResultSetHandler结果集处理器ResultSetHandler负责把 JDBC 的ResultSet转换成 Java 对象主要工作自动映射根据列名和属性名自动匹配手动映射根据resultMap配置映射类型转换调用TypeHandler把 JDBC 类型转换为 Java 类型十、为什么 Mapper 接口不支持方法重载10.1 MyBatis 的映射规则MyBatis 的statementId是接口全限定名 . 方法名例如textcom.example.mapper.UserMapper.selectById注意这个方法名里不包含参数信息。10.2 重载会导致什么问题如果 Mapper 接口定义了两个同名方法javapublic interface UserMapper { User selectById(Long id); User selectById(Long id, boolean includeDeleted); }两个方法对应的 statementId 都是textcom.example.mapper.UserMapper.selectById在Configuration.mappedStatements中这个 statementId 只能对应一个MappedStatement。后注册的会覆盖先注册的导致其中一个方法找不到 SQL或走错 SQL。10.3 官方立场MyBatis 官方文档明确说明Mapper 接口不支持方法重载。这也是为什么 MyBatis 的 Mapper 方法命名通常很具体例如selectById、selectByUserIdAndStatus。10.4 面试加分点如果面试官问为什么 Mapper 不支持重载你可以这样回答MyBatis 的映射规则是 statementId 接口全限定名 . 方法名不包含参数信息。所以两个同名方法会映射到同一个 statementId导致覆盖或冲突。如果要支持重载MyBatis 需要把参数信息也纳入 statementId但这会破坏现有的 XML namespace id 的唯一性约束因此官方明确不支持。十一、实战问题排查11.1 常见错误一Invalid bound statement (not found)现象textorg.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.mapper.UserMapper.selectById排查XML 的namespace是否等于 Mapper 接口的全限定名XML 的select id...是否等于方法名XML 是否在 MyBatis 的扫描路径下mybatis.mapper-locationsMapper 接口是否被Mapper或MapperScan扫描到如果使用 Spring Boot检查mybatis-plus或mybatis-spring-boot-starter的自动配置11.2 常见错误二方法重载导致的冲突现象两个同名方法只有一个能正常工作。排查检查 Mapper 接口是否有重载方法。如果确实需要重载改为不同方法名。11.3 常见错误三代理对象类型转换异常现象textjava.lang.ClassCastException: com.sun.proxy.$Proxy123 cannot be cast to com.example.mapper.UserMapperImpl排查MyBatis 生成的代理对象只实现了 Mapper 接口不能转换为任何实现类。检查是否错误地把代理对象强转为具体实现类。11.4 常见错误四默认方法调用不走 SQL现象Mapper 接口中定义了默认方法调用时不执行 SQL。排查Java 8 的默认方法走DefaultMethodInvoker不进入 SQL 执行流程。如果默认方法内部调用了接口方法那些接口方法依然会走代理执行 SQL。11.5 常见错误五Object 方法被误用现象在代码中调用userMapper.toString()、userMapper.hashCode()返回的是代理对象的实现而不是 SQL 执行结果。排查这是正常行为MapperProxy.invoke()对Object类方法做了特殊处理。不要在 Mapper 中定义toString等方法的业务语义。十二、高频追问与回答模板12.1 高频追问MyBatis 的 Mapper 接口为什么能执行 SQL动态代理是 JDK 的还是 CGLIB 的代理对象什么时候创建的MapperProxyFactory和MapperProxy有什么区别MapperMethod是怎么找到 SQL 的为什么 Mapper 不支持重载Mapper 接口和 XML 的 namespace 必须一致吗一级缓存和二级缓存在哪个环节生效多个 SqlSession 使用同一个 Mapper 接口代理对象是同一个吗MyBatis 和 Spring Data JPA 的接口自动实现在原理上有什么区别12.2 回答模板回答Mapper 接口为什么能执行 SQL时可以按这个结构组织一句话结论MyBatis 用 JDK 动态代理为 Mapper 接口生成代理对象调用走MapperProxy.invoke()。讲启动阶段MyBatis 启动时解析 XML 和注解通过MapperRegistry把接口和MapperProxyFactory关联起来。讲调用阶段sqlSession.getMapper()生成代理对象调用方法时进入MapperProxy.invoke()通过MapperMethod找到MappedStatement。讲执行阶段MapperMethod.execute()根据 SQL 类型调用SqlSession对应方法最终由Executor和 JDBC 执行。讲约束statementId 是接口全限定名 方法名因此不支持方法重载namespace 必须匹配接口全限定名。12.3 对比 Spring Data JPA维度MyBatisSpring Data JPA接口实现方式JDK 动态代理 MapperProxyJDK 动态代理 RepositoryFactorySupport方法映射statementId 接口名 方法名方法名解析 Query 注解SQL 来源XML / 注解JPQL / 原生 SQL / 方法名派生灵活性高XML 可写复杂 SQL中复杂查询需要 Query学习曲线中高十三、总结13.1 核心结论MyBatis 的 Mapper 接口能执行 SQL是因为 MyBatis 用 JDK 动态代理为接口生成了代理对象。完整链路text启动阶段 解析 XML / 注解 → MapperRegistry.addMapper() → 创建 MapperProxyFactory → 放入 knownMappers 调用阶段 sqlSession.getMapper(UserMapper.class) → MapperProxyFactory.newInstance(sqlSession) → 创建 MapperProxy → Proxy.newProxyInstance() 执行阶段 userMapper.selectById(1L) → MapperProxy.invoke() → MapperMethod.execute() → SqlSession.selectOne() → Executor.query() → JDBC 执行 SQL13.2 关键要点动态代理类型JDK 动态代理因为 Mapper 是接口代理对象生成时机sqlSession.getMapper()时方法映射规则statementId 接口全限定名 . 方法名不支持重载因为 statementId 不含参数信息namespace 必须匹配XML 的 namespace 必须等于接口全限定名methodCache 缓存方法映射只解析一次应用级别共享13.3 一句话总结MyBatis 的 Mapper 接口本身不会执行 SQL能执行 SQL 的是 MyBatis 通过 JDK 动态代理生成的代理对象。代理对象根据接口全限定名 方法名找到 SQL再交给 SqlSession 和 Executor 执行。理解了这条链路你不仅能回答面试题还能在遇到Invalid bound statement、方法重载冲突、代理对象类型转换异常等问题时快速定位根因。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号