2 Mybatis Plus 核心功能

1、主键策略

自3.3.0开始,默认使用雪花算法+UUID(不含中划线)

(1)ID_WORKER

MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID

参考资料:分布式系统唯一ID生成方案汇总:https://www.cnblogs.com/haoxinyue/p/5208136.html

(2)自增策略

  • 要想主键自增需要配置如下主键策略

    • 需要在创建数据表的时候设置主键自增
    • 实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;

要想影响所有实体的配置,可以设置全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

其它主键策略:分析 IdType 源码可知

public enum IdType {
    AUTO(0),		//数据库ID自增
    NONE(1),		//该类型为未设置主键类型
    INPUT(2),		//用户输入ID,该类型可以通过自己注册自动填充插件进行填充
    ASSIGN_ID(3),	//支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer。类型:Long,Integer,String
    ASSIGN_UUID(4),	//默认不含中划线的UUID生成。类型:String
    
    //*****以下过期*****
    @Deprecated
    ID_WORKER(3),		//全局唯一ID (idWorker)
    @Deprecated
    ID_WORKER_STR(3),	//字符串全局唯一ID (idWorker 的字符串表示)
    @Deprecated
    UUID(4);			//全局唯一ID (UUID)

​ idWorker、idWorker、UUID,只有当插入对象ID 为空,才自动填充。

2、自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

(1)数据库表中添加自动填充字段

在User表中添加datetime类型的新的字段 create_time、update_time

(2)实体上添加注解

@Data
@Accessors(chain = true)
public class User {
	
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

支持类型:

public enum FieldFill {
    DEFAULT,	//删除填充
    INSERT,		//添加填充
    UPDATE,		//更新填充
    INSERT_UPDATE;	//添加或更新时填充

    private FieldFill() {
    }
}

(3)实现元对象处理器接口

注意:不要忘记添加 @Component 注解

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

3、乐观锁

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

(1)数据库中添加version字段

ALTER TABLE `user` ADD COLUMN `version` INT

(2)实体类添加version字段

并添加 @Version 注解

@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;

(3)元对象处理器接口添加version的insert默认值

@Override
public void insertFill(MetaObject metaObject) {
    this.setFieldValByName("version", 1, metaObject);
}

特别说明:

  • 支持的数据类型只有 int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 `newVersion = oldVersion + 1```
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

(4)在 MybatisPlusConfig 中注册 Bean

package com.mybatis.plus.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.mybatis.plus.mapper")
public class MybatisPlusConfig {
    /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

4、分页

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

(1)创建配置类

此时可以删除主类中的 @MapperScan 扫描注解

package com.mybatis.plus.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.mybatis.plus.mapper")
public class MybatisPlusConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

(2)测试selectPage分页

测试:最终通过page对象获取相关数据

@Test
public void testSelectPage() {
    Page<User> page = new Page<>(1, 2);
    userMapper.selectPage(page, null);

    page.getRecords().forEach(System.out::println);
    System.out.println(page.getCurrent());
    System.out.println(page.getPages());
    System.out.println(page.getSize());
    System.out.println(page.getTotal());
    System.out.println(page.hasNext());
    System.out.println(page.hasPrevious());
}

(3)测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {
    Page<User> page = new Page<>(1, 5);
    IPage<Map<String, Object>> mapIPage = userMapper.selectMapsPage((Page)page, null);
    //注意:此行必须使用 mapIPage 获取记录列表,否则会有数据类型转换错误
    mapIPage.getRecords().forEach(System.out::println);
    System.out.println(page.getCurrent());
    System.out.println(page.getPages());
    System.out.println(page.getSize());
    System.out.println(page.getTotal());
    System.out.println(page.hasNext());
    System.out.println(page.hasPrevious());
}

5、逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

(1)数据库中添加 deleted字段

ALTER TABLE `user` ADD COLUMN `deleted` boolean

**(2)实体类添加deleted 字段

并加上 @TableLogic 注解 和 @TableField(fill = FieldFill.INSERT) 注解

@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;

(3)元对象处理器接口添加deleted的insert默认值

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("deleted", 0, metaObject);
    }
}    

(4)application.properties 加入配置

此为默认值,如果你的默认值和mp默认的一样,该配置可无

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
      logic-delete-value: 1 	# 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  • 全局逻辑删除: begin 3.3.0

    如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。

    使用此配置则不需要在实体类上添加 @TableLogic。

    但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除。

  • 若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。

  • 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。

  • 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。

    ​ 如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。

6. wapper条件构造器

image-20200519190242430

Wrapper : 条件构造抽象类,最顶端父类

AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

​ QueryWrapper : Entity 对象封装操作类,不是用lambda语法

​ UpdateWrapper : Update 条件封装,用于Entity对象更新操作

AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。

​ LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper

​ LambdaUpdateWrapper : Lambda 更新封装Wrapper

说明:

  • boolean condition表示该条件是否加入最后生成的sql中。默认为true
  • 泛型Param均为Wrapper的子类实例(均具有AbstractWrapper的所有方法)
  • 方法在入参中出现的R为泛型,在普通wrapper中是String,在LambdaWrapper中是函数(例:Entity::getId,Entity为实体类,getId为字段idgetMethod)

警告:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输

  1. wrapper 很重
  2. 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
  3. 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
  4. 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr

QueryWrapper:

@RunWith(SpringRunner.class)
@SpringBootTest
public class QueryWrapperTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testDelete() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .isNull("name")
                .ge("age", 12)
                .isNotNull("email");
        int result = userMapper.delete(queryWrapper);
        System.out.println("delete return count = " + result);
    }
}

lambdaQuery:

  • Wrappers.lambdaQuery()
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class LambdaQueryWrapperTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testDelete() {
        List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(false, User::getAge, 11).eq(User::getNickName, "a"));
        users.forEach((e) -> {
            e.setNickName("a");
            userMapper.updateById(e);
        });
    }
}

原文地址:https://www.cnblogs.com/luliang888/p/13282030.html