MyBatis-Plus常用注解
# MyBatis-Plus常用注解
# 1. MyBatis-Plus注解使用规则
在使用MyBatis-Plus时,注解并非必须的,其使用取决于具体的业务需求。以下情况说明何时需要使用注解:
# 1.1 基础CRUD无需注解的情况
当满足以下条件时,你可以无需任何注解即可使用MyBatis-Plus的基础功能:
- 实体类的类名与表名一致(忽略大小写)
- 实体类的属性名与表的字段名一致(支持驼峰与下划线自动转换)
- 表的主键列名为
id
(实体类属性也为id
)
/**
* 用户实体类 - 无需任何注解即可完成与user表的映射
* 前提条件:
* 1. 数据库有一个名为user的表
* 2. 表中有id、username、password字段
* 3. id为主键列
*/
public class User {
// 主键,会被自动识别
private Long id;
// 与表中username字段自动映射
private String username;
// 与表中password字段自动映射
private String password;
// Getters and Setters 省略
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.2 需要使用注解的场景
在以下情况下,你需要使用MyBatis-Plus提供的注解来实现特定功能:
注解 | 使用场景 | 作用 |
---|---|---|
@TableName | 实体类名与表名不一致 | 指定实体类对应的表名 |
@TableId | 主键不是id或需要特定主键策略 | 指定主键字段和主键生成策略 |
@TableField | 属性名与字段名不一致或需要特殊配置 | 自定义属性与字段的映射关系 |
@TableLogic | 需要逻辑删除功能 | 标记逻辑删除字段,实现软删除 |
@Version | 需要乐观锁功能 | 标记版本号字段,实现乐观锁 |
@EnumValue | 使用枚举类型 | 标记枚举字段,指定数据库存储的值 |
@JsonFormat | 日期类型格式化 | 控制日期类型字段的序列化格式 |
提示:MyBatis-Plus的注解设计遵循"约定大于配置"的原则,只有在默认约定无法满足需求时才需要使用注解进行个性化配置。
# 2. @TableName注解
@TableName
注解用于指定实体类对应的数据库表名,当实体类与表名不一致时使用。
# 2.1 基本使用
正常情况下,MyBatis-Plus会将实体类User
映射到数据库表user
。但当表名为t_user
或其他不同名称时,就需要使用@TableName
注解。
/**
* 用户实体类 - 使用@TableName注解指定对应的表
* 该实体类将映射到t_user表,而非默认的user表
*/
@Data
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
11
12
# 2.2 注解属性详解
@TableName
注解支持以下属性配置:
/**
* 表名注解详细配置示例
*/
@TableName(
value = "t_user", // 表名
schema = "public", // 数据库schema,适用于PostgreSQL等数据库
keepGlobalPrefix = false, // 是否保持全局表前缀
resultMap = "", // 结果集映射引用
autoResultMap = false, // 是否自动构建ResultMap
excludeProperty = {"createTime"} // 排除的属性名
)
public class User {
// 实体属性...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.3 全局表前缀配置
在实际项目中,通常会有统一的表名前缀(如t_
或tbl_
)。MyBatis-Plus允许通过全局配置设置表前缀,避免在每个实体类上都添加@TableName
注解。
application.yml配置:
# MyBatis-Plus全局配置
mybatis-plus:
global-config:
db-config:
# 设置表前缀,实体User将自动映射到t_user表
table-prefix: t_
# 其他全局配置...
2
3
4
5
6
7
实体类:
/**
* 使用全局表前缀配置后,无需@TableName注解
* 该类将自动映射到t_user表
*/
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
11
# 2.4 动态表名
在某些复杂场景下,可能需要根据不同条件操作不同的表。MyBatis-Plus提供了动态表名功能:
/**
* 动态表名处理器配置
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
return new DynamicTableNameInnerInterceptor(new HashMap<String, TableNameHandler>() {{
// User实体操作t_user表时,可以动态变更表名
put("t_user", (sql, tableName) -> {
// 获取参数,根据参数返回不同的表名
String yearMonth = DateUtil.format(new Date(), "yyyyMM");
return tableName + "_" + yearMonth;
});
}});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3. @TableId注解
@TableId
注解用于指定实体类中的主键属性,并可配置主键生成策略。
# 3.1 基本使用
MyBatis-Plus默认将名为id
的属性识别为主键。当主键名称不是id
或需要特定的主键策略时,需要使用@TableId
注解。
/**
* 使用@TableId标识主键
*/
@Data
public class User {
// 标识uid为主键字段,并使用雪花算法生成ID
@TableId(value = "user_id", type = IdType.ASSIGN_ID)
private Long uid;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.2 @TableId属性
@TableId
注解支持以下属性:
属性 | 类型 | 描述 | 默认值 |
---|---|---|---|
value | String | 指定表中的主键列名 | "" |
type | IdType | 指定主键生成策略 | IdType.NONE |
# 3.3 主键生成策略详解
MyBatis-Plus提供了多种主键生成策略,满足不同业务场景需求:
策略 | 描述 | 适用场景 |
---|---|---|
AUTO | 数据库自增主键 | 适用于MySQL、SQL Server等支持自增的数据库 |
NONE | 不设置主键生成策略 | 需要手动设置主键值,否则抛出异常 |
INPUT | 用户输入主键 | 需要手动设置主键值,否则抛出异常 |
ASSIGN_ID | 分配ID (默认雪花算法) | 适用于分布式系统,生成全局唯一ID |
ASSIGN_UUID | 分配UUID | 适用于需要UUID作为主键的场景 |
ID_WORKER | 分布式全局唯一ID (已废弃) | 建议使用ASSIGN_ID |
UUID | UUID主键 (已废弃) | 建议使用ASSIGN_UUID |
注意:ID_WORKER和UUID策略在新版本中已被废弃,建议使用ASSIGN_ID和ASSIGN_UUID代替。
# 3.4 全局主键策略配置
可以在全局配置中设置默认的主键策略,避免在每个实体类中重复配置:
# MyBatis-Plus全局配置
mybatis-plus:
global-config:
db-config:
# 全局主键生成策略
id-type: auto
# 其他配置...
2
3
4
5
6
7
# 3.5 主键回填机制
MyBatis-Plus在插入数据后会自动将主键值回填到实体对象中:
/**
* 主键回填示例
*/
@Test
public void testInsertWithIdReturn() {
User user = new User();
user.setName("测试用户");
user.setAge(28);
user.setEmail("test@example.com");
// 插入前,ID为null
System.out.println("插入前ID: " + user.getId());
// 执行插入
userMapper.insert(user);
// 插入后,ID已自动回填
System.out.println("插入后ID: " + user.getId());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4. @TableField注解
@TableField
注解用于配置实体类属性与表字段的映射关系,是MyBatis-Plus中功能最强大的注解之一。
# 4.1 基本使用
在以下情况下需要使用@TableField
注解:
- 属性名与字段名不一致(且不符合驼峰转下划线规则)
- 需要排除某些属性不映射到表字段
- 需要设置字段默认值、填充策略等
/**
* @TableField注解基本使用示例
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 指定属性对应的字段名
@TableField("user_name")
private String name;
private Integer age;
// 属性与字段映射,并指定条件
@TableField(value = "email", condition = SqlCondition.LIKE)
private String email;
// 标记为非数据库字段
@TableField(exist = false)
private String remark;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.2 @TableField属性详解
@TableField
注解支持以下属性:
属性 | 类型 | 描述 | 默认值 |
---|---|---|---|
value | String | 字段名 | "" |
exist | boolean | 是否为数据库表字段 | true |
condition | String | 字段条件注解 | "" |
update | String | 字段更新注解 | "" |
insertStrategy | FieldStrategy | 插入策略 | DEFAULT |
updateStrategy | FieldStrategy | 更新策略 | DEFAULT |
whereStrategy | FieldStrategy | 查询策略 | DEFAULT |
fill | FieldFill | 字段自动填充策略 | FieldFill.DEFAULT |
select | boolean | 是否进行select查询 | true |
keepGlobalFormat | boolean | 是否保持全局格式 | false |
jdbcType | JdbcType | JDBC类型 | JdbcType.UNDEFINED |
typeHandler | Class<?> | 类型处理器 | UnknownTypeHandler.class |
numericScale | String | 数字精度 | "" |
# 4.3 字段自动填充
对于创建时间、更新时间等常见字段,可以使用自动填充功能:
/**
* 字段自动填充示例
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
// 插入时自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 插入和更新时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配合自动填充处理器:
/**
* 自动填充处理器
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 添加时自动填充创建时间和更新时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时自动填充更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.4 字段类型处理
处理特殊类型的字段映射:
/**
* 使用类型处理器示例
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
// 使用JSON类型处理器处理复杂类型
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> info;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.5 字段验证策略
控制字段在不同操作下的验证行为:
/**
* 字段验证策略示例
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 插入时非空校验,更新时忽略null值
@TableField(insertStrategy = FieldStrategy.NOT_NULL, updateStrategy = FieldStrategy.IGNORED)
private String name;
private Integer age;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5. @TableLogic注解
@TableLogic
注解用于标记逻辑删除字段,实现软删除功能。软删除保留数据在数据库中,但在查询时会自动过滤被标记为删除的数据。
# 5.1 逻辑删除基本概念
- 物理删除:真正从数据库中删除记录,数据不可恢复
- 逻辑删除:仅标记记录为"已删除"状态,实际上数据仍保留在数据库中
- 应用场景:数据审计、数据恢复、误删除保护等
# 5.2 基本使用
使用逻辑删除需要以下步骤:
- 数据库添加逻辑删除标记字段:
-- 为表添加逻辑删除标记字段(通常为整型,0表示未删除,1表示已删除)
ALTER TABLE `user` ADD COLUMN `is_deleted` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除标记:0未删除,1已删除';
2
- 实体类中添加逻辑删除属性:
/**
* 用户实体类 - 使用逻辑删除
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
// 标记为逻辑删除字段,value为未删除值,delval为已删除值
@TableLogic(value = "0", delval = "1")
private Integer isDeleted;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 测试逻辑删除功能:
/**
* 测试逻辑删除
*/
@Test
public void testLogicDelete() {
// 执行删除操作
int result = userMapper.deleteById(1L);
System.out.println("删除结果:" + (result > 0 ? "成功" : "失败"));
// 查询(被删除的数据不会被查询到)
User user = userMapper.selectById(1L);
System.out.println("查询结果:" + user); // 输出null
}
2
3
4
5
6
7
8
9
10
11
12
13
# 5.3 全局配置逻辑删除
可以在全局配置中设置逻辑删除的默认值,避免在每个实体类上重复配置:
# MyBatis-Plus全局配置
mybatis-plus:
global-config:
db-config:
# 全局逻辑删除字段名
logic-delete-field: is_deleted
# 逻辑已删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 0
2
3
4
5
6
7
8
9
10
配置后,只需在实体类中添加@TableLogic
注解,无需指定value和delval:
@Data
public class User {
// 其他属性省略...
// 使用全局配置的逻辑删除设置
@TableLogic
private Integer isDeleted;
}
2
3
4
5
6
7
8
# 5.4 SQL执行分析
使用逻辑删除后,MyBatis-Plus会自动修改SQL语句:
- 删除操作:从DELETE语句转变为UPDATE语句
-- 原始SQL
DELETE FROM user WHERE id = 1;
-- 逻辑删除实际执行的SQL
UPDATE user SET is_deleted = 1 WHERE id = 1 AND is_deleted = 0;
2
3
4
5
- 查询操作:自动添加未删除条件
-- 原始SQL
SELECT * FROM user WHERE id = 1;
-- 逻辑删除条件下实际执行的SQL
SELECT * FROM user WHERE id = 1 AND is_deleted = 0;
2
3
4
5
# 5.5 高级应用:查询被删除的数据
如果需要查询被标记为删除的数据,可以使用自定义SQL:
/**
* 自定义UserMapper接口
*/
public interface UserMapper extends BaseMapper<User> {
/**
* 查询已被逻辑删除的数据
*/
@Select("SELECT * FROM user WHERE is_deleted = 1")
List<User> selectLogicDeleted();
/**
* 真实删除数据
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int realDeleteById(@Param("id") Long id);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6. @Version注解
@Version
注解用于实现乐观锁功能,防止并发操作时数据覆盖问题。
# 6.1 乐观锁机制介绍
乐观锁假设多用户并发访问数据时,大部分情况下不会发生冲突,只在更新时检查是否有冲突,避免了悲观锁的性能开销。
# 6.2 基本使用
实现乐观锁需要以下步骤:
- 数据库添加版本号字段:
-- 为表添加版本号字段
ALTER TABLE `user` ADD COLUMN `version` INT DEFAULT 1 COMMENT '乐观锁版本号';
2
- 实体类中添加版本号属性:
/**
* 用户实体类 - 使用乐观锁
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
// 标记为乐观锁版本号字段
@Version
private Integer version;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 配置乐观锁插件:
/**
* MybatisPlus配置类
*/
@Configuration
public class MybatisPlusConfig {
/**
* 注册乐观锁插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 测试乐观锁功能:
/**
* 测试乐观锁
*/
@Test
public void testOptimisticLocker() {
// 查询
User user = userMapper.selectById(1L);
// 更新数据
user.setName("新名称");
// 执行更新(此时version会自动+1)
userMapper.updateById(user);
// 模拟并发冲突
User user2 = userMapper.selectById(1L);
user2.setName("并发修改名称");
// 此时version已经被上面的操作更新,所以此次更新会失败
int result = userMapper.updateById(user2);
System.out.println("并发更新结果:" + result); // 输出0,表示更新失败
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6.3 工作原理
乐观锁的工作原理基于版本号比对:
- 取出记录时,获取当前版本号
- 更新时,检查版本号是否与取出时一致
- 若一致,执行更新并将版本号+1
- 若不一致,表示数据已被其他线程修改,更新失败
实际执行的SQL如下:
-- 乐观锁更新SQL
UPDATE user SET name = '新名称', version = version + 1
WHERE id = 1 AND version = 原始版本号;
2
3
# 7. 其他常用注解
# 7.1 @EnumValue
用于标记枚举字段,指定数据库存储的值:
/**
* 性别枚举
*/
public enum GenderEnum {
MALE(1, "男"),
FEMALE(2, "女"),
UNKNOWN(0, "未知");
@EnumValue // 标记数据库存储的值
private final int code;
private final String desc;
GenderEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
// getter方法省略
}
/**
* 使用枚举的实体类
*/
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
// 使用枚举类型
private GenderEnum gender;
}
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
# 7.2 @SqlParser
用于标记是否需要SQL解析:
/**
* 自定义Mapper方法
*/
public interface UserMapper extends BaseMapper<User> {
// 跳过SQL解析
@SqlParser(filter = true)
List<User> selectAllByCustomSql();
}
2
3
4
5
6
7
8
# 7.3 @KeySequence
用于Oracle和PostgreSQL等支持序列的数据库,指定主键序列名:
/**
* 使用序列生成主键的实体类
*/
@Data
@KeySequence("SEQ_USER") // 指定序列名
public class User {
@TableId(value = "id", type = IdType.INPUT)
private Long id;
private String name;
}
2
3
4
5
6
7
8
9
10
11
# 7.4 @InterceptorIgnore
用于指定某些操作忽略特定拦截器:
/**
* 指定方法忽略拦截器
*/
public interface UserMapper extends BaseMapper<User> {
// 忽略租户拦截器
@InterceptorIgnore(tenantLine = "true")
List<User> selectAllUsers();
}
2
3
4
5
6
7
8