注解开发
除了 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);
}
参数传递规则
| 参数类型 | 是否需要 @Param | SQL 中引用方式 |
|---|---|---|
| 单个基本类型 | 不需要 | #{任意名称} |
| 多个基本类型 | 需要 | #{参数名} |
| 对象类型 | 不需要 | #{属性名} |
| 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 支持 resultMap 和 columnPrefix 属性:
// 使用 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);
鉴别器的实际应用场景
- 多态查询:根据类型字段返回不同的子类对象
- 状态分支:根据状态值映射不同的属性
- 类型区分:处理不同类型的数据存储在同一张表的情况
// 更复杂的示例:订单状态映射
@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 | 数据库列名 |
javaType | Java 类型 |
jdbcType | JDBC 类型 |
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 | 语句类型:STATEMENT、PREPARED(默认)、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> |
@MapKey | Map 结果的 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:自定义缓存属性
最佳实践建议
- 简单 SQL 使用注解:简单的 CRUD 操作使用注解更加简洁
- 复杂 SQL 使用 XML:复杂查询、动态 SQL 较多时推荐使用 XML 方式
- 混合使用:简单 SQL 用注解,复杂 SQL 用 XML,可以并存
- Provider 类适合复杂动态 SQL:当动态 SQL 逻辑复杂时,使用 Provider 类更易维护
注解方式的局限性:
- 无法实现复杂的 JOIN 映射(Java 注解不支持循环引用)
- 动态 SQL 不如 XML 方式直观
- 复杂映射的可读性不如 XML
下一章将详细介绍动态 SQL。