← 返回题目列表

MyBatis Mapper 接口没有实现类为什么能执行 SQL?

高频 中等 第 11 / 24 题 更新于 2026/07/26
MyBatisMapperProxy动态代理MappedStatement

简化版

MyBatis 会为 Mapper 接口创建 JDK 动态代理,方法调用进入 MapperProxy 后,根据“接口全限定名 + 方法名”定位对应 MappedStatement,再通过 SqlSession 执行查询或更新。接口方法与 XML statement 的 namespace、id 必须正确对应,重载方法也不适合作为 statement 标识。

详细版

MapperRegistry 保存接口与 MapperProxyFactory 的关系,sqlSession.getMapper() 用工厂生成代理。代理把 Object 方法、接口 default 方法和普通 Mapper 方法分别处理;普通方法会解析返回类型、参数形式和 SQL 命令类型,最终调用 SqlSession 的 selectOne、selectList、insert、update 或 delete。

XML 中 <mapper namespace="com.example.UserMapper"> 与接口全限定名一致,<select id="findById"> 与方法名组合为 statement id。注解 SQL 也会在配置解析阶段注册为 MappedStatement,运行时不是临时扫描字符串。

完整版教学

一、代理对象从哪里来

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1L);

mapper 的运行时对象是 JDK 代理,不是开发者编写的实现类。Spring 集成会通过 Mapper 扫描和 FactoryBean 把这种代理注册为容器 Bean,但底层仍是 MyBatis Mapper 代理机制。

角色做什么面试关键词
Mapper 接口声明 Java 方法契约无实现类、方法名
MapperProxyFactory为接口创建代理JDK 动态代理
MapperProxy接住方法调用并分发InvocationHandler
MapperMethod解析 SQL 命令和方法签名返回类型、参数解析
MappedStatement保存具体 SQL 映射namespace + id

记忆钩子:Mapper 接口像“遥控器按钮”,MapperProxy 负责把按钮编号翻译成 statement id,再交给 SqlSession 真正执行。

二、方法如何定位 SQL

假设接口是 com.example.UserMapper,方法名为 findById,默认 statement id 就是:

com.example.UserMapper.findById

Configuration 在启动解析 XML 或注解时已经保存 MappedStatement,其中包含 SQL 来源、参数映射、结果映射、缓存和 statement 类型等信息。

<mapper namespace="com.example.UserMapper">
  <select id="findById" resultType="User">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>
接口全名: com.example.UserMapper
方法名  : findById
statement id = com.example.UserMapper.findById

如果 namespace 写成 com.example.mapper.UserMapper,或 XML 没被扫描加载,代理仍然能创建,但调用方法时会找不到对应 MappedStatement,于是报 Invalid bound statement (not found)

三、MapperMethod 做了什么

MapperMethod 会确定命令类型和方法签名:返回集合时调用 selectList,返回单值时调用 selectOne,返回 Cursor、Map 或 Optional 等类型时走相应分支;增删改返回值可映射为受支持的行数或布尔结果。

方法参数则交给参数解析器转换成 MyBatis 可识别的参数对象。代理通常会缓存已解析的方法信息,避免每次调用重复分析。

四、为什么不建议重载

Java 方法可以按参数列表重载,但 MyBatis statement id 主要由接口名和方法名确定,无法天然把两个同名方法映射成两个独立 statement。重载容易产生映射冲突或含糊行为,应使用语义明确的不同方法名。

五、default 方法与 Object 方法

toStringequals 等 Object 方法不应被当作 SQL;接口 default 方法也可按 Java 接口默认方法语义执行。只有被识别为 Mapper 命令的普通方法才进入 SqlSession。

若调用时报 Invalid bound statement (not found),应优先检查 namespace、statement id、XML 是否被加载以及接口包扫描,而不是怀疑数据库连接。

六、为什么 Spring 里可以直接注入 Mapper

Spring 集成时,Mapper 扫描器会为接口注册 FactoryBean,容器创建 Bean 时拿到的仍是 MyBatis 代理对象。也就是说,@Autowired UserMapper userMapper 注入的不是你写的实现类,而是 Spring 帮你管理生命周期后的代理。

这解释了两个常见现象:第一,Mapper 接口不需要 @Repository 也能注入,前提是扫描配置正确;第二,启动阶段可能能通过,但第一次调用才暴露 statement id 缺失,因为代理对象本身和 SQL 绑定是否存在是两件事。一个项目有 50 个 Mapper,如果只有 1 个 XML 路径写错,往往就是对应方法调用时才报错。

七、常见误区与追问

  • 误区:Mapper 接口背后一定有 MyBatis 生成的 Java 实现类文件。 MyBatis 使用 JDK 动态代理运行时接住调用,不会生成需要你维护的普通实现类源码。
  • 误区:方法名随便重载也能自动区分 SQL。 statement id 主要按接口全名加方法名定位,重载会让映射含糊,应使用不同语义方法名。
  • 追问:Invalid bound statement 先查什么? 查 namespace 是否等于接口全限定名、statement id 是否等于方法名、XML 是否被加载、Mapper 扫描路径是否正确。
  • 追问:注解 SQL 和 XML SQL 有何共同点? 启动解析后都会变成 Configuration 里的 MappedStatement,运行时代理按 id 查找。
  • 误区:Mapper 代理直接拼 SQL 执行。 代理只负责定位 MapperMethod 和 MappedStatement,真正执行仍走 SqlSession、Executor、StatementHandler 等链路。
  • 追问:Object 方法会不会执行 SQL? toStringequalshashCode 等 Object 方法不会当作 Mapper SQL 命令处理。

八、加强记忆

Mapper 没有实现类,是因为 MyBatis 用 JDK 代理接住方法调用;代理按“接口全名 + 方法名”找到 MappedStatement,再委托 SqlSession 执行。namespace、id 和资源加载是绑定成功的三大检查点。