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。

浙公网安备 33010602011771号