MyBatis-Plus 的进阶

1,247 阅读4分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

MyBatis-Plus 快速入门 -- 入门篇链接 项目地址 :github.com/tyronczt/ja…

逻辑删除的运用

1、在删除字段上增加 @TableLogic 注解

/**
 * 删除标识,0未删除,1已删除
 * @TableLogic 描述:表字段逻辑处理注解(逻辑删除)
 */
@TableLogic
private Integer deleted;

2、配置文件中增加删除标识的值,默认是 0 表示未删除,1 表示已删除

mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

3、测试方法

 /**
   * 逻辑删除(TableLogic的使用)
   *
   * ==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
   * ==> Parameters: 1094592041087729666(Long)
   * <==    Updates: 1
   * 影响行数:1
   */
@Test
public void deleteById() {
    int effectNum = userMapper.deleteById(1094592041087729666L);
    System.out.println("影响行数:" + effectNum);
}

 /**
   * 根据id查询用户信息,会增加delete=0的条件
   *
   * ==>  Preparing: SELECT id,deleted,create_time,name,update_time,manager_id,version,email,age FROM user WHERE id=? AND deleted=0
   * ==> Parameters: 1094592041087729666(Long)
   * <==      Total: 0
   * 用户信息:null
   *
   * 在deleted字段上增加注解:@TableField(select = false) 不显示deleted字段。
   * ==>  Preparing: SELECT id,create_time,name,update_time,manager_id,version,email,age FROM user WHERE id=? AND deleted=0
   */
@Test
public void selectById() {
    User user = userMapper.selectById(1094592041087729666L);
    System.out.println("用户信息:" + user);
}

注意事项:自定义方法中 @TableLogic 不会起作用

自动填充的运用

1、在自动填充字段上增加 @TableField(fill = FieldFill.XXX) 注解

/**
 * 创建时间
 */
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

/**
 * 更新时间
 */
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;

2、自定义表对象处理器 MyMetaObjectHandler,实现 MetaObjectHandler 接口

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        System.out.println("---insertFill---");
        setInsertFieldValByName("createTime", LocalDateTime.now(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        System.out.println("---updateFill---");
        setUpdateFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}

3、测试方法

/**
 *  新增用户,会自动填充操作时间
 *
 * ---insertFill---
 * ==>  Preparing: INSERT INTO user ( id, create_time, name, manager_id, email, age ) VALUES ( ?, ?, ?, ?, ?, ? )
 * ==> Parameters: 1260593237899411457(Long), 2020-05-13T23:28:14.124(LocalDateTime), 陈海华(String), 1088250446457389058(Long), chh@tyron.com(String), 23(Integer)
 * <==    Updates: 1
 * 影响行数:1
 */
@Test
public void insert() {
    User user = new User();
    user.setAge(23);
    user.setName("陈海华");
    user.setEmail("chh@tyron.com");
    user.setManagerId(1088250446457389058L);
    int effectNum = userMapper.insert(user);
    System.out.println("影响行数:" + effectNum);
}

/**
 * 更新用户信息,会自动填充更新时间
 *
 * ---updateFill---
 * ==>  Preparing: UPDATE user SET update_time=?, age=? WHERE id=? AND deleted=0
 * ==> Parameters: 2020-05-13T23:37:12.779(LocalDateTime), 24(Integer), 1260593237899411457(Long)
 * <==    Updates: 1
 * 影响行数:1
 */
@Test
public void updateById() {
    User user = new User();
    user.setId(1260593237899411457L);
    user.setAge(24);
    int effectNum = userMapper.updateById(user);
    System.out.println("影响行数:" + effectNum);
}

4、根据实际情况对处理器优化

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 优化1:当有自动填充字段时再进行自动填充
        boolean hasSetter = metaObject.hasSetter("createTime");
        if (hasSetter) {
            System.out.println("---insertFill---");
            setInsertFieldValByName("createTime", LocalDateTime.now(), metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 优化2:当字段值没有设值时再自动填充
        Object updateTime = getFieldValByName("updateTime", metaObject);
        if (null == updateTime) {
            System.out.println("---updateFill---");
            setUpdateFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        }
    }
}

乐观锁插件的运用

悲观锁(Pessimistic Locking),悲观锁是指在数据处理过程,使数据处于锁定状态,一般使用数据库的锁机制实现。适合在写多读少的并发环境中使用,虽然无法维持非常高的性能,但是在乐观锁无法提更好的性能前提下,可以做到数据的安全性。

乐观锁相对悲观锁而言,它认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回错误信息,让用户决定如何去做。

利用数据版本号(version)机制是乐观锁最常用的一种实现方式。

1、配置乐观锁插件

/**
 * 乐观锁插件
 */
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
	return new OptimisticLockerInterceptor();
}

2、在版本字段上增加 @Version 注解

@Version
private Integer version;

3、测试栗子

/**
  * 乐观锁-更新用户数据
  * 
  * ==>  Preparing: UPDATE user SET update_time=?, version=?, age=? WHERE id=? AND version=? AND deleted=0
  * ==> Parameters: 2020-05-17T20:42:39.527(LocalDateTime), 2(Integer), 29(Integer), 1260593237899411457(Long), 1(Integer)
  * <==    Updates: 1
  * 影响行数:1
  */
@Test
public void updateById() {
    // 模拟从数据库取出版本信息
    int version = 1;

    User user = new User();
    user.setId(1260593237899411457L);
    user.setVersion(version);
    user.setAge(29);
    int effectNum = userMapper.updateById(user);
    System.out.println("影响行数:" + effectNum);
}

执行 SQL 分析打印

1、Maven:

<dependency>
  <groupId>p6spy</groupId>
  <artifactId>p6spy</artifactId>
  <version>最新版本</version>
</dependency>

2、application.yml 配置:

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:h2:mem:test
    ...

3、spy.properties 配置:

#3.2.1以上使用
#modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
#appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
#日志输出到文件中
logfile=tyron.log
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

Sql 注入器

1.1、创建定义方法的类

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
    // 执行的SQL
    String sql = "delete from " + tableInfo.getTableName();
    // Mapper接口方法名
    String method = "deleteAll";
    SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
    return addDeleteMappedStatement(mapperClass, method, sqlSource);
}

1.2、创建注入器

@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAllMethod());
        return methodList;
    }
}

1.3、在Mapper中加入自定义方法

/**
 * 删除所有数据
 *
 * @return 影响的行数
 */
int deleteAll();

1.4、测试

/**
 * 删除所有行数
 *
 * ==>  Preparing: delete from user
 * ==> Parameters:
 * <==    Updates: 7
 * 影响的行数:7
 */
@Test
public void deleteAll() {
    int effectNums = userMapper.deleteAll();
    System.out.println("影响的行数:" + effectNums);
}

2.1、将自带批量插入注入器添加到methodList中

@Component
public class MySqlInjector extends DefaultSqlInjector {

   @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAllMethod());
        methodList.add(new InsertBatchSomeColumn(t -> !t.isLogicDelete() && !t.getColumn().equals("age")));// 自带批量插入注入器(逻辑删除字段和年龄字段不填充)
        return methodList;
    }
}

2.2、在Mapper中加入自带方法

 /**
  * 批量插入用户
  *
  * @param list 列表
  * @return
  */
int insertBatchSomeColumn(List<User> list);

2.3、测试

 @Test
 public void insertBatchSomeColumn() {
     User user1 = new User();
     user1.setName("黄呼呼");
     user1.setEmail("hhh@tyron.com");
     user1.setManagerId(1087982257332887553L);
     user1.setAge(33);

    User user2 = new User();
    user2.setName("张云云");
    user2.setEmail("zyy@tyron.com");
    user2.setManagerId(1087982257332887553L);
    user2.setAge(23);
    int effectNums = userMapper.insertBatchSomeColumn(Arrays.asList(user1, user2));
    System.out.println("影响的行数:" + effectNums);
}

同理的自带注入器还有:

  • LogicDeleteByIdWithFill:根据 id 逻辑删除数据,并带字段填充功能。注意入参是 entity !!! ,如果字段没有自动填充,就只是单纯的逻辑删除;
  • AlwaysUpdateSomeColumnById :根据 ID 更新固定的那几个字段(但是不包含逻辑删除)