概述

一款方便开发的工具,按照现在我的水平,它足以覆盖我99.9%的需求……
当然,这只是暂时的!

官网在此:Mybatis-Plus

有事翻官网,所有的疑问都能在那里找到答案。

虽然说官网写得非常详细了,但是我还是要记录一下自己在学这个工具的时候的一些代码or笔记,会有很多和官网重复的。并不是完全抄袭噢,这样抄也没意思……是个人记录。

快速开始

查看官网:快速开始

想想好像也啥可写的,跟着快速开始走就行了,能跑通开始为准。跟着做,总不会出错的。
不过还是要注意一下,导入的依赖、配置等等,不要弄错了。

另外,最好把配置给弄上,这样才知道它执行了什么操作。
这是application.yml的一部分

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

CRUD

展示一下它最强大的功能:替我们做好基本的CRUD操作。当然,在此之前,我们还得搭好一套用于测试的代码片段,如下。

@SpringBootTest
public class MyTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void t() {
        // write your code
    }
}

因为主要是使用Mapper的接口,更多详细的接口用法可以查看这里
(不过现在IDEA这么智能,.一下,写几个词上去,大概知道用哪个了)

插入

int insert(T entity);

    @Test
    public void test() {
        // write your code
        User user = User.builder()
                .name("timmm")
                .age(99)
                .email("666@qq.com").build();
        userMapper.insert(user);
    }

查看数据库中的信息,发现:
1419541351104454657,timmm,99,666@qq.com

为什么这个ID这么奇怪呢?
这就要谈到雪花算法……

主键生成策略

这些主键策略,其实都是唯一ID生成方案,唯一

雪花算法:它是Twitter开源的分布式ID生成算法。
它是一个Long型的ID。

image.png

组成部分(64bit)

  1. 第一位 占用1bit,其值始终是0,没有实际作用。
  2. 时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间。
  3. 工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点。
  4. 序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID。

SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢:: 同一毫秒的ID数量 = 1024 X 4096 = 4194304


如何设置主键生成策略?
只要在你的实体类的ID上加上这么个注解即可:@TableId(value = "id", type = IdType.ASSIGN_ID)
type的部分代码如下(过时的字段已经去除),看着它的注释来用就行了。

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string),
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),

    /**
     * 分配UUID (主键类型为 string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4),
    
    private final int key;

    IdType(int key) {
        this.key = key;
    }
}

更新

感觉……这个用起来是真的容易用呀,看看他的接口就行了,没啥技术含量。

更新,传入一个实体类和一个Wrapper。(什么是Wrapper?它是一个条件构造器我觉得它像是那个“万能的map”,里面放了一堆条件,然后他就会按照你的条件来进行筛选)
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);

更新,直接更新你实体中所设置的字段。
int updateById(@Param(Constants.ENTITY) T entity);

自动填充

上面的使用非常容易,但是更实用的功能是自动填充。

比如我想在数据库中为用户这个字段加上时间相关的记录,比如添加gmt_creategmt_update两个字段,表示创建时间和更新时间。

思考一下,这两个时间需要自己每次更新或每次插入的时候手动set一下时间吗?这里有更智能的操作,就是采用某种手段让它自己给填上这两个字段。

  1. 没学习之前,想到的就是数据库设置默认值,然后在每次更新的时候创建一个时间,赋值给他,也就是上面说的,确实是可行,但下面的更智能。
  2. 采用自动填充功能

在数据库修改了字段之后,首先要在实体类中加上这两个字段。
同时,还要加上填充的操作策略。

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime gmtCreate;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime gmtUpdate;

策略见名思义,这里也摘抄一下它的策略枚举类。

public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}

然后填写填充器

PS:这里不要忘记配置了,你想要哪个字段设置成哪个初值,就要这里写好了。

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 傻了,居然没匹配填充器  
        this.strictUpdateFill(metaObject, "gmtUpdate", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "gmtUpdate", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
    }
}

经过这样的操作之后,就能在你设想的场景,对值进行自动填充了。

乐观锁

MP也提供了实现乐观锁的插件,使用起来也很简单。

乐观锁,它的概念其实非常简单,就是它总是十分“乐观”的,总是认为数据是没有变化的;当它发现数据被改动的时候,就进行别的操作。

要实现这个乐观锁,需要加一个版本号version。因为它的逻辑是,数据每被修改,其版本号就要+1。当他发现他将要修改的版本号与预期的不符时,即表示已经有别的线程在他之前修改了!

在数据库中加一个字段version,然后在实体类的version上,也加一个注解@Version,它代表开启乐观锁。

然后再编写一个配置类,在里面注册一下乐观锁的插件。
(这里也能配置其它插件的东西,只要add一下就行。而且我还发现它好像是一个过滤器一样的东西?所有的数据都会经过它一下……)

// 自动管理事务
@EnableTransactionManagement
// mybatis的扫描,放到这里
@MapperScan("com.example.testmybatisplus.mapper")
@Configuration
public class MybatisPlusConfig {
    /**
     * 注册乐观锁插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 乐观锁的过滤
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 分页插件的过滤
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

然后就能用啦。

    @Test
    public void fail(){
        User user1 = userMapper.selectById(2);
        user1.setName("op1111111");
        user1.setEmail("ltiukttyk@qq.com");

        User user2 = userMapper.selectById(2);
        user2.setName("op2222222222222");
        user2.setEmail("mfryjw@qq.com");

        // user1抢先更新
        userMapper.updateById(user2);

        // 如果没有乐观锁,user2的值就会被覆盖
        // 这里可以使用自旋锁进行判断
        userMapper.updateById(user1);
    }

查询操作

没意思呀没意思,这工具是真的好用,简单又直观。看着文档来就行了,我就不再赘述了。

不过这里有意思是的分页查询,它也替我们做了相当多的工作,把复杂的分页查询优化成像普通的查询一样,十分简单。

要想使用它,首先还是得在MybatisPlusInterceptor的配置上加上这一行:interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

然后就能用了,比如:

        IPage<User> page = new Page<>(2,3);
        userMapper.selectPage(page, null);
        // 获取分页后的数据
        page.getRecords().forEach(System.out::println);

好像还有很多可以定制化的东西,需要的时候再查看吧。

删除

简单的删除操作可以查看文档,上手就能用的那种。

这里提了一个逻辑删除的概念,就是数据从逻辑上是查不到了,实际上它还在数据库中,保存得好好着。

这个怎么操作呢?其实是一个update操作,在数据库中加一个字段,比如deleted,然后默认值是0,表示数据正常;当它被删除后,把这个字段改成1,以后在查询的时候,只要发现deleted字段为1的数据都跳过。
这个操作自己做也不难实现的,但是如果有这个工具的话,就更简单了!

首先在数据库中添加一个deleted字段,然后在实体类中也对应添加上,并且加上一个注解:@TableLogic

    @TableLogic
    private Integer deleted;

最后再配置一下就行 .

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

好像也不用改那个字段名,反正配置+注解,它就能找到你要设置的逻辑删除的,然后就能解决了。(总结:它很智能【x】智能到我设置错了都可以工作正确……好像是可以只使用注解,这样就不用在配置文件上写了)

一点补充

Wrapper

关于Wrapper,好像它用得更多的是和lambda表达式一起使用,所以这块的话暂时还是先看官方文档,之后在学Java8新特性的时候我会再回来补充的,或者说再开一篇新的。

代码生成器

这个也很智能,我在之前写的那个博客项目里面就是用它,然后几乎不用自己加点什么东西了……这块,暂时还是直接拿起来用吧,需要什么再配置什么,我在这里贴一下它的代码,以后如果有机会多用几次,我也会再在这基础上作修改的。
另附上官方文档:使用详细配置

package com.example.testmybatisplus;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;

public class TestAutoGenerate {
    @Test
    public void autoGenerate() {
        // Step1:代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // Step2:全局配置
        GlobalConfig gc = new GlobalConfig();
        // 填写代码生成的目录(需要修改)
        String projectPath = "F:/testmybatisplus";
        // 拼接出代码最终输出的目录
        gc.setOutputDir(projectPath + "/src/main/java");
        // 配置开发者信息(可选)(需要修改)
        gc.setAuthor("tjm");
        // 配置是否打开目录,false 为不打开(可选)
        gc.setOpen(false);
        // 实体属性 Swagger2 注解,添加 Swagger 依赖,开启 Swagger2 模式(可选)
        //gc.setSwagger2(true);
        // 重新生成文件时是否覆盖,false 表示不覆盖(可选)
        gc.setFileOverride(true);
        // 配置主键生成策略,此处为 ASSIGN_ID(可选)
        gc.setIdType(IdType.ASSIGN_ID);
        // 配置日期类型,此处为 ONLY_DATE(可选)
        gc.setDateType(DateType.ONLY_DATE);
        // 默认生成的 service 会有 I 前缀
        gc.setServiceName("%sService");
        mpg.setGlobalConfig(gc);

        // Step3:数据源配置(需要修改)
        DataSourceConfig dsc = new DataSourceConfig();
        // 配置数据库 url 地址
        dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定数据库名
        // 配置数据库驱动
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        // 配置数据库连接用户名
        dsc.setUsername("root");
        // 配置数据库连接密码
        dsc.setPassword("admin");
        mpg.setDataSource(dsc);

        // Step:4:包配置
        PackageConfig pc = new PackageConfig();
        // 配置父包名(需要修改)
        pc.setParent("com.example");
        // 配置模块名(需要修改)
        pc.setModuleName("testmybatisplus");
        // 配置 entity 包名
        pc.setEntity("entity");
        // 配置 mapper 包名
        pc.setMapper("mapper");
        // 配置 service 包名
        pc.setService("service");
        // 配置 controller 包名
        pc.setController("controller");
        mpg.setPackageInfo(pc);

        // Step5:策略配置(数据库表配置)
        StrategyConfig strategy = new StrategyConfig();
        // 指定表名(可以同时操作多个表,使用 , 隔开)(需要修改)
        strategy.setInclude("user");
        // 配置数据表与实体类名之间映射的策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 配置数据表的字段与实体类的属性名之间映射的策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 配置 lombok 模式
        strategy.setEntityLombokModel(true);
        // 配置 rest 风格的控制器(@RestController)
        strategy.setRestControllerStyle(true);
        // 配置驼峰转连字符
        strategy.setControllerMappingHyphenStyle(true);
        // 配置表前缀,生成实体时去除表前缀
        // 此处的表名为 test_mybatis_plus_user,模块名为 test_mybatis_plus,去除前缀后剩下为 user。
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);

        // Step6:执行代码生成操作
        mpg.execute();
    }

}