MyBatis 的 Mapper 接口

public interface UserMapper {

    UserDO selectById(Long userId);

    int freezeUser(Long userId);
}

再配一份 XML:

<mapper namespace="com.order.dao.UserMapper">

    <select id="selectById" resultType="com.order.model.UserDO">
        select id, user_name, status
        from t_user
        where id = #{userId}
    </select>

    <update id="freezeUser">
        update t_user
        set status = 'FROZEN'
        where id = #{userId}
    </update>

</mapper>

这里有个很关键的对应关系:

namespace = 接口全限定名
id        = 方法名

也就是说,com.order.dao.UserMapper.selectById 这个方法,刚好能定位到 XML 里的那条 SQL。

MyBatis 启动时会把 XML 解析成一堆 MappedStatement,放到配置对象里。你可以粗暴理解成一个 Map:

key   = com.order.dao.UserMapper.selectById
value = 那条 select SQL 的执行信息

所以 Mapper 接口的方法,真正的价值不是“写业务逻辑”,而是给 MyBatis 一个 SQL 坐标。

代理对象。

MyBatis 会给 Mapper 接口创建一个 JDK 动态代理。你调用接口方法时,真正进去的是代理对象的 invoke 方法

:代理对象拦截接口方法,根据接口全限定名和方法名找到对应 SQL,再通过 SqlSession 执行。

Spring 里还能自动注入,是因为 MyBatis-Spring 又做了一层事。

它扫描到 @Mapper 或者 @MapperScan 指定包下的接口后,不是注册普通对象,而是注册一个 MapperFactoryBean。等 Spring 真要创建 Bean 时,这个 FactoryBean 会去 MyBatis 里拿 Mapper 代理对象。

大概像这样:

@Configuration
@MapperScan("com.order.dao")
public class MyBatisConfig {
}

然后你才能这么注入:

@Service
public class UserFreezeService {

    private final UserMapper userMapper;

    public UserFreezeService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public void freeze(Long userId) {
        int rows = userMapper.freezeUser(userId);
        if (rows != 1) {
            throw new IllegalStateException("freeze user failed, userId=" + userId);
        }
    }
}

这里的 userMapper 不是实现类,是代理。

这个设计最容易踩坑的地方,是 XML 的 namespace 或 id 写错。

比如接口是:

package com.order.dao;

public interface UserMapper {
    UserDO selectById(Long userId);
}

XML 却写成:

<mapper namespace="com.order.mapper.UserMapper">

启动或者调用时就可能炸:

Invalid bound statement (not found):
com.order.dao.UserMapper.selectById

这个报错我一般不先怀疑数据库,也不怀疑 SQL 语法。先查三样:

1. XML 有没有被扫描到
2. namespace 是不是接口全限定名
3. id 是不是方法名

尤其老项目里 dao、mapper 包名改过,XML 没跟着改,这种问题很常见。

还有一种是方法参数没写 @Param,SQL 里又乱取名字:

UserDO selectByMobileAndStatus(String mobile, Integer status);

XML 里写:

where mobile = #{mobile}
and status = #{status}

这时候我一般直接补上:

UserDO selectByMobileAndStatus(@Param("mobile") String mobile,
                               @Param("status") Integer status);

Mapper 接口不用实现类,不是因为 MyBatis 会偷偷生成一份 Java 文件放在某个角落。

它是运行时给接口挂了一个代理。

接口负责定义方法,XML 或注解负责提供 SQL,MyBatis 负责把这两块用 statementId 绑起来。调用方法时,代理对象接管调用,再扔给 SqlSession。

这套机制看着绕,但好处很实在:业务代码不用碰 JDBC,不用手写实现类,也不用每个查询都重复写连接、参数、结果映射。

你写的是接口。

真正干活的,是背后的代理和那条 SQL。

posted @ 2026-06-26 23:07  KLAPT  阅读(2)  评论(0)    收藏  举报