前言
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
MyBatis Plus
是目前很多公司为了简化开发、提升开发效率的持久层框架神器,作为CRUD
程序员,熟练使用它非常重要!那就从 MP 常用注解开始吧! (p≧w≦q) 从使用场景出发,亲自实践,看完你一定收获满满。
常用注解以及使用场景
常用于类上
@TableName 表名注解
- 当实体类名和数据库表名不一致时,使用
value
来指定实体类和数据库表之间的映射关系,
代码举例:
@TableName("数据库表名")
public class xxx {
...
}
复制代码
或
@TableName(value="数据库表名")
public class xxx {
...
}
复制代码
常用于属性上
@TableId 表主键注解
当表中主键和实体类中主键字段名一致时,可忽略该注释,MP
会自动识别,不用去特地显式指定。反之则使用 value
属性指定数据库表映射到实体类上哪一个是主键字段。
除了指定主键字段以外,还能使用 type
指定主键生成策略,默认策略 IdType.NONE
即 insert
前自行 set
主键值,如果没有给主键赋值,并且未设置主键自增,就看是否有默认值,没有就会报错。
常用主键生成策略:
-
IdType.AUTO 主键自增,需要和数据库表设置一致,否则
Field 'xxx' doesn't have a default value
安排! -
IdType.ASSIGN_ID 主键分配
ID
,主键Java
类型要是Number(Long和Integer)
或String
,通过雪花算法得到 19 全局唯一ID
。 -
IdType.ASSIGN_UUID 主键分配
UUID
,主键java
类型要求为String
。
使用场景举例:
数据库中 tb_user
表:
CREATE TABLE `tb_user` (
`id` bigint(64) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_uuid` varchar(32) DEFAULT NULL COMMENT '用户uuid',
`user_name` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(20) DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
表映射到实体类上:
注意:@TableId 注解可以标注在并不是主键的属性字段上,但是在一个类中不能同时出现两个以上
@TableName(value = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
// @TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户uuid
*/
@TableId(value = "user_uuid", type = IdType.ASSIGN_UUID)
private String userUuid;
/**
* 用户名
*/
private String userName;
/**
* 用户密码
*/
private String password;
}
复制代码
新增记录(用户名和用户密码)后,表数据:
id | user_uuid | user_name | password |
---|---|---|---|
1 | cf204ada021e11f6887c865c96967733 | 悠悠球 | 323uj |
插入时 id
和 user_uuid
均为被赋值,但插入记录后,id
跟随表主键自增设置,从 1 开始,而 user_uuid
则是走 MP
的主键策略,会自动生成一个 UUID
。
那能不能通过雪花算法生成一个主键
id
值,并用这个值作为后续插入行记录的主键起始值呢?
答案当然是肯定的,可以通过 MP
中实现类为 DefaultIdentifierGenerator
中的 nextId()
方法可以得到一个由雪花算法生成的长整型数值。
User user = new User();
user.setUserName("HUALEI");
user.setPassword("123456");
DefaultIdentifierGenerator generator = new DefaultIdentifierGenerator();
Long id = generator.nextId(user);
user.setId(id);
userMapper.insert(user);
复制代码
通过控制台可以很轻易地看出,生成的主键 id
并不是一个短整型,而是一个足足有 19 位的长整型数。
==> Preparing: INSERT INTO tb_user ( user_uuid, id, user_name, password ) VALUES ( ?, ?, ?, ? )
==> Parameters: fdf8c23862755f91a1fbc8385b449c94(String), 1453644498497929218(Long), HUALEI(String), 123456(String)
<== Updates: 1
复制代码
之后,只要不覆盖主键 id
,之后新增记录的主键 id
等于上一条记录的主键值加 1。
@TableField 非主键字段注解
value
- 注解在某一字段上,使用
value
指定实体类中的字段和数据库表的列的映射关系,下划线转驼峰命名时可以不用写,会自动形成映射。
使用场景举例:
数据库表的列名为 password
,但和实体类中的字段名并不一致:
/**
* 用户密码
*/
@TableField(value = "password")
private String userPassword;
复制代码
运行查询语句后,可以在控制台看见 :
SELECT user_uuid,id,user_name,password AS userPassword FROM tb_user WHERE user_uuid=?
复制代码
使用 @TableField
注解作用就是将表列名通过 as
取别名的方法,使结果集中字段映射到实体类的属性上。
exist
- 使用
exist
指定实体类中的字段是否为数据库表字段,被标记的字段插入记录时会被忽略。
使用场景举例:
想使用该字段存储一些相关的额外信息,但是又不想在数据库表中新增列。
/**
* 用户反馈(表中无对应列)
*/
@TableField(exist = false)
private String userFeedback;
复制代码
插入一条记录:
User user = new User();
user.setUserName("刚子");
user.setPassword("987654321");
user.setUserFeedback("滴滴滴");
userMapper.insert(user);
复制代码
即使,在 user
中给 userFeedback
设置了值,MP
还是会将其忽略不作为插入值。
condition
- 通过
condition
属性预处理WHERE
实体条件自定义规则,通过SqlCondition
选择实现自定义条件。
使用场景举例:
仅适用注解完成对指定字段的模糊查询,不使用 Wrapper
。
/**
* 用户名
*/
@TableField(condition = SqlCondition.LIKE)
private String userName;
复制代码
用实体条件进行查询测试:
User user = new User();
user.setUserName("麻");
List<User> users = userMapper.selectList(new QueryWrapper<>(user));
System.out.println(users);
复制代码
没毛病,查询时用 %s
占位进行模糊查询:
==> Preparing: SELECT user_uuid,id,user_name,password AS userPassword FROM tb_user WHERE user_name LIKE CONCAT('%',?,'%')
==> Parameters: 麻(String)
<== Columns: user_uuid, id, user_name, userPassword
<== Row: a56ca26ec06932cd2b64dc06305c9909, 1453644498497929219, 王麻子, sdfd323
<== Total: 1
复制代码
fill
- 通过
fill
指定,字段为空时会通过FieldFill
选择填充策略进行自动填充。
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
复制代码
使用场景举例:
更新一条记录时,自动填充更新时间,不用手动赋值。
@TableField(value = "update_time", fill = FieldFill.UPDATE)
private Date updateTime;
复制代码
执行更新操作后,自动填充更新时间,前提是更新时间为空:
==> Preparing: UPDATE tb_user SET user_name=?, password=?, update_time=? WHERE user_uuid=?
==> Parameters: 皮皮虾(String), password(String), 2021-10-29 11:27:07.752(Timestamp), 3fcf2ead00347248b85821baf5a23704(String)
<== Updates: 1
复制代码
注意:也可以自行配置生成器策略部分
- 首先,实现元对象处理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
,注意使用@Component
或@Bean
注解注入到Spring
容器中。
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
.....
}
复制代码
- 自定义实现类
MyMetaObjectHandler
,完成插入 / 更新时填充字段逻辑。
使用场景举例:
当我更新或者插入一条新数据时,数据库表中的 update_time
字段自动填充为当前时间戳。
实体类中对应的属性:
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
复制代码
MyMetaObjectHandler
实现类中的逻辑:
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
insertAndUpdate(metaObject, "updateTime");
}
private boolean ifAutoFill(MetaObject metaObject ,String fieldName) {
Object fieldValue = metaObject.getValue(fieldName);
return fieldValue == null;
}
private void insertAndUpdate(MetaObject metaObject, String fieldName) {
// 如果属性未被赋值,则自动填充为当前时间
if (ifAutoFill(metaObject, fieldName)) {
this.setFieldValByName(fieldName, new Date(), metaObject);
}
}
复制代码
select
- 通过
select
指定该字段是否作为查询字段。
使用场景举例:
忽略查询字段 insert_time
。
@TableField(select = false)
private Date insertTime;
复制代码
查询 SQL
及结果:
==> Preparing: SELECT user_uuid,id,user_name,password AS userPassword,update_time FROM tb_user WHERE user_name LIKE CONCAT('%',?,'%')
==> Parameters: 麻(String)
<== Columns: user_uuid, id, user_name, userPassword, update_time
<== Row: a56ca26ec06932cd2b64dc06305c9909, 1453644498497929219, 王麻子, sdfd323, 2021-10-20 05:38:04
<== Total: 1
复制代码
@Version 乐观锁注解
-
使用
@Verison
注解标记在字段上,该字段用于控制唯一的修改操作,避免修改数据冲突。 -
支持的数据类型只有:int、Integer、long、Long、Date、Timestamp、LocalDateTime
-
整数类型下
newVersion = oldVersion + 1
-
newVersion
会回写到entity
中 -
仅支持
updateById(id)
与update(entity, wrapper)
方法
使用场景举例:
当有人在修改同一条记录时,只会有一个人修改成功,只有当修改成功后,后面修改操作才会生效。
数据库表:
CREATE TABLE `tb_user` (
`id` bigint(64) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_uuid` varchar(32) DEFAULT NULL COMMENT '用户uuid',
`user_name` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(20) DEFAULT NULL COMMENT '用户密码',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`insert_time` datetime DEFAULT NULL COMMENT '新增时间',
`version` int(11) DEFAULT '1' COMMENT '乐观锁标识',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1453644498497929222 DEFAULT CHARSET=utf8;
复制代码
实体类标记指定字段:
/**
* 乐观锁标识
*/
@Version
private Integer version;
复制代码
配置乐观锁拦截器:
@Configuration
public class MybatisPlusConfig {
/**
* 配置乐观锁拦截器
* @return OptimisticLockerInterceptor 乐观锁拦截器对象
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
复制代码
测试用例:
User user1 = userMapper.selectById("fdf8c23862755f91a1fbc8385b449c94");
user1.setPassword("password1");
User user2 = userMapper.selectById("fdf8c23862755f91a1fbc8385b449c94");
user2.setPassword("password2");
userMapper.updateById(user1);
userMapper.updateById(user2);
复制代码
当修改数据时,会以 version
作为条件,当条件成立的时候才会修改成功,否则不允许修改。
==> Preparing: UPDATE tb_user SET id=?, user_name=?, password=?, update_time=?, version=? WHERE user_uuid=? AND version=?
==> Parameters: 1453644498497929218(Long), HUALEI(String), password1(String), 2021-10-29 14:58:19.0(Timestamp), 2(Integer), fdf8c23862755f91a1fbc8385b449c94(String), 1(Integer)
<== Updates: 1
复制代码
修改同一条记录,userMapper.updateById(user1)
在 userMapper.updateById(user2)
前,所以先判断 version = 1
条件成立修改密码成功,后面再次判断同样的条件,这时 version = 2
,条件不成立故更新操作失败。
==> Preparing: UPDATE tb_user SET id=?, user_name=?, password=?, update_time=?, version=? WHERE user_uuid=? AND version=?
==> Parameters: 1453644498497929218(Long), HUALEI(String), password2(String), 2021-10-29 14:58:19.0(Timestamp), 2(Integer), fdf8c23862755f91a1fbc8385b449c94(String), 1(Integer)
<== Updates: 0
复制代码
@EnumValue 枚举字段注解
- 通常使用
@EnumValue
注解标记在枚举字段上,将数据库中的字段映射为实体类的枚举类型成员变量。
使用场景举例:
数据库表中通过 status
记录用户状态(0.离线 1.在线),字段值希望映射成枚举类型,通过 code
值关联状态。
通过 @EnumValue
指定数据库中的字段映射到目标属性,通过值关联得到 status
字符串。
@Getter
public enum UserStatusEnum {
OFFLINE(0, "离线"),
ONLINE(1, "上线");
@EnumValue
private Integer code;
private String status;
}
复制代码
修改 resources/application.yml
,配置枚举包扫描:
type-enums-package: com.xxx.xxx.enums
复制代码
测试用例:
User user = userMapper.selectById("fdf8c23862755f91a1fbc8385b449c94");
System.out.println(user.getStatus().getStatus()); // 离线
user.setStatus(UserStatusEnum.ONLINE);
userMapper.updateById(user);
System.out.println(user.getStatus().getStatus()); // 上线
复制代码
查询记录:
==> Preparing: SELECT user_uuid,id,user_name,password AS userPassword,status,update_time,version FROM tb_user WHERE user_uuid=?
==> Parameters: fdf8c23862755f91a1fbc8385b449c94(String)
<== Columns: user_uuid, id, user_name, userPassword, status, update_time, version
<== Row: fdf8c23862755f91a1fbc8385b449c94, 1453644498497929218, HUALEI, password1, 0, 2021-10-29 14:58:19, 2
<== Total: 1
复制代码
更新状态:
==> Preparing: UPDATE tb_user SET id=?, user_name=?, password=?, status=?, update_time=?, version=? WHERE user_uuid=? AND version=?
==> Parameters: 1453644498497929218(Long), HUALEI(String), password1(String), 1(Integer), 2021-10-29 14:58:19.0(Timestamp), 3(Integer), fdf8c23862755f91a1fbc8385b449c94(String), 2(Integer)
<== Updates: 1
复制代码
上面这种方式是通过 @EnumValue
注解枚举属性 方式实现的,也可以不用注解通过实现 IEnum
接口的方式实现,泛型和数据库字段类型保持一致。
实现接口后,重写 getValue()
方法,
public enum AgeEnum implements IEnum<Integer> {
ONE(1, "一岁"),
TWO(2, "二岁"),
THREE(3, "三岁");
private int value;
private String desc;
AgeEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
public Integer getValue() {
return this.value;
}
}
复制代码
查询返回的实体类对象:
User [Hash = 1000592566, id=1453644498497929218, userUuid=fdf8c23862755f91a1fbc8385b449c94, userName=HUALEI,
age=ONE, password=password1, status=ONLINE, feedback=null, updateTime=Fri Oct 29 14:58:19 CST 2021, insertTime=null, version=3]
复制代码
@TableLogic 映射逻辑删除注解
- 使用
@TableLogic
注解标记逻辑删除字段,删除记录时(实则是做的事更新操作)只是将数据库该字段置为另一个值表示已被删除,该条记录实则还存在数据库表中。逻辑删除是为了方便数据恢复和保护数据本身价值,如果需要频繁查看就不应使用逻辑删除,而是用一个状态去表示。
使用场景举例:
数据库表的逻辑删除字段:
`is_del` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0. 未删除 1. 已删除'
复制代码
对应实体类中的字段,支持 Integer
Boolean
LocalDateTime
数据类型,这里使用布尔值并加上逻辑删除注解:
/**
* 逻辑删除字段,0.未删除 1.已删除
*/
@TableLogic
private Boolean isDel;
复制代码
添加配置,如果配置了 logic-delete-field: isDel
表示指定全局逻辑删除的实体字段名为 isDel
(since 3.3.0),配置了全局,可以不用再每个有该字段的实体类中添加注解和写成员变量。
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
复制代码
执行删除操作后再次查询删除前的记录,查询的条件中会判断 is_del=0
,即查询的每条记录都是逻辑上存在未被删除的。
==> Preparing: UPDATE tb_user SET is_del=1 WHERE user_uuid=? AND is_del=0
==> Parameters: fdf8c23862755f91a1fbc8385b449c94(String)
<== Updates: 1
==> Preparing: SELECT user_uuid,id,user_name,age,password AS userPassword,status,update_time,version,is_del FROM tb_user WHERE user_uuid=? AND is_del=0
==> Parameters: fdf8c23862755f91a1fbc8385b449c94(String)
<== Total: 0
复制代码
结尾
撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。
近期评论