全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 6 分钟 ✍️ juanwangdev

接口绑定原理

MyBatis 的核心特性之一是只需定义 Mapper 接口而无需编写实现类,框架通过 JDK 动态代理自动将接口方法绑定到对应的 SQL 语句。理解这一机制是掌握 MyBatis 的关键。

动态代理创建原理

MyBatis 使用 JDK 动态代理为 Mapper 接口生成代理对象,核心流程如下:

Java
// 用户代码
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

// MyBatis 内部实现(简化)
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

// MapperRegistry 中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> factory = knownMappers.get(type);
    return factory.newInstance(sqlSession);
}

MapperProxyFactory 通过 Proxy.newProxyInstance() 创建代理对象:

Java
// MapperProxyFactory 核心逻辑
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return (T) Proxy.newProxyInstance(
        mapperInterface.getClassLoader(),
        new Class[] { mapperInterface },
        mapperProxy
    );
}

代理对象的拦截器是 MapperProxy,它实现了 InvocationHandler 接口。

方法到 SQL 语句的映射

当调用代理对象的方法时,MapperProxy.invoke() 被触发,执行以下流程:

Java
// MapperProxy.invoke() 简化流程
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 1. 如果是 Object 类的方法(如 toString、equals),直接调用
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    }

    // 2. 获取或缓存 MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);

    // 3. 执行 SQL
    return mapperMethod.execute(sqlSession, args);
}

方法到 SQL 的绑定关系如下表所示:

步骤说明示例
1. 获取 namespace从接口全限定名获取com.example.mapper.UserMapper
2. 获取 statementId从方法名获取selectById
3. 组合完整 IDnamespace + "." + methodNamecom.example.mapper.UserMapper.selectById
4. 查找 MappedStatement从 Configuration 中查找configuration.getMappedStatement("...")
5. 执行 SQL根据 SQL 类型调用对应方法sqlSession.selectOne()
Java
// MapperMethod 执行逻辑
public 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 SELECT:
            // 根据返回值类型选择 selectOne 或 selectList
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
            } else if (method.returnsMany()) {
                result = sqlSession.selectList(command.getName(), param);
            } else {
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        // ... UPDATE, DELETE
    }
    return result;
}

BindingException 常见原因

当接口绑定失败时,MyBatis 会抛出 BindingException,常见原因如下:

1. namespace 与接口不匹配

XML
<!-- 错误:namespace 与实际接口不一致 -->
<mapper namespace="com.example.mapper.OldUserMapper">
    <select id="selectById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>
Java
// 实际使用的接口
public interface UserMapper {
    User selectById(Long id);
}
// 抛出:BindingException: Invalid bound statement (not found): com.example.mapper.UserMapper.selectById

解决:namespace 必须与接口全限定名一致。

2. XML 文件未被扫描

Java
// mybatis-config.xml 中未配置 mapper 位置
<mappers>
    <!-- 缺少 UserMapper.xml 的注册 -->
    <mapper resource="mapper/OrderMapper.xml"/>
</mappers>

解决:确保 XML 文件在 <mappers> 中注册,或使用 @MapperScan 扫描包路径。

3. 方法名与 statement id 不匹配

Java
public interface UserMapper {
    User findById(Long id);  // 方法名为 findById
}
XML
<!-- XML 中 id 为 selectById,与接口方法名不一致 -->
<select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
</select>
<!-- 抛出:BindingException: Invalid bound statement (not found): com.example.mapper.UserMapper.findById -->

解决:XML 中的 id 必须与接口方法名完全一致。

4. 接口未被 @Mapper 注解或未被扫描

Java
// 忘记添加 @Mapper 注解
public interface UserMapper {
    User selectById(Long id);
}

解决:添加 @Mapper 注解或在启动类配置 @MapperScan("com.example.mapper")

绑定流程总结

text
用户调用 mapper.selectById(1)
         ↓
    JDK 动态代理拦截
         ↓
  MapperProxy.invoke()
         ↓
  组合 statementId = "namespace.methodName"
         ↓
  从 Configuration 查找 MappedStatement
         ↓
  根据 SQL 类型执行 sqlSession.selectOne/selectList/insert/update/delete
         ↓
    返回结果

要点总结

  • MyBatis 通过 JDK 动态代理为 Mapper 接口生成代理对象,无需编写实现类
  • 代理对象的方法调用通过 namespace.方法名 定位到对应的 SQL 语句
  • MapperProxy 是核心拦截器,负责方法到 SQL 的转换与执行
  • BindingException 通常由 namespace 不匹配、XML 未注册、方法名与 id 不一致导致
  • 接口必须被 @Mapper 注解或通过 @MapperScan 扫描,否则无法生成代理

📝 发现内容有误?点击此处直接编辑

← 上一篇 SqlSession 获取 Mapper
下一篇 → 事务控制基础
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库