文章目录
一、@TableName
value 属性
实体类的名字是 User,数据库表名是 t_user
@TableName(value = "t_user")
public class User{
}
二、@TableId
1、雪花算法
默认情况下数据库的 id 列使用的是基于雪花算法
的策略生产
雪花算法
雪花算法是由Twitter 公布的分布式主键生成算法,他能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
- 核心思想:
- 长度一共有 64 bit(一个long型)
- 首先是一个符号位,1 bit 标识,由于 long 基本类型在java 是带有符号的,最高位是符号位,正数是0,负数是1.所以 id 一般是正数,最高位为0.
- 41 bit 时间戳(毫秒级),存储的是时间的差值(当前时间 - 开始时间戳),结果约等于69.73年。
- 10 bit 作为机器 ID(5个 bit 是数据中心,5个 bit 的机器ID,可以部署在1024个节点)。
- 12 bit 作为毫秒内流水号(意味着每个节点在每毫秒可以产生 4096 个ID)。
- 优点:整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞,并且效率较高。
2、指定主键列
- 测试:将数据库表中的 id 改为 uid ,将实体中的 id 属性修改为 uid,执行数据插入,则报告如下错误:
- 原因:因为 MP 默认认为
id
是主键列,其他名字的属性 MP 无法默认自动填充 - 解决方案:为主键列添加
@TableId
注解
3、value 属性
实体类的属性名是 id,数据库中的列名是 uid,此时使用 value 属性将属性名映射到列名。
@TableId(value = "uid")
private Long id;
4、type属性
type 属性用来定义主键策略
- IdType.ASSIGN_ID:使用基于
雪花算法
的策略生成数据 id
@TableId(value = "uid", type = IdType.ASSIGN_ID)
private Long id;
注意:当对象的 id 被明确赋值时,不会使用雪花算法
- IdType.AUTO:使用数据库的自增策略
@TableId(value = "uid", type = IdType.AUTO)
private Long id;
注意:该类型请确保数据库设置了 ID 自增,否则无效
- 全局配置:要想影响所有的实体配置,可以设置全局主键配置
# 全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
三、@TableField
1、value 属性
功能同 TableId 的 value 属性
注意:MP会自动将数据库中的下划线命名风格转化为实体类中的驼峰命名风格
例如:数据库中的列 create_time 和 update-time 自动对应
实体类中的 createTime 和 updateTime
private LocalDateTime createTime;
private LocalDateTime updateTime;
- 扩展知识:为什么建议使用你 LocalDateTime ,而不是 Date?https://zhuanlan.zhihu.com/p/87555377
- java.util.Date的大多数方法已经过时
- java.util.Date的输出可读性差
- java.util.Date对应的格式化类SimpleDateFormat是线程不安全的类。阿里巴巴开发手册中禁用static修饰SimpleDateFormat。
- LocalDateTime 对应的格式化类DateTimeFormatter是线程安全的
2、自动填充
需求描述:
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作。
例如,阿里巴巴的开发手册中建议每个数据库表必须要有create_time 和 update_time字段,我们可以使用自动填充功能维护这两个字段。
- step1:添加
fill
属性
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
- step2:实现元对象处理器接口 -> 创建 handle 包,创建 MyMetaObjectHandler 类
注意:不要忘记添加 @Component
注解
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
3、测试
- 测试新增
- 测试修改
4、优化
- 避免自动填充是开销过大,填充前先判断当前对象是否有相关属性
@Override
public void insertFill(MetaObject metaObject) {
//其他代码
//判断是否具备author属性
boolean hasAuthor = metaObject.hasSetter("author");
if(hasAuthor){
log.info("start insert fill author....");
this.strictInsertFill(metaObject, "author", String.class, "Helen");
}
}
- 用户明确定义了属性值,则无需自动填充,否则使用自动填充
@TableField(fill = FieldFill.INSERT)
private Integer age;
@Override
public void insertFill(MetaObject metaObject) {
//其他代码
//判断age是否赋值
Object age = this.getFieldValByName("age", metaObject);
if(age == null){
log.info("start insert fill age....");
this.strictInsertFill(metaObject, "age", String.class, "18");
}
}
四、@TableLogic
1、逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
使用场景:可以进行数据恢复
2、实现逻辑删除
-
step1:数据库中创建删除状态列
-
step2:实体类中添加逻辑删除属性
@TableLogic
@TableField(value = "is_deleted")
private Integer deleted;
3、测试
- 测试删除:删除功能被转换为更新功能
-- 实际执行的SQL
update user set is_deleted=1 where id = 1 and is_deleted=0
- 测试查询:被逻辑删除的数据不会被查询到
-- 实际执行的SQL
select id,name,is_deleted from user where is_deleted=0
五、@Version
1、乐观锁场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
- step1:数据库中增加商品表
CREATE TABLE product
(
id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO product (id, NAME, price) VALUES (1, '笔记本', 100);
- step2:创建实体类
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
- step3:创建Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
- step4:测试
@Resource
private ProductMapper productMapper;
@Test
public void testConcurrentUpdate() {
//1、小李
Product p1 = productMapper.selectById(1L);
//2、小王
Product p2 = productMapper.selectById(1L);
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);
//最后的结果
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}
2、乐观锁方案
数据库中添加version字段:取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
- 更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
3、乐观锁实现流程
-
step1:修改实体类
添加 @Version 注解
@Version
private Integer version;
- step2:添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//乐观锁
-
step3:重新执行测试
小王的修改失败!
4、优化流程
失败后重试
if(result2 == 0){//更新失败,重试
System.out.println("小王重试");
//重新获取数据
p2 = productMapper.selectById(1L);
//更新
p2.setPrice(p2.getPrice() - 30);
productMapper.updateById(p2);
}