0%

使用注解方式

MyBatis 注解开发全指南:从基础到高级用法

MyBatis 注解方式提供了一种无需 XML 配置即可编写映射语句的方案,适用于 SQL 逻辑简单、追求代码集中管理的场景。系统梳理注解开发的完整用法,包括基础 CRUD、高级映射、动态 SQL 及最佳实践,帮助你灵活选择 XML 或注解方式。

基础注解:CRUD 操作

MyBatis 提供了 @Insert@Update@Delete@Select 四个核心注解,分别对应 XML 中的 <insert><update><delete><select> 标签,使用简单直接。

1. @Insert:插入数据

1
2
3
4
5
6
7
8
9
10
public interface UserMapper {
// 基础插入
@Insert("INSERT INTO user(username, age, email) VALUES(#{username}, #{age}, #{email})")
int insertUser(User user);

// 插入并返回自增主键(等价于 XML 中的 useGeneratedKeys)
@Insert("INSERT INTO user(username, age) VALUES(#{username}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insertUserWithId(User user);
}
  • @Options 用于配置插入策略,useGeneratedKeys=true 启用自增主键,keyProperty 绑定实体类属性,keyColumn 绑定数据库列(可选,默认与属性名一致)。

2. @Update@Delete:更新与删除

1
2
3
4
5
6
7
8
9
public interface UserMapper {
// 更新用户
@Update("UPDATE user SET username=#{username}, age=#{age} WHERE id=#{id}")
int updateUser(User user);

// 根据 ID 删除
@Delete("DELETE FROM user WHERE id=#{id}")
int deleteUserById(Integer id);
}

3. @Select:查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserMapper {
// 根据 ID 查询单个用户
@Select("SELECT id, username, age, email FROM user WHERE id=#{id}")
User selectById(Integer id);

// 查询所有用户
@Select("SELECT id, username, age, email FROM user")
List<User> selectAll();

// 多参数查询(需用 @Param 绑定参数名)
@Select("SELECT * FROM user WHERE username LIKE #{name} AND age > #{minAge}")
List<User> selectByCondition(@Param("name") String username, @Param("minAge") Integer age);
}
  • 多参数时必须使用 @Param 注解指定参数名,否则 MyBatis 无法映射 #{name} 等占位符。

结果映射注解:处理字段映射

当数据库列名与实体类属性名不一致时,需通过结果映射注解自定义映射关系,核心注解为 @Results@Result@ResultMap

1. @Results@Result:定义映射关系

等价于 XML 中的 <resultMap><result> 标签:

1
2
3
4
5
6
7
8
9
10
11
12
public interface UserMapper {
@Select("SELECT id, user_name, user_age, create_time FROM user WHERE id=#{id}")
@Results({
@Result(id = true, column = "id", property = "id"), // id=true 表示为主键
@Result(column = "user_name", property = "username"), // 数据库列 -> 实体属性
@Result(column = "user_age", property = "age"),
@Result(column = "create_time", property = "createTime",
javaType = LocalDateTime.class, // 指定 Java 类型(可选)
jdbcType = JdbcType.TIMESTAMP) // 指定 JDBC 类型(可选)
})
User selectByIdWithMapping(Integer id);
}
  • column:数据库表列名;
  • property:实体类属性名;
  • javaType/jdbcType:解决类型转换问题(如日期类型)。

2. @ResultMap:复用结果映射

当多个方法需要相同的结果映射时,可通过 @ResultMap 复用,避免重复代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface UserMapper {
// 定义基础结果映射
@Results(id = "userResultMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "user_name", property = "username"),
@Result(column = "user_age", property = "age")
})
@Select("SELECT id, user_name, user_age FROM user WHERE id=#{id}")
User selectById(Integer id);

// 复用结果映射
@Select("SELECT id, user_name, user_age FROM user WHERE age > #{age}")
@ResultMap("userResultMap") // 引用上面定义的 id="userResultMap" 的映射
List<User> selectByAge(Integer age);
}
  • @Resultsid 属性为映射命名,@ResultMap 通过该 id 引用。

3. 混合 XML 映射:@ResultMap 引用 XML 中的 <resultMap>

当结果映射复杂(如级联查询)时,可在 XML 中定义 <resultMap>,再通过注解引用:

1
2
3
4
5
6
7
8
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="xmlResultMap" type="com.example.model.User">
<id column="id" property="id"/>
<result column="user_name" property="username"/>
<result column="user_age" property="age"/>
</resultMap>
</mapper>
1
2
3
4
5
6
// UserMapper.java
public interface UserMapper {
@Select("SELECT id, user_name, user_age FROM user WHERE id=#{id}")
@ResultMap("com.example.mapper.UserMapper.xmlResultMap") // 全限定名引用 XML 中的 resultMap
User selectByIdWithXmlMap(Integer id);
}

级联查询注解:处理关联关系

MyBatis 注解提供 @One(一对一)和 @Many(一对多)注解,处理实体间的关联关系,等价于 XML 中的 <association><collection>

1. @One:一对一关联

例如,User 关联 UserInfo(一个用户对应一个详细信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface UserMapper {
@Select("SELECT * FROM user WHERE id=#{id}")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
// 一对一关联:通过 user_id 查询 UserInfo
@Result(column = "id", property = "userInfo",
one = @One(
select = "com.example.mapper.UserInfoMapper.selectByUserId", // 关联查询的 Mapper 方法
fetchType = FetchType.EAGER // 加载策略:立即加载(默认)/延迟加载
)
)
})
User selectUserWithInfo(Integer id);
}

// UserInfoMapper.java
public interface UserInfoMapper {
@Select("SELECT * FROM user_info WHERE user_id=#{userId}")
UserInfo selectByUserId(Integer userId);
}
  • column = "id":将 user 表的 id 作为参数传递给 selectByUserId 方法;
  • fetchType = FetchType.LAZY:开启延迟加载(需全局配置 lazyLoadingEnabled=true)。

2. @Many:一对多关联

例如,User 关联 Order(一个用户对应多个订单):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface UserMapper {
@Select("SELECT * FROM user WHERE id=#{id}")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
// 一对多关联:通过 user_id 查询订单列表
@Result(column = "id", property = "orders",
many = @Many(
select = "com.example.mapper.OrderMapper.selectByUserId",
fetchType = FetchType.LAZY
)
)
})
User selectUserWithOrders(Integer id);
}

// OrderMapper.java
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE user_id=#{userId}")
List<Order> selectByUserId(Integer userId);
}

级联查询注意事项

  • N+1 查询问题:注解方式的级联查询默认会触发 N+1 次 SQL(1 次主查询 + N 次关联查询),性能较差;
  • 优化方案:复杂关联查询建议使用 XML 中的 join 语句 + 结果映射,或通过 @SelectProvider 编写动态 SQL 实现关联查询。

缓存注解:二级缓存配置

MyBatis 注解提供 @CacheNamespace@CacheNamespaceRef 注解,对应 XML 中的 <cache><cache-ref>,用于配置二级缓存。

1. @CacheNamespace:开启当前 Mapper 的二级缓存

1
2
3
4
5
6
7
8
9
10
// 在 Mapper 接口上添加缓存注解
@CacheNamespace(
eviction = LruCache.class, // 缓存回收策略(LRU 最近最少使用)
flushInterval = 60000, // 缓存刷新间隔(60秒)
size = 1024, // 最大缓存条目
readWrite = true // 是否可读写(false 为只读)
)
public interface UserMapper {
// ... 方法定义
}
  • 需确保实体类实现 Serializable 接口(readWrite=true 时需要序列化)。

2. @CacheNamespaceRef:引用其他 Mapper 的缓存

当多个 Mapper 操作同一张表时,可通过该注解共享缓存:

1
2
3
4
5
// OrderMapper 引用 UserMapper 的缓存配置
@CacheNamespaceRef(UserMapper.class)
public interface OrderMapper {
// ... 方法定义
}

动态 SQL 注解:@*Provider 系列注解

对于复杂的动态 SQL(如条件判断、循环),注解方式通过 @SelectProvider@InsertProvider@UpdateProvider@DeleteProvider 实现,原理是通过工具类动态生成 SQL 语句。

1. 基础用法:静态 SQL 生成

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserMapper {
// 引用 UserProvider 类的 selectById 方法生成 SQL
@SelectProvider(type = UserProvider.class, method = "selectByIdSql")
User selectById(Integer id);
}

// SQL 生成工具类
public class UserProvider {
// 方法返回值为 SQL 字符串
public String selectByIdSql(Integer id) {
return "SELECT id, username, age FROM user WHERE id = " + id;
}
}

2. 动态 SQL 生成:条件拼接

使用 MyBatis 提供的 SQL 类(构建器)简化 SQL 拼接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface UserMapper {
// 动态查询:根据条件拼接 SQL
@SelectProvider(type = UserProvider.class, method = "selectByCondition")
List<User> selectByCondition(
@Param("username") String username,
@Param("minAge") Integer minAge,
@Param("maxAge") Integer maxAge
);
}

public class UserProvider {
public String selectByCondition(Map<String, Object> params) {
// 使用 SQL 构建器
return new SQL() {{
SELECT("id, username, age");
FROM("user");

// 条件判断:若 username 不为空,添加 LIKE 条件
if (params.get("username") != null) {
WHERE("username LIKE CONCAT('%', #{username}, '%')");
}
// 若 minAge 不为空,添加 >= 条件
if (params.get("minAge") != null) {
WHERE("age >= #{minAge}");
}
// 若 maxAge 不为空,添加 <= 条件
if (params.get("maxAge") != null) {
WHERE("age <= #{maxAge}");
}
}}.toString();
}
}
  • SQL 构建器的方法(SELECTFROMWHERE)支持链式调用,自动处理空格和关键字拼接;
  • 多参数时,工具类方法参数建议使用 Map<String, Object>,通过 params.get("key") 获取参数。

3. 批量操作:动态生成批量插入 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface UserMapper {
// 批量插入
@InsertProvider(type = UserProvider.class, method = "batchInsert")
int batchInsert(@Param("users") List<User> users);
}

public class UserProvider {
public String batchInsert(Map<String, Object> params) {
List<User> users = (List<User>) params.get("users");
// 构建批量插入 SQL
SQL sql = new SQL() {{
INSERT_INTO("user");
VALUES("username, age", "#{user.username}, #{user.age}");
}};

// 拼接多个 VALUES 子句
String baseSql = sql.toString();
StringBuilder sb = new StringBuilder(baseSql);
for (int i = 1; i < users.size(); i++) {
sb.append(", (#{users[").append(i).append("].username}, #{users[").append(i).append("].age})");
}
return sb.toString();
}
}

注解 vs XML:如何选择?

场景 推荐方式 理由
简单 CRUD 操作 注解方式 代码集中,无需切换文件,开发效率高
复杂动态 SQL XML 方式 XML 标签(ifforeach 等)更直观,维护性好
复杂结果映射(级联) XML 方式 XML 的 <resultMap> 支持更丰富的映射配置,避免注解代码冗长
多数据库适配 XML 方式 支持 <databaseIdProvider> 标签,注解方式需通过 Provider 类硬编码处理
团队协作 统一方式 避免混合使用导致风格混乱,大型项目建议以 XML 为主(可读性强)

最佳实践

  1. 避免过度使用注解:复杂 SQL 用 XML,简单 SQL 用注解,平衡开发效率与可维护性;
  2. 参数绑定规范:多参数必须用 @Param 注解,明确参数名,避免 arg0param1 等模糊命名;
  3. 动态 SQL 工具类:使用 SQL 构建器生成动态 SQL,避免字符串拼接错误;
  4. 级联查询优化:注解方式的级联查询(@One/@Many)易导致 N+1 问题,优先使用 JOIN 语句;
  5. 缓存配置:二级缓存建议在 XML 中配置(更直观),注解方式适合简单场景。

总结

MyBatis 注解方式为简单 SQL 操作提供了便捷的解决方案,通过 @Select@Insert 等基础注解可快速实现 CRUD,配合 @Results@One@Many 可处理结果映射和关联查询。但对于复杂动态 SQL 和级联关系,XML 方式仍具有不可替代的优势

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10