跳到主要内容

注解开发

除了 XML 映射文件,MyBatis 还支持使用注解来定义 SQL 语句。注解方式更加简洁,适合简单的 CRUD 操作。

注解 vs XML

特性注解XML
简洁性
可读性简单SQL高复杂SQL高
维护性SQL分散SQL集中
动态SQL支持(Script)支持(更好)
适用场景简单CRUD复杂查询

基础注解

@Select

用于定义查询语句:

public interface UserMapper {

@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

@Select("SELECT * FROM user ORDER BY id DESC")
List<User> selectAll();

@Select("SELECT COUNT(*) FROM user")
long count();

@Select("SELECT * FROM user WHERE username = #{username} AND status = #{status}")
User selectByUsernameAndStatus(@Param("username") String username,
@Param("status") Integer status);
}

@Insert

用于定义插入语句:

public interface UserMapper {

@Insert("INSERT INTO user (username, password, email, phone) " +
"VALUES (#{username}, #{password}, #{email}, #{phone})")
int insert(User user);

@Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertWithKey(User user);
}

@Update

用于定义更新语句:

public interface UserMapper {

@Update("UPDATE user SET username = #{username}, email = #{email} WHERE id = #{id}")
int update(User user);

@Update("UPDATE user SET status = #{status} WHERE id = #{id}")
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
}

@Delete

用于定义删除语句:

public interface UserMapper {

@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);

@Delete("DELETE FROM user WHERE status = #{status}")
int deleteByStatus(@Param("status") Integer status);
}

参数注解

@Param

用于给参数命名,在 SQL 中通过名称引用:

public interface UserMapper {

// 单个参数不需要 @Param
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

// 多个参数需要 @Param
@Select("SELECT * FROM user WHERE username = #{username} AND email = #{email}")
User selectByUsernameAndEmail(@Param("username") String username,
@Param("email") String email);

// 对象参数不需要 @Param
@Insert("INSERT INTO user (username, password, email) VALUES (#{username}, #{password}, #{email})")
int insert(User user);

// 混合使用
@Select("SELECT * FROM user WHERE status = #{status} AND username LIKE #{keyword}")
List<User> search(@Param("status") Integer status, @Param("keyword") String keyword);
}

参数传递规则

参数类型是否需要 @ParamSQL 中引用方式
单个基本类型不需要#{任意名称}
多个基本类型需要#{参数名}
对象类型不需要#{属性名}
Map 类型不需要#{key}
List/数组不需要#{list[0]}#{array[0]}

结果映射注解

@Results

定义结果映射,类似于 XML 中的 resultMap

public interface UserMapper {

@Results(id = "userResultMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "email", property = "email"),
@Result(column = "phone", property = "phone"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
}

@ResultMap

引用已定义的结果映射:

public interface UserMapper {

@Results(id = "userResultMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "create_time", property = "createTime")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

// 引用上面的结果映射
@ResultMap("userResultMap")
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(String username);
}

@One 一对一关联

public interface OrderMapper {

@Results(id = "orderResultMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "order_no", property = "orderNo"),
@Result(column = "user_id", property = "userId"),
@Result(column = "user_id", property = "user",
one = @One(select = "com.example.mybatis.mapper.UserMapper.selectById"))
})
@Select("SELECT * FROM orders WHERE id = #{id}")
Order selectById(Long id);
}

@Many 一对多关联

public interface UserMapper {

@Results(id = "userWithOrdersMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "id", property = "orders",
many = @Many(select = "com.example.mybatis.mapper.OrderMapper.selectByUserId"))
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectWithOrders(Long id);
}

@One 和 @Many 的高级属性

从 MyBatis 3.5.5 开始,@One@Many 支持 resultMapcolumnPrefix 属性:

// 使用 resultMap 属性
@Result(column = "user_id", property = "user",
one = @One(
select = "com.example.mapper.UserMapper.selectById",
resultMap = "com.example.mapper.UserMapper.userResultMap"
))

// 使用 columnPrefix 属性处理列名冲突
@Result(column = "id", property = "user",
one = @One(
select = "com.example.mapper.UserMapper.selectById",
columnPrefix = "u_" // 处理带有 u_ 前缀的列
))

fetchType 属性:可以覆盖全局的延迟加载设置

// 延迟加载
@Result(column = "user_id", property = "user",
one = @One(
select = "com.example.mapper.UserMapper.selectById",
fetchType = FetchType.LAZY
))

// 立即加载
@Result(column = "user_id", property = "user",
one = @One(
select = "com.example.mapper.UserMapper.selectById",
fetchType = FetchType.EAGER
))

@TypeDiscriminator 鉴别器映射

@TypeDiscriminator 用于根据某列的值选择不同的结果映射,相当于 XML 中的 <discriminator> 元素。这在处理继承映射或根据类型返回不同对象时非常有用。

基本用法

假设数据库中有一个车辆表,根据类型字段返回不同的对象:

// 父类
@Data
public abstract class Vehicle {
private Long id;
private String name;
private Integer type; // 1-汽车,2-卡车
}

// 子类:汽车
@Data
@EqualsAndHashCode(callSuper = true)
public class Car extends Vehicle {
private Integer doorCount; // 车门数量
}

// 子类:卡车
@Data
@EqualsAndHashCode(callSuper = true)
public class Truck extends Vehicle {
private BigDecimal loadCapacity; // 载重量
}

使用 @TypeDiscriminator 根据类型返回不同的对象:

public interface VehicleMapper {

@Results(id = "vehicleResultMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "name", property = "name"),
@Result(column = "type", property = "type")
})
@TypeDiscriminator(
column = "type",
javaType = Integer.class,
cases = {
@Case(value = "1", type = Car.class, results = {
@Result(column = "door_count", property = "doorCount")
}),
@Case(value = "2", type = Truck.class, results = {
@Result(column = "load_capacity", property = "loadCapacity")
})
}
)
@Select("SELECT * FROM vehicle WHERE id = #{id}")
Vehicle selectById(Long id);
}

@Case 注解说明

@Case 用于定义鉴别器的一个分支:

属性说明
value匹配的值(数据库列的值)
type匹配时返回的对象类型
results该类型的属性映射(@Result 数组)

结合 @ResultMap 使用

定义好后,可以通过 @ResultMap 引用:

@ResultMap("vehicleResultMap")
@Select("SELECT * FROM vehicle WHERE type = #{type}")
List<Vehicle> selectByType(Integer type);

鉴别器的实际应用场景

  1. 多态查询:根据类型字段返回不同的子类对象
  2. 状态分支:根据状态值映射不同的属性
  3. 类型区分:处理不同类型的数据存储在同一张表的情况
// 更复杂的示例:订单状态映射
@TypeDiscriminator(
column = "status",
javaType = Integer.class,
cases = {
@Case(value = "0", type = PendingOrder.class),
@Case(value = "1", type = PaidOrder.class),
@Case(value = "2", type = ShippedOrder.class)
}
)
@Select("SELECT * FROM orders WHERE id = #{id}")
Order selectById(Long id);

@ConstructorArgs 构造器映射

当实体类使用构造器创建对象时,使用 @ConstructorArgs 指定构造器参数映射:

// 实体类(使用构造器)
public class User {
private final Long id;
private final String username;
private String email;

public User(Long id, String username) {
this.id = id;
this.username = username;
}

// getter/setter...
}

// Mapper 接口
public interface UserMapper {

@ConstructorArgs({
@Arg(column = "id", javaType = Long.class, id = true),
@Arg(column = "username", javaType = String.class)
})
@Results({
@Result(column = "email", property = "email")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
}

@Arg 注解说明

属性说明
id是否为主键参数(类似 <idArg>
column数据库列名
javaTypeJava 类型
jdbcTypeJDBC 类型
typeHandler类型处理器
select嵌套查询
resultMap嵌套结果映射
name构造器参数名(3.5.4+,可替代位置匹配)

从 MyBatis 3.5.4 开始,@Arg 可以作为可重复注解使用:

@Arg(column = "id", javaType = Long.class, id = true)
@Arg(column = "username", javaType = String.class)
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

@SelectProvider 动态 SQL

MyBatis 注解方式支持三种动态 SQL 方式:Provider 类、<script> 标签和 SQL 构建器。Provider 类是最灵活的方式,适合复杂的动态 SQL 场景。

使用 Provider 类动态生成 SQL

public class UserSqlProvider {

public String selectByCondition(UserQuery query) {
return new SQL() {{
SELECT("*");
FROM("user");
if (query.getUsername() != null) {
WHERE("username LIKE CONCAT('%', #{username}, '%')");
}
if (query.getEmail() != null) {
WHERE("email = #{email}");
}
if (query.getStatus() != null) {
WHERE("status = #{status}");
}
ORDER_BY("id DESC");
}}.toString();
}

public String updateSelective(User user) {
return new SQL() {{
UPDATE("user");
if (user.getUsername() != null) {
SET("username = #{username}");
}
if (user.getEmail() != null) {
SET("email = #{email}");
}
if (user.getPhone() != null) {
SET("phone = #{phone}");
}
WHERE("id = #{id}");
}}.toString();
}
}

public interface UserMapper {

@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(UserQuery query);

@UpdateProvider(type = UserSqlProvider.class, method = "updateSelective")
int updateSelective(User user);
}

SQL 工具类

MyBatis 提供了 SQL 类来构建动态 SQL:

public class UserSqlProvider {

// SELECT 语句
public String selectById() {
return new SQL() {{
SELECT("id, username, email, phone");
FROM("user");
WHERE("id = #{id}");
}}.toString();
}

// INSERT 语句
public String insert() {
return new SQL() {{
INSERT_INTO("user");
VALUES("username", "#{username}");
VALUES("password", "#{password}");
VALUES("email", "#{email}");
}}.toString();
}

// UPDATE 语句
public String update() {
return new SQL() {{
UPDATE("user");
SET("username = #{username}");
SET("email = #{email}");
WHERE("id = #{id}");
}}.toString();
}

// DELETE 语句
public String deleteById() {
return new SQL() {{
DELETE_FROM("user");
WHERE("id = #{id}");
}}.toString();
}

// 多表 JOIN
public String selectUserWithOrders() {
return new SQL() {{
SELECT("u.*, o.id as order_id, o.order_no");
FROM("user u");
LEFT_OUTER_JOIN("orders o ON u.id = o.user_id");
WHERE("u.id = #{id}");
}}.toString();
}
}

SQL 类方法

方法说明
SELECT(String... columns)SELECT 子句
FROM(String table)FROM 子句
JOIN(String join)INNER JOIN
LEFT_OUTER_JOIN(String join)LEFT JOIN
RIGHT_OUTER_JOIN(String join)RIGHT JOIN
WHERE(String... conditions)WHERE 子句
OR()OR 条件
AND()AND 条件
GROUP_BY(String... columns)GROUP BY 子句
HAVING(String... conditions)HAVING 子句
ORDER_BY(String... columns)ORDER BY 子句
INSERT_INTO(String table)INSERT 语句
VALUES(String column, String value)VALUES 子句
UPDATE(String table)UPDATE 语句
SET(String... sets)SET 子句
DELETE_FROM(String table)DELETE 语句

其他 Provider 注解

public interface UserMapper {

@InsertProvider(type = UserSqlProvider.class, method = "insert")
int insert(User user);

@UpdateProvider(type = UserSqlProvider.class, method = "update")
int update(User user);

@DeleteProvider(type = UserSqlProvider.class, method = "deleteById")
int deleteById(Long id);
}

@Options 注解

@Options 用于设置语句的额外选项,对应 XML 中语句元素的各种属性。

基本用法

public interface UserMapper {

// 主键回填
@Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertWithKey(User user);

// 超时设置
@Select("SELECT * FROM user")
@Options(timeout = 30)
List<User> selectAll();

// 缓存设置
@Select("SELECT * FROM user WHERE id = #{id}")
@Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE)
User selectById(Long id);
}

@Options 完整属性

属性说明默认值
useCache是否使用二级缓存true
flushCache刷新缓存策略DEFAULT
resultSetType结果集类型DEFAULT
statementType语句类型PREPARED
fetchSize获取数量-1
timeout超时时间(秒)-1
useGeneratedKeys使用自动生成的主键false
keyProperty主键属性名""
keyColumn主键列名""
resultSets多结果集名称""
databaseId数据库 ID(3.5.5+)""

databaseId 属性(3.5.5+)

当配置了 DatabaseIdProvider 时,可以使用 databaseId 指定语句适用的数据库:

// MySQL 专用
@Select("SELECT * FROM user LIMIT 10")
@Options(databaseId = "mysql")
List<User> selectTopUsersMySQL();

// Oracle 专用
@Select("SELECT * FROM user WHERE ROWNUM <= 10")
@Options(databaseId = "oracle")
List<User> selectTopUsersOracle();

注意事项

由于 Java 注解无法设置 null 值,一旦使用 @Options 注解,所有属性都会被赋予默认值。请务必了解默认值以避免意外行为。

// 这个语句会应用所有默认值
@Select("SELECT * FROM user WHERE id = #{id}")
@Options(timeout = 30) // 其他属性也会被设置
User selectById(Long id);

@SelectKey 注解

@SelectKey 注解用于在插入前或插入后获取数据库生成的主键值,功能等同于 XML 中的 <selectKey> 元素。该注解只能用于 @Insert@InsertProvider@Update@UpdateProvider 标注的方法上。

基本用法

插入前获取序列值(Oracle、DB2等)

@Insert("INSERT INTO users (id, name) VALUES (#{id}, #{name})")
@SelectKey(
statement = "SELECT seq_users.nextval FROM dual",
keyProperty = "id",
before = true,
resultType = Long.class
)
int insert(User user);

插入后获取自增主键(MySQL、SQL Server等)

@Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")
@SelectKey(
statement = "SELECT LAST_INSERT_ID()",
keyProperty = "id",
before = false,
resultType = Long.class
)
int insert(User user);

属性说明

属性说明
statement要执行的 SQL 语句(字符串数组形式,会自动拼接)
keyProperty主键属性名,会将结果设置到该属性
before是否在插入前执行:true(前)、false(后)
resultType返回结果的类型
statementType语句类型:STATEMENTPREPARED(默认)、CALLABLE
databaseId数据库标识(3.5.5+)

不同数据库示例

// Oracle 序列
@Insert("INSERT INTO users (id, name) VALUES (#{id}, #{name})")
@SelectKey(
statement = "SELECT seq_users.nextval FROM dual",
keyProperty = "id",
before = true,
resultType = Long.class,
databaseId = "oracle"
)
int insertForOracle(User user);

// PostgreSQL 序列
@Insert("INSERT INTO users (name) VALUES (#{name})")
@SelectKey(
statement = "SELECT nextval('seq_users')",
keyProperty = "id",
before = true,
resultType = Long.class,
databaseId = "postgresql"
)
int insertForPostgreSQL(User user);

// MySQL 自增主键
@Insert("INSERT INTO users (name) VALUES (#{name})")
@SelectKey(
statement = "SELECT LAST_INSERT_ID()",
keyProperty = "id",
before = false,
resultType = Long.class,
databaseId = "mysql"
)
int insertForMySQL(User user);

// SQL Server 自增主键
@Insert("INSERT INTO users (name) VALUES (#{name})")
@SelectKey(
statement = "SELECT SCOPE_IDENTITY()",
keyProperty = "id",
before = false,
resultType = Long.class,
databaseId = "sqlserver"
)
int insertForSQLServer(User user);

@SelectKey vs @Options(useGeneratedKeys)

两种方式都可以获取自增主键:

// 方式一:使用 @Options(推荐用于 MySQL 等支持自增的数据库)
@Insert("INSERT INTO user (username) VALUES (#{username})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

// 方式二:使用 @SelectKey(更灵活,支持更多数据库)
@Insert("INSERT INTO user (username) VALUES (#{username})")
@SelectKey(
statement = "SELECT LAST_INSERT_ID()",
keyProperty = "id",
before = false,
resultType = Long.class
)
int insert(User user);

选择建议

  • MySQL、PostgreSQL 等支持自增的数据库:优先使用 @Options(useGeneratedKeys = true)
  • Oracle、DB2 等使用序列的数据库:使用 @SelectKey(before = true)
  • 需要更灵活控制的场景:使用 @SelectKey

注意事项

  • @SelectKey 会覆盖 @Options 中的主键生成设置
  • 使用 @SelectKey 时,插入语句中通常需要显式指定主键列(对于 before = true 的情况)
  • before = true 时,先获取主键值再执行插入,适用于序列方式
  • before = false 时,先执行插入再获取主键值,适用于自增方式

@ResultType 注解

@ResultType 注解在使用结果处理器(ResultHandler)时必须使用。当方法的返回类型为 void 时,MyBatis 需要知道每一行结果的类型。

基本用法

// 使用 ResultHandler 时必须指定 @ResultType
@Select("SELECT * FROM user WHERE status = #{status}")
@ResultType(User.class)
void selectWithHandler(@Param("status") Integer status, ResultHandler<User> handler);

// 调用示例
mapper.selectWithHandler(1, context -> {
User user = context.getResultObject();
System.out.println(user);
});

使用场景

场景一:使用 ResultHandler 进行自定义处理

public interface UserMapper {

@Select("SELECT * FROM user")
@ResultType(User.class)
void selectAll(ResultHandler<User> handler);
}

// 使用
List<String> emails = new ArrayList<>();
userMapper.selectAll(context -> {
User user = context.getResultObject();
emails.add(user.getEmail());
});

场景二:大量数据处理

@Select("SELECT * FROM large_table")
@ResultType(Data.class)
void processLargeData(ResultHandler<Data> handler);

// 批量处理大数据
List<Data> batch = new ArrayList<>();
userMapper.processLargeData(context -> {
batch.add(context.getResultObject());
if (batch.size() >= 1000) {
// 批量处理
saveBatch(batch);
batch.clear();
}
});

@ResultType vs @ResultMap

// 使用 @ResultType:直接指定返回类型
@Select("SELECT * FROM user WHERE id = #{id}")
@ResultType(User.class)
void selectById(Long id, ResultHandler<User> handler);

// 使用 @ResultMap:引用 XML 中定义的 resultMap
@Select("SELECT * FROM user WHERE id = #{id}")
@ResultMap("userResultMap")
void selectByIdWithResultMap(Long id, ResultHandler<User> handler);

选择建议

  • 简单映射:使用 @ResultType
  • 复杂映射(关联、嵌套等):在 XML 中定义 resultMap,使用 @ResultMap

@MapKey 注解

@MapKey 用于将查询结果转换为 Map,指定某个属性作为 Map 的 key。

基本用法

public interface UserMapper {

// 返回 Map<Long, User>,以 id 作为 key
@MapKey("id")
@Select("SELECT * FROM user")
Map<Long, User> selectAllMap();

// 以 username 作为 key
@MapKey("username")
@Select("SELECT * FROM user")
Map<String, User> selectAllByUsernameMap();
}

// 使用示例
Map<Long, User> userMap = userMapper.selectAllMap();
User user = userMap.get(1L);

Map<String, User> usernameMap = userMapper.selectAllByUsernameMap();
User user = usernameMap.get("zhangsan");

与 XML 方式的对比

XML 方式使用 selectMap 方法或 @MapKey 注解效果相同:

<!-- XML 方式 -->
<select id="selectAllMap" resultType="User">
SELECT * FROM user
</select>

// Java 调用
Map<Long, User> map = session.selectMap("selectAllMap", "id");

使用场景

  • 需要按某属性快速查找数据
  • 构建缓存数据
  • 批量数据处理时建立索引

@CacheNamespace 和 @CacheNamespaceRef

@CacheNamespace 用于在 Mapper 接口上开启二级缓存,功能等同于 XML 中的 <cache> 元素。

基本用法

@CacheNamespace
public interface UserMapper {

@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
}

完整属性配置

@CacheNamespace(
implementation = LruCache.class, // 缓存实现类
eviction = LruCache.class, // 回收策略
flushInterval = 60000, // 刷新间隔(毫秒)
size = 1024, // 缓存大小
readWrite = true, // 是否可读写
blocking = false // 是否阻塞
)
public interface UserMapper {
// ...
}

@Property 自定义属性(3.4.2+)

从 MyBatis 3.4.2 开始,可以使用 @Property 注解为缓存配置自定义属性:

@CacheNamespace(
implementation = MyCustomCache.class,
properties = {
@Property(name = "host", value = "localhost"),
@Property(name = "port", value = "6379")
}
)
public interface UserMapper {
// ...
}

@Property 也可以使用配置文件中的占位符:

@CacheNamespace(
properties = {
@Property(name = "host", value = "${cache.host}"),
@Property(name = "port", value = "${cache.port}")
}
)
public interface UserMapper {
// ...
}

@CacheNamespaceRef 引用其他缓存

当多个 Mapper 需要共享同一个缓存空间时,使用 @CacheNamespaceRef 引用其他命名空间的缓存:

// UserMapper 定义缓存
@CacheNamespace
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
}

// OrderMapper 引用 UserMapper 的缓存
@CacheNamespaceRef(UserMapper.class)
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> selectByUserId(Long userId);
}

从 3.4.2 开始,也可以通过名称引用:

@CacheNamespaceRef(name = "com.example.mybatis.mapper.UserMapper")
public interface OrderMapper {
// ...
}

注意事项

  • 使用 @CacheNamespaceRef 时,引用的缓存必须已经存在
  • XML 文件中定义的缓存与注解定义的缓存是独立的,即使命名空间相同
  • 共享缓存可以解决多表关联查询时的缓存一致性问题

使用 script 标签

在注解中可以使用 <script> 标签编写动态 SQL:

public interface UserMapper {

@Select("<script>" +
"SELECT * FROM user" +
"<where>" +
" <if test='username != null'> AND username = #{username}</if>" +
" <if test='email != null'> AND email = #{email}</if>" +
" <if test='status != null'> AND status = #{status}</if>" +
"</where>" +
"</script>")
List<User> selectByCondition(UserQuery query);

@Update("<script>" +
"UPDATE user" +
"<set>" +
" <if test='username != null'>username = #{username},</if>" +
" <if test='email != null'>email = #{email},</if>" +
" <if test='phone != null'>phone = #{phone},</if>" +
"</set>" +
"WHERE id = #{id}" +
"</script>")
int updateSelective(User user);

@Select("<script>" +
"SELECT * FROM user WHERE id IN" +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
" #{id}" +
"</foreach>" +
"</script>")
List<User> selectByIds(@Param("ids") List<Long> ids);
}

混合使用

注解和 XML 可以混合使用:

// 简单 SQL 使用注解
public interface UserMapper {

@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

@Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

// 复杂 SQL 使用 XML(在 UserMapper.xml 中定义)
List<User> selectByCondition(UserQuery query);

int updateSelective(User user);
}
<!-- UserMapper.xml -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">

<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY id DESC
</select>

<update id="updateSelective">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="phone != null">phone = #{phone},</if>
</set>
WHERE id = #{id}
</update>

</mapper>

完整示例

@CacheNamespace
public interface UserMapper {

// ========== 查询 ==========

@Results(id = "userResultMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "email", property = "email"),
@Result(column = "phone", property = "phone"),
@Result(column = "status", property = "status"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

@ResultMap("userResultMap")
@Select("SELECT * FROM user ORDER BY id DESC")
List<User> selectAll();

@ResultMap("userResultMap")
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(String username);

@Select("<script>" +
"SELECT * FROM user" +
"<where>" +
" <if test='username != null'> AND username LIKE CONCAT('%', #{username}, '%')</if>" +
" <if test='email != null'> AND email = #{email}</if>" +
" <if test='status != null'> AND status = #{status}</if>" +
"</where>" +
"ORDER BY id DESC" +
"</script>")
List<User> selectByCondition(UserQuery query);

// ========== 插入 ==========

@Insert("INSERT INTO user (username, password, email, phone, status) " +
"VALUES (#{username}, #{password}, #{email}, #{phone}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

@Insert("<script>" +
"INSERT INTO user (username, password, email, phone, status) VALUES " +
"<foreach collection='list' item='user' separator=','>" +
" (#{user.username}, #{user.password}, #{user.email}, #{user.phone}, #{user.status})" +
"</foreach>" +
"</script>")
int insertBatch(List<User> users);

// ========== 更新 ==========

@Update("UPDATE user SET username = #{username}, email = #{email}, phone = #{phone} WHERE id = #{id}")
int update(User user);

@Update("<script>" +
"UPDATE user" +
"<set>" +
" <if test='username != null'>username = #{username},</if>" +
" <if test='email != null'>email = #{email},</if>" +
" <if test='phone != null'>phone = #{phone},</if>" +
" <if test='status != null'>status = #{status},</if>" +
"</set>" +
"WHERE id = #{id}" +
"</script>")
int updateSelective(User user);

// ========== 删除 ==========

@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);

@Delete("<script>" +
"DELETE FROM user WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
" #{id}" +
"</foreach>" +
"</script>")
int deleteByIds(@Param("ids") List<Long> ids);

// ========== 关联查询 ==========

@Results(id = "userWithOrdersMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "id", property = "orders",
many = @Many(select = "com.example.mybatis.mapper.OrderMapper.selectByUserId"))
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectWithOrders(Long id);
}

所有可用注解一览

根据官方文档,MyBatis 提供了以下注解:

语句注解

注解说明XML 等价元素
@Select定义查询语句<select>
@Insert定义插入语句<insert>
@Update定义更新语句<update>
@Delete定义删除语句<delete>
@SelectProvider动态查询语句<select> + Provider
@InsertProvider动态插入语句<insert> + Provider
@UpdateProvider动态更新语句<update> + Provider
@DeleteProvider动态删除语句<delete> + Provider

结果映射注解

注解说明XML 等价元素
@Results结果映射定义<resultMap>
@Result单个属性映射<result> / <id>
@ResultMap引用结果映射resultMap 属性
@ConstructorArgs构造器参数映射<constructor>
@Arg构造器参数<arg> / <idArg>
@One一对一关联<association>
@Many一对多关联<collection>
@TypeDiscriminator鉴别器<discriminator>
@Case鉴别器分支<case>
@MapKeyMap 结果的 key-

缓存注解

注解说明XML 等价元素
@CacheNamespace开启二级缓存<cache>
@CacheNamespaceRef引用其他缓存<cache-ref>
@Property缓存属性(3.4.2+)<property>

其他注解

注解说明
@Options语句选项配置
@Param参数命名
@Flush批量执行刷新

小结

本章详细介绍了 MyBatis 注解开发方式:

基础注解

  • @Select、@Insert、@Update、@Delete:定义 CRUD 语句
  • @Param:多参数命名
  • @Options:语句选项配置
  • @MapKey:Map 结果转换

结果映射注解

  • @Results、@Result:定义结果映射
  • @ResultMap:引用已有映射
  • @One、@Many:关联映射
  • @ConstructorArgs、@Arg:构造器映射
  • @TypeDiscriminator、@Case:鉴别器映射

动态 SQL 注解

  • @SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider:使用 Provider 类动态生成 SQL
  • <script> 标签:在注解中嵌入 XML 动态 SQL

缓存注解

  • @CacheNamespace:开启二级缓存
  • @CacheNamespaceRef:引用其他命名空间的缓存
  • @Property:自定义缓存属性

最佳实践建议

  1. 简单 SQL 使用注解:简单的 CRUD 操作使用注解更加简洁
  2. 复杂 SQL 使用 XML:复杂查询、动态 SQL 较多时推荐使用 XML 方式
  3. 混合使用:简单 SQL 用注解,复杂 SQL 用 XML,可以并存
  4. Provider 类适合复杂动态 SQL:当动态 SQL 逻辑复杂时,使用 Provider 类更易维护

注解方式的局限性:

  • 无法实现复杂的 JOIN 映射(Java 注解不支持循环引用)
  • 动态 SQL 不如 XML 方式直观
  • 复杂映射的可读性不如 XML

下一章将详细介绍动态 SQL。