关注我,更多精彩文章第一时间推送给你
mybatis plus之主键生成策略
1.自增策略
@TableId(value = "id", type = IdType.AUTO)
private String id;
2.雪花生成器(推)
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
3.UUID
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
-
自3.3.0开始,默认使用雪花算法+UUID(不含中划线)
方法 主键生成策略 主键类型 说明 nextId ASSIGN_ID, ID_WORKER,ID_WORKER_STRLong,Integer,String 支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer nextUUID ASSIGN_UUID, UUIDString 默认不含中划线的UUID生成
4.Sequence主键
@KeySequence(value = "SEQ_ORACLE_STRING_KEY", clazz = String.class)
public class YourEntity {
@TableId(value = "ID_STR", type = IdType.INPUT)
private String idStr;
}
主键生成策略必须使用INPUT
支持父类定义@KeySequence子类继承使用
内置支持:
- DB2KeyGenerator
- H2KeyGenerator
- KingbaseKeyGenerator
- OracleKeyGenerator
- PostgreKeyGenerator
如果内置支持不满足你的需求,可实现IKeyGenerator接口来进行扩展.
SpringBoot方式一:配置类
@Bean
public IKeyGenerator keyGenerator() {
return new H2KeyGenerator();
}
SpringBoot方式二:通过MybatisPlusPropertiesCustomizer自定义
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return plusProperties -> plusProperties.getGlobalConfig().getDbConfig().setKeyGenerator(new H2KeyGenerator());
}
mybatis plus之自动填充字段功能
- 个人比较喜欢这个功能,尤其是用于
create_time
和update_time
两个字段的自动填充。
/**
* 注意:设置此字段为自动填充字段,即添加记录的时候自动添加创建时间
* 需要配置实现接口 MetaObjectHandler
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 注意:设置此字段为自动填充字段,添加或更新记录时候,此字段自动填充
* 需要配置实现接口 MetaObjectHandler
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
- 需要实现接口
MetaObjectHandler
元数据处理器接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 实现添加的时候自动填充的字段
* 添加记录的时候自动设置创建时间和修改时间为当前时间
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
/**
* 修改的时候自动填充的字段, 填充为当前时间
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}
mybatis plus之乐观锁
- 首先了解一下什么是乐观锁?什么是悲观锁?
乐观锁与悲观锁都是用来解决并发情况下写操作可能会导致的丢失更新的问题。
- 什么是并发操作下造成的读的问题 ?
首先来说如果不考虑事务的隔离性,会产生几个读的问题,脏读、不可重复读、幻读
脏读:读到未提交的数据,也就是事务A读到事务B更改后的数据,事务B异常回滚了,所以事务A读到的是脏数据。
不可重复读:例如事务A读到小李30岁,这时候事务B把小李改成20岁提交了,事务A未结束又读了一遍,发现小李变成了20岁。
幻读:例如事务A读员工数量200人,这时候事务B添加了5条员工数据并提交,这时候事务A未结束,又读取了一遍员工数量,发现变成了205条。
-
额、上面简单总结一下,不考虑事务的隔离级别的情况下可能造成的读的问题。
-
事务的隔离级别有什么呢?
1、Serializable (串行化):最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ(重复读) :保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失。
3、READ COMMITTED (读提交):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。
4、Read Uncommitted(读未提交) :事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。
- mysql默认的事务隔离级别是:可重复读
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
- 什么是并发写操作造成的丢失更新?
例如事务A和事务B同时修改小明的工资,他们两个读取到的小明工资都是2000然后事务A更改了小明工资为3000后提交,这时候事务B更改了小明的工资为5000,提交之后事务A对工资的更新就丢失了。结果为5000
悲观锁和乐观锁就是为了解决并发操作造成的丢失更新问题:
**悲观锁:**顾名思义,就是悲观的认为并发问题一定会出现,所以在对一个数据进行操作的时候一定会加锁,因为他认为不加锁一定会产并发问题。
乐观锁:顾名思义,就是乐观的认为读取数据的时候不会有其他事务进行修改,所以不会加锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。这里mybatis plus使用的就是版本号机制实现的乐观锁。
- 先介绍一下CAS操作:Compare and Swap,即比较再交换。
java中有一个并发包
java.util.concurrent.*
,这个包中的类就是使用CAS算法实现了乐观锁。之前java语言靠synchronized
关键字保证同步,使用synchronized
关键字是一种独占锁,属于悲观锁。
- CAS的功能是判断内存中的某个位置的值是否为预期值,如果是则改变为新的值,这个过程是原子的。
CAS算法实现的一个前提是需要取出内存中某时刻的数据,并在当前时间进行比较并替换,那么在这个时间差内,如果线程A从内存V处取出值为1,这时候另一个线程B也在内存V处取出值为1,并且线程B经过一些操作将值改成了2,然后线程B又经过操作将值改回1,这时候线程A操作CAS发现内存中的值仍为1,然后线程A操作成功改了V处的值为3。
尽管这个线程A的CAS操作执行成功,但是并不代表这个过程是没有问题的。这就是著名的
ABA问题
。
- 如何解决ABA问题?使用版本号!mybatis plus就是使用版本号来实现乐观锁的。
可以使用版本号来解决,例如:数据库中添加一个版本号,取出记录的时候,获取当前的版本号,更新数据的时候,带上这个版本号,即 update set version = newVersion where version = oldVersion,如果版本号不等于之前的版本号,说明已经被更改过了,即本次更新失败。
- 使用mybatis plus实现乐观锁
- 表中添加字段,作为乐观锁的版本号
- 对应实体类添加属性version
- 在实体类版本号属性上添加@Version注解
- 配置mybatis plus乐观锁的插件
- (可选)可以利用之前讲的自动填充在添加记录的时候给Version一个默认值1
/**
* 版本号属性
* 设置此字段在添加的时候自动设置version值为 1
* 需要配置实现接口 MetaObjectHandler
*/
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
@Configuration
@MapperScan(basePackages = {
"com.yunqing.demomybatisplus.mapper"})
public class MybatisPlusConfig {
//配置乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 实现添加的时候自动填充的字段
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
//配置添加role记录的时候自动填充版本号为1
this.setFieldValByName("version", "1", metaObject);
}
}
- 测试使用乐观锁进行更新数据
/**
* ActiveRecord修改
* UPDATE t_role SET role_code=?, role_name=?, update_time=?, version=? WHERE id=? AND version=?
*
* UPDATE t_role SET create_time=? WHERE (id = ?)
*/
@Test
void update() {
/**
* 测试乐观锁更新Role,先查询获取版本号,在更新,之后再获取版本号
*/
Role role = new Role().selectById(2L);
log.info("更新之前获取版本号 = {}", role.getVersion());
role.setRoleName("管理员1234");
Assertions.assertTrue(role.updateById());
Role role2 = new Role().selectById(2L);
log.info("更新之后获取版本号 = {}", role2.getVersion());
/**
* 非乐观锁更新,直接更新
*/
Assertions.assertTrue(new Role().update(new UpdateWrapper<Role>().lambda()
.set(Role::getCreateTime, LocalDateTime.now()).eq(Role::getId, 1)));
}
- 可以看到控制台如下输出:
- 适用场景:读取频繁使用乐观锁,写入频繁使用悲观锁
mybatis之逻辑删除
- 之前我们做逻辑删除,需要先在数据库创建标志位,之后删除语句也成修改,修改标志位的值,查询的时候还要添加where deleted = 0 来查询没被删除的。
- mybatis 提供了一个注解
@TableLogic
来让我们不再需要关注这个标志位。 - 只需要给实体类中的逻辑删除字段加上此注解即可。
/**
* 逻辑删除标志位
*/
@TableLogic
private Integer deleted;
- 执行删除操作,默认就是逻辑删除
/**
* 测试逻辑删除
*/
@Test
void ljDeleted() {
Assertions.assertTrue(new Role().setId(2L).deleteById());
}
- 看到上面测试代码执行的是mybatis plus的删除操作,看一下控制行输出代码,执行的却是update
- 由下图可以看到,id为2的这条数据被逻辑删除了 deleted = 1
- 之后执行查询的时候也不用关注这个逻辑删除标志位了,默认查询未被删除的数据。
/**
* 测试查询,看看逻辑删除的数据是否还在
*/
@Test
void query() {
List<Role> roles = new Role().selectAll();
roles.forEach(System.out::println);
}
- 可以看控制台输出的sql 自动帮我们加上了
where deleted = 0
并且遍历结果集,没有被逻辑删除的id = 2的数据。
以后再写查询再也不用关注是否被逻辑删除了,但是这也造成了另一个问题,假如我们想要查询被逻辑删除的数据怎么办???
这就要通过在
mapper.xml
中手写sql来完成啦,也很方便,毕竟查询被逻辑删除的数据的业务需求几乎没有。