Mybatis-Plus基础功能测试使用

时间:2022-07-22
本文章向大家介绍Mybatis-Plus基础功能测试使用,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

该文章持续不定期更新中,最后一次更新于2020年6月22日

Mapper层接口

Service层接口

说明:

  • 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
  • 对象 Wrapper条件构造器

插入数据

在此之前,请务必开启日志配置。

添加测试方法,我们不设置id,看看会不会报错。

@Test
    public void testInsert(){
        User user = new User();
        user.setName("乐心湖");
        user.setAge(18);
        user.setEmail("jialna@qq.com");
        int insert = userMapper.insert(user);
        System.out.println(insert);
        System.out.println(user);
    }

我们看到插入成功了,观察控制台中的sql语句,发现它自动给我们生成了一个id。

这就是下面说的雪花算法。

主键生成策略

推荐阅读:分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

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

mybaits-plus中默认的使用的是ID_WORKER,即@TableId(type = IdType.ID_WORKER)使用的是雪花算法生成,全局唯一id。当然我们也可以自己修改主键生成策略,如主键自增。

雪花算法

snowflflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

主键自增

我们首先需要在User中对id主键开启自增,然后在User类里的id属性添加一个注解。

@TableId(type = IdType.AUTO)

@TableId

描述

AUTO

数据库自增

NONE

MP set 主键,雪花算法实现

INPUT

需要开发者手动赋值

ASSIGN_ID

MP 分配 ID,Long、Integer、String

ASSIGN_UUID

分配 UUID,Strinig

INPUT 如果开发者没有手动赋值,则数据库通过自增的方式给主键赋值,如果开发者手动赋值,则存入该值。

AUTO 默认就是数据库自增,开发者无需赋值。

ASSIGN_ID MP 自动赋值,雪花算法。

ASSIGN_UUID 主键的数据类型必须是 String,自动生成 UUID 进行赋值

UUID

这需要你的id为String,也就是数据库id改为varchar类型。

生成一个全局唯一的id,带字母和数字。

换了。。。现在叫ASSIGN_UUID

方法

主键生成策略

主键类型

说明

nextId

ASSIGN_ID,ID_WORKER,ID_WORKER_STR

Long,Integer,String

支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer

nextUUID

ASSIGN_UUID,UUID

String

默认不含中划线的UUID生成

public enum IdType { 

    AUTO(0), // 数据库id自增 

    NONE(1), // 未设置主键 

    INPUT(2), // 手动输入 

    ASSIGN_ID(3), // 默认的全局唯一id 

    ASSIGN_UUID(4), // 全局唯一id uuid 

}

更新数据

    @Test
    public void testUpdate() {
        User user = new User();
        //通过条件自动拼接动态sql 
        user.setId(6);
        user.setName("乐心湖666");
        user.setAge(18);
        //注意:updateById 但是参数是一个 对象! 
        int i = userMapper.updateById(user);
        System.out.println(i);
    }

自动填充

参考:https://mp.baomidou.com/guide/auto-fill-metainfo.html

我们在数据库表中经常会有两个字段,分别是create_time和update_time,对应创建时间和更新时间

我们有两种操作,一个是在数据库操作,一个是在代码中操作,前者不推荐

数据库操作(不建议)

  • 类型为datetime
  • 默认为:CURRENT_TIMESTAMP
  • 勾选上自动更新

代码操作

Mybatis-Plus 时间自动填充

乐观锁

需求:当要更新一条记录的时候,希望这条记录没有被别人更新

我们使用一个场景来帮助理解

场景

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。

现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。

乐观锁与悲观锁

上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。

如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。

乐观锁实现方式:

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

简单理解就是在多线程的时候,如果需要去更新一条数据的时候刚好这条数据被别人更新了,版本验证不对,就更新失败。

示例SQL原理:

update tbl_user set name = 'update',version = 3 where id = 100 and version = 2

数据库操作

在表中建立一个字段,叫version,默认为1,注释为乐观锁

插件配置

@Configuration
public class MybatisPlusConfig {

    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

在User类中加入version字段,并且必须带上注解@Version

@Version
private Integer version;

测试

    @Test
    public void testUpdate(){
        User user = userMapper.selectById(12L);
        user.setAge(11);
        userMapper.updateById(user);
    }

特别说明:

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

查询数据

Id查询

    @Test
    public void testSelectById(){
        User user = userMapper.selectById(12);
        System.out.println(user);
    }

Id批量查询

    @Test
    public void testSelect(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1,12));
        System.out.println("------------------------");
        users.forEach(System.out::println);
    }

Map查询

比如当我们想查年龄为16岁的人

    @Test
    public void testSelectByBatchIds(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","乐心湖是小白");
        map.put("id","12");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }

分页查询

一个非常常用的功能,在MP中能够非常非常简单就帮你搞定。

写一个配置类。

//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

测试第二页的4条记录,效果立竿见影。

注意:查询出来的结果自动返回到了page里,不需要我们重新定义去接收查询结果。

建议:仔细查看官方关于分页的介绍和案例,具体开发实战并不会在这里测试。

    @Test
    public void testSelectPage(){
        /**
         * current: 第几页
         * size:每页的数量
         */
        Page<User> page = new Page<>(2, 4);
        userMapper.selectPage(page, null);
        for (User user : page.getRecords()) {
            System.out.println(user);
        }
    }

删除数据

Id删除

    @Test
    public void testDeleteById(){
        userMapper.deleteById(1243823657826996248L);
    }

Map删除

    @Test
    public void testDeleteByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","乐心湖是小白");
        userMapper.deleteByMap(map);
    }

Id批量删除

@Test public void testDeleteBatchId(){
    userMapper.deleteBatchIds(Arrays.asList(1240620674645544961L,124062067464554496 2L));
    
}

逻辑删除

  • 物理删除 :从数据库中直接移除
  • 逻辑删除 :在数据库中没有真的被移除,而是通过一个变量来让他失效
  • deleted = 0 ——> deleted = 1
  • 管理员可以查看被删除的记录,防止数据的丢失,类似于回收站的功能。

在数据表中增加一个deleted字段,默认为0

SpringBoot 配置方式:

  • application.yml 加入配置(如果你的默认值和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)

实体类字段上加上@TableLogic注解

@TableLogic
private Integer deleted;

效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)

example
删除时 update user set deleted=1 where id =1 and deleted=0
查找时 select * from user where deleted=0

全局逻辑删除:3.3.0开始支持

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

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

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

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  #全局逻辑删除字段值

附件说明

  • 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
  • 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。

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

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