惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
C
CERT Recently Published Vulnerability Notes
V
Visual Studio Blog
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
博客园 - 聂微东
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
AI
AI
S
Security @ Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
Jina AI
Jina AI
W
WeLiveSecurity
大猫的无限游戏
大猫的无限游戏
腾讯CDC
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
宝玉的分享
宝玉的分享
AWS News Blog
AWS News Blog
月光博客
月光博客
P
Palo Alto Networks Blog
小众软件
小众软件
V2EX - 技术
V2EX - 技术
罗磊的独立博客
V
Vulnerabilities – Threatpost
J
Java Code Geeks
H
Heimdal Security Blog
S
SegmentFault 最新的问题
博客园 - 【当耐特】
Cyberwarzone
Cyberwarzone
S
Schneier on Security
博客园_首页
T
The Exploit Database - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
Forbes - Security
Forbes - Security
N
News | PayPal Newsroom
IT之家
IT之家
Project Zero
Project Zero
Help Net Security
Help Net Security
P
Privacy International News Feed
爱范儿
爱范儿
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threat Research - Cisco Blogs

博客园 - KLAPT

Gateway 网关 CodeX =>Skills Redis 内存满了怎么处理 SpringBoot 默认配置修改 JWT 续签 Access Token + Refresh Token 双 Token claudeCode 命令 AI | CC GUI 集成 IDEA 完整教程 在IDEA中使用Claude Code IDEA中使用CodeX MyBatisPlus解决大数据量查询慢问题 idea 中的 claude code Token Dubbo 和 Spring Cloud Gateway的区别 Transactional 注解中propagation 掌握 Spring 框架这 10 个扩展点 SpringBoot 快速实现 api 加密 Spring Boot/Cloud 中 bootstrap.yml 与 application.yml SpringBoot 实现 DOCX 转 PDF 微服务Token鉴权设计的几种方案 进程、线程、协程 RSA 加密 Java二维码 ntp服务端和客户端 Chronyd与NTP chronyd 作为服务器时钟 chrony sudo命令和su 的区别 java -cp 和 java -jar Maven 项目打包:实现业务代码与第三方依赖分离 达梦数据库创建用户 梦数据库新增大字段报错问题 达梦数据库操作 MySQL UPDATE多表关联更新 达梦数据库 为HTTP POST请求设置请求体 在Java中调用第三方接口并返回第三方页面 Java调用第三方接口的方法 Nginx 之Rewrite 使用详解 linux 命令 Spring Boot项目中集成Spring Security OAuth2和Apache Shiro
MyBatis 的 Mapper 接口
KLAPT · 2026-06-26 · via 博客园 - KLAPT
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。