天天看点

mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点

mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点

关注我,精彩文章第一时间推送给你

mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点

mybatis plus之主键生成策略

1.自增策略

@TableId(value = "id", type = IdType.AUTO)
private String id;
           

2.雪花生成器(推)

java @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(不含中划线)
mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点

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默认的事务隔离级别是:可重复读
mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点
  • 什么是并发写操作造成的丢失更新?

例如事务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
mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点
/**
 * 版本号属性
 * 设置此字段在添加的时候自动设置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)));
    }
           
  • 可以看到控制台如下输出:
mybatisplus @tablefield注解_基于mybatis plus讲解一些知识点
  • 适用场景:读取频繁使用乐观锁,写入频繁使用悲观锁