TL;DR
- 场景:Java 持久层开发中,需要在 MyBatis 框架下直接通过接口注解定义 SQL,避免维护额外的 XML 映射文件
- 结论 :注解方式适合简单 CRUD 场景,复杂关联映射仍需配合
@Results/@One/@Many使用,动态 SQL 和大段查询推荐 XML - 产出:完整的 MyBatis 注解速查表,含 CRUD 注解、结果映射注解、关联查询注解的使用示例与代码片段

版本矩阵
| 功能 | 版本 | 状态 | 说明 |
|---|---|---|---|
@Select / @Insert / @Update / @Delete |
3.0+ | ✅ 已验证 | 基础 CRUD 注解,替代对应 XML 标签 |
@Results / @Result |
3.0+ | ✅ 已验证 | 结果集映射,替代 <resultMap> |
@One (一对一关联) |
3.0+ | ✅ 已验证 | 替代 <association>,支持子查询 |
@Many (一对多关联) |
3.0+ | ✅ 已验证 | 替代 <collection>,支持子查询 |
@Param 参数命名 |
3.0+ | ✅ 已验证 | 解决多参数绑定歧义 |
@Options 选项配置 |
3.0+ | ✅ 已验证 | 主键生成、缓存等额外选项 |
@ConstructorArgs / @Arg |
3.0+ | ✅ 已验证 | 构造函数映射 |
| MyBatis 最新稳定版 | 3.5.13 | ✅ 已验证 | 2023 年 3 月发布 |
| mybatis-spring-boot-starter | 3.0.3 | ✅ 已验证 | Spring Boot 3.x 配套版本 |
| MyBatis-Plus | 3.5.5 | ⚠️ 参考 | MyBatis-Plus 3.5 系列为当前主流 |
MyBatis 简介
MyBatis 是一款优秀的持久层框架,它支持通过注解的方式进行开发,而无需使用传统的 XML 配置文件。这种方式更加简洁、直观,适合于简单的应用场景或轻量级开发项目。MyBatis 提供了一系列注解,用于代替 XML 配置文件中定义的 SQL 语句和映射规则。这些注解直接写在接口或方法上,使得开发更加面向对象,也减少了配置文件的维护成本。
@Insert新增@Update更新@Delete删除@Select查询@Result结果集封装@Results与@Result一起使用,封装多个结果集@One实现一对一的结果封装@Many实现一对多的结果封装
@Select
用途 :用于执行查询操作。 位置 :放在 Mapper 接口的方法上。 支持功能:可以直接写简单的查询语句,支持动态参数。
代码示例:
java
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(int id);
@Select("SELECT * FROM user WHERE username LIKE CONCAT('%', #{name}, '%')")
List<User> searchByName(String name);
@Insert
用途 :用于执行插入操作。 位置 :放在 Mapper 接口的方法上。 支持功能:支持插入单条记录或批量插入,支持自动生成主键。
代码示例:
java
@Insert("INSERT INTO user(username, password, birthday) VALUES(#{username}, #{password}, #{birthday})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
@Insert("<script>" +
"INSERT INTO user(username, password) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.username}, #{item.password})" +
"</foreach>" +
"</script>")
int batchInsert(@Param("list") List<User> userList);
@Update
用途 :用于执行更新操作。 位置 :放在 Mapper 接口的方法上。 支持功能:支持根据条件更新记录。
代码示例:
java
@Update("UPDATE user SET username = #{username}, password = #{password} WHERE id = #{id}")
int updateUser(User user);
@Update("UPDATE user SET password = #{newPassword} WHERE id = #{id} AND password = #{oldPassword}")
int updatePassword(@Param("id") int id, @Param("oldPassword") String oldPassword, @Param("newPassword") String newPassword);
@Delete
用途 :用于执行删除操作。 位置 :放在 Mapper 接口的方法上。 支持功能:支持根据条件删除记录。
代码示例:
java
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(int id);
@Delete("DELETE FROM user WHERE username = #{username}")
int deleteByUsername(String username);
@Results 和 @Result
用途 :用于结果集的手动映射,将查询的字段与对象属性一一对应。 位置 :放在 Mapper 接口的方法上。 支持功能:用于字段名和对象属性名不一致的情况。
代码示例:
java
@Select("SELECT id, user_name AS userName, create_time AS createTime FROM user")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "createTime", column = "create_time")
})
List<User> findAllUsers();
@Param
用途 :用于给 SQL 中的参数命名,绑定方法的参数到 SQL 语句中的占位符。 位置 :放在 Mapper 方法的参数上。 支持功能:解决方法参数无法直接被引用或多参数的绑定问题。
@Options
用途 :用于设置方法的额外选项,比如主键生成、查询的缓存等。 位置 :放在 @Insert、@Update 等注解上。
@ResultMap
用途 :引用 XML 或注解中定义的结果映射。 位置 :放在 Mapper 接口的方法上。 支持功能:简化复杂的结果映射。
@ConstructorArgs 和 @Arg
用途 :用于在构造函数映射场景中,将查询结果映射到构造函数参数。 位置:放在 Mapper 接口的方法上。
注解优点
- 简洁:直接在代码中定义 SQL,无需额外的 XML 文件。
- 强类型支持:与 Java 代码紧密结合,便于重构和代码检查。
- 便于维护:SQL 紧贴业务逻辑,便于定位问题。
注解缺点
- 不适合复杂 SQL:对于动态 SQL 和大段查询,注解方式不够灵活。
- 代码冗长:复杂查询可能导致注解内容过多,影响可读性。
- SQL 可重用性差:注解中的 SQL 不能像 XML 一样被复用。
常见使用
- 简单 SQL 使用注解,复杂 SQL 使用 XML 配置文件。
- 使用
@Param命名参数,避免歧义。 - 对大段复杂 SQL 优先采用 XML 的
<sql>和<include>标签,提高复用性。 - 结合 SpringBoot 使用
@MapperScan注解统一管理 Mapper。
注解映射
实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用 @Results 注解、@Result 注解、@One 注解、@Many 注解结合起来完成复杂功能的开发。

一对一
查询模型
用户表和订单表的关系,一个用户有多个订单,一个订单只从属于一个用户,一对一查询的需求是:查询一个订单,与此同时查询出该订单所属的用户。
编写代码
OrderMapper
新增了一个方法 findAllWithAnnotation,通过注解的方式进行开发:
java
@Select("select * from wzk_orders")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "ordertime", column = "ordertime"),
@Result(property = "total", column = "total"),
@Result(
property = "user", column = "uid",
javaType = WzkUser.class,
one = @One(select = "icu.wzk.mapper.UserMapper.findByIdWithAnnotation")
),
})
List<WzkOrder> findAllWithAnnotation();
截图如下所示: 
UserMapper
在该类中再加入一个新的方法,也用注解的方式,因为 OrderMapper 中要用到:
java
@Select("select * from wzk_user where id = #{id}")
WzkUser findByIdWithAnnotation(int id);
对应的截图如下所示: 
调用代码
java
package icu.wzk;
import icu.wzk.mapper.OrderMapper;
import icu.wzk.mapper.UserMapper;
import icu.wzk.model.WzkOrder;
import icu.wzk.model.WzkUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class WzkIcu11 {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List<WzkOrder> dataList = orderMapper.findAllWithAnnotation();
dataList.forEach(System.out::println);
sqlSession.close();
}
}
对应的代码截图如下所示: 
测试结果
执行代码,对应的控制台输出结果如下所示:
shell
WzkOrder(id=1, ordertime=Mon Nov 11 00:00:00 CST 2024, total=100.0, user=WzkUser(id=1, username=wzk, password=icu, birthday=Mon Nov 11 00:00:00 CST 2024, orderList=null, roleList=null))
WzkOrder(id=2, ordertime=Mon Nov 11 00:00:00 CST 2024, total=200.0, user=WzkUser(id=1, username=wzk, password=icu, birthday=Mon Nov 11 00:00:00 CST 2024, orderList=null, roleList=null))
WzkOrder(id=3, ordertime=Sun Nov 10 00:00:00 CST 2024, total=150.0, user=WzkUser(id=2, username=wzk2, password=icu2, birthday=Mon Nov 11 00:00:00 CST 2024, orderList=null, roleList=null))
24/11/13 09:02:37 DEBUG jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.JdbcConnectionImpl@75329a49]
对应的截图如下所示: 
错误速查卡
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
BindingException: Invalid bound statement |
注解方法未被发现 | 检查 @MapperScan 是否扫描到对应包;确认接口全限定名与 one=@One(select="...") 中路径一致 |
确保 Mapper 接口在 @MapperScan 指定的包路径下,或在接口上加 @Mapper 注解 |
SQL 中参数无法绑定(#{} 不生效) |
多参数未使用 @Param 命名 |
确认方法参数是否直接作为 SQL 占位符使用 | 多参数方法加 @Param("name") 注解,SQL 中使用 #{name} 引用 |
字段映射结果为 null |
数据库列名与对象属性名不一致且未用 @Results 映射 |
确认 SQL 查询的列别名与 POJO 属性名是否匹配 | 使用 @Results + @Result 显式指定 column 到 property 的映射 |
一对一关联查出的 user 为 null |
子查询方法路径写错或未标记 @Param |
检查 one=@One(select="...") 中的全限定方法名是否正确,参数名是否匹配 |
确认子查询方法存在且可访问,被引用方法的参数需与 column 传参对应 |
批量插入 foreach 语法报错 |
<script> 标签包裹的动态 SQL 格式错误 |
检查 <foreach> 标签是否正确闭合,separator 是否正确 |
确保 <script> 包裹整个语句,collection='list' 对应 @Param("list") |
useGeneratedKeys 主键返回为 0 |
MySQL 驱动版本过低或 keyProperty 路径错误 |
确认 keyProperty="id" 是否指向对象正确的属性路径 |
使用 MySQL Connector/J 8.0+,确认 keyProperty 为对象属性名而非列名 |
| 延迟加载不生效 | 未配置 <settings> 中的延迟加载开关 |
检查 mybatis-config.xml 中是否设置 lazyLoadingEnabled=true |
在 XML 配置或 @Select 注解方法上确认结果映射类型 |
作者:武子康的个人博客