化繁为简,MyBatis-Plus 里面的增删改查

1,666 阅读7分钟

一. MP 里面的增删改查

在当前盛行的 SpringBoot 项目中,整合持久层这一块,目前主流的有两种:JPA 和 MyBatis-Plus。至于哪个用的更多一些,这个主要还是看每个公司的技术架构,但硬是要说一个最为常用的,我认为是 MyBatis-Plus,而在这里也是对 MyBatis-Plus 的一个使用进行演示

好了,废话不多说,直接开始吧。为了减少冗余,下面所有 MyBatis-Plus 都使用 MP 来代替

1.1 项目搭建

项目的创建就直接跳过了,直接从依赖开始吧。开始之前得先弄个表,表怎么弄都行,下面这个是我测试建的,可以参考一下

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
  `age` int(0) NOT NULL COMMENT '年龄',
  `mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号',
  `email` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更改时间',
  `update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更改人',
  `is_delete` int(0) NULL DEFAULT NULL COMMENT '是否删除 0-未删除 1-已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3554031 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
  1. 依赖

    (1) 目前 MP 的最新版本

            <!-- Mybatis-plus 依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.5.2</version>
            </dependency>
    

    (2) 当然,如果你想使用 MP 中的快速生成实体类,需要添加 MP 中的一个工具依赖且需导入一个模板引擎的依赖 (模板可以自定义,具体看 MP 官网的例子,这里直接使用 Freemark)

            <!-- Mybatis-plus 逆向自动生成实体类所需依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>3.5.2</version>
            </dependency>
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.31</version>
            </dependency>
    

    (3) 以上的都是 MP 中所需的依赖,除此之外,当然还少不了 SpringWeb 的依赖了;我们还需要对数据库进行操作,MySQL 的依赖也必不可缺

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
  2. 逆向工程创建实体类

    (1) 其实这个有好几种方法,自己手动创建也行;使用 IDEA 中的一些插件也行;或者使用 MP 官网提供的代码生成模板也行。不管用哪种方法,最终要做的都是一样的,就是把项目搭起来,实体类弄起来。

    (2) 在这里,就直接使用 MP 官网提供的代码模板吧。直接在 MP官网 —— 指南 —— 代码生成器 (新) 就能找到

    在这里插入图片描述

    (3) copy 这段代码到 maven 工程自带的单元测试中,然后根据注释去修改一下,直接运行即可。

    (4) 稍微解释一下怎么使用这个,其实很简单:

    • urlusernamepassword :即你数据库连接的地址、用户名、密码
    • outputDir :这个就是注释的意思,输出目录嘛~但注意一下,这个写到 java 目录即可。(可参考:"D:\code\test\test-demo\ts-mybatis\src\main\java")
    • parent :父包名 (一般是 com.xxx)
    • moduleName :父包名下的模块名 (即 com.xxx.yyy 这个 yyy 即是这个模块名了)
    • pathInfo :后面的那个地址即是 mapperxml 的路径了 (可参考:"D:\code\test\test-demo\ts-mybatis\src\main\resources\mapper")
    • addInclude :这个就更简单了,你要它帮你生成什么实体类,你就把表名往这这里填,注意的是,名字可别写错,不然识别不出来
    • addTablePrefix :这个就是一些表的前缀在生成到 Java 代码中的类名时,给你去掉前缀,没有可以不管他
       /**
        * mp 逆向工程生成实体类
        */
       @Test
       void contextLoads() {
           FastAutoGenerator.create("jdbc:mysql://localhost:3306/ts_mybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai", "root", "root")
                   .globalConfig(builder -> {
                       builder.author("Peng") // 设置作者
                               .enableSwagger() // 开启 swagger 模式
                               .fileOverride() // 覆盖已生成文件
                               .outputDir("D:\\code\\test\\test-demo\\ts-mybatis\\src\\main\\java"); // 指定输出目录
                   })
                   .packageConfig(builder -> {
                       builder.parent("com.peng") // 设置父包名
                               .moduleName("tsmybatis") // 设置父包模块名
                               .pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\code\\test\\test-demo\\ts-mybatis\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
                   })
                   .strategyConfig(builder -> {
                       builder.addInclude("user") // 设置需要生成的表名
                               .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                   })
                   .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                   .execute();
       }

(5) 注意,这个如果没有加 模板引擎 运行的时候会报错,加上 Freemarker 依赖即可

  1. 大功告成之后,在你的项目中 MP 就会把整个项目基础模块都给搭好了,如下图:

    在这里插入图片描述

  2. 注意,mapper 里面的扫描在 MP 中并不会帮你生成,所以这个 MapperScan 还需要自己去加上,在启动类上面加上一个 @MapperScan 这个注解,去扫描你的 mapper 文件

    @MapperScan(value = {"com.peng.tsmybatis.mapper"})
    

1.2 极其便利的单表操作

以下的测试都是,都是使用 Postman 进行测试的,在这里为了方便测试,全都是使用 GetMapping 去调用

1.2.1 增

    @Resource
    IUserService userService;

    @GetMapping("/add")
    public boolean add() {
        User user = new User();
        user.setName("李四");
        user.setAge(18);
        return userService.save(user);
    }

(1) 很简单,其实就是 new 了一个对象扔到 MP 的方法里面而已,通过观察 IUserService 这个接口可以发现,它继承了 IService<User> 且泛型里面放的就是你对应的实体类 User,因此在此 service 下的一切 MP 相关的方法都是针对 User 这个实体类的操作

(2) 因为 User 这个实体类是对应数据库中的 User 表的,所以需要注意的是当前的这个表设计中是否有哪些必填的字段,如果遗漏了(即没有 set 那个值到这个 User 对象中),那么就会报错

(3) 其他就没什么需要注意的,你想添加什么内容,就往这个 User 对象中 set 什么进去就可以了。成功即返回 true 了,反之则 false 了。另外如果你想查看 MP 帮你执行的 SQL 可以在 yaml 或者 properties 中加上以下这段。我这里使用的是 yaml 文件配置

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印 mybatis-plus 的执行日志

添加之后访问接口时便会有以下日志打印:

在这里插入图片描述

(4) 我这里使用的是 Postman 来测试这个接口,返回 true 则表示成功了,此时去数据库查看便可发现,多了一条 name = 李四;age = 18 的记录

在这里插入图片描述

1.2.2 批增

(1) 当我们有当量添加的需求时,不停的去 new 对象太麻烦了,因此 MP 为我们提供了批量添加的方法。

(2) 还是很简单,只是由原来的一个对象变成了集合而已。我们只需要把需要添加的信息,收集成一个集合即可,然后把这个集合丢给 MP ,剩下的事情 MP 就会帮我们去做了,像这样:

    @GetMapping("/addBatch")
    public boolean addBatch() {
        List<User> entityList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            User user = new User();
            user.setName("张三" + i);
            user.setAge(20 + i);
            entityList.add(user);
        }
        return userService.saveBatch(entityList);
    }

(3) 通过 Postman 访问 http://localhost:8082/addBatch 这个接口后返回 true,表示成功了,此时查看数据库就多了10条数据:

在这里插入图片描述

(4) 玩法很简单,只是从一个对象,变成了一个集合而已。所以 MP 还是很方便的,它在原来的 MyBatis 上增加了很多对数据库的操作方法,这些都只是它最基础的一些操作。

1.2.3 改、批改

(1) 这个与增其实是一样的,唯一的区别是需要传一个 ID,不然 MP 不知道你需要对哪条记录进行修改。所以只需要在上面的基础上多去 set 一个 id 就可以了,像这样:

    @GetMapping("/update")
    public boolean update() {
        User entity = new User();
        entity.setId(1);
        entity.setName("灵儿");
        entity.setAge(16);
        return userService.updateById(entity);
    }


    @GetMapping("/updateBatch")
    public boolean updateBatch() {
        List<User> entityList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            User user = new User();
            user.setId(i + 1);
            user.setName("拜月" + i);
            user.setAge(30 + i);
            entityList.add(user);
        }
        return userService.updateBatchById(entityList);
    }

(2) 增和改其实本质上是差不多的,运行成功之后,查看数据库的结果就变成了这样:

在这里插入图片描述

1.2.4 查

(1) 在 MP 中,查就更简单了,因为在我们实际项目中用的最多的就是查询了。当然往往我们的查询会伴随着很多条件,在 MP 中给我们提供了条件构造器,也是能够满足到我们不写 SQL 的场景的,这个后续再细聊。

(2) 对于单表的操作,在 MP 中就一个 list() 的方法就完成了

    @GetMapping("/list")
    public List<User> list() {
        return userService.list();
    }

(3) 通过 Postman 访问接口后,在 IDEA 中可以看到 MP 中打印出来的日志在这里插入图片描述

(4) 可能你会说,后面那些字段都没有数据,你不想去查出来,浪费资源,那应该怎么办呢?这时就需要我们使用条件构造器了,通过条件构造器去进行操作

    @GetMapping("/list")
    public List<User> list() {
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.select("id","name","age");
//        qw.lambda().select(User::getId, User::getName, User::getAge);  JDK8的方法
        return userService.list(qw);
    }

在这里插入图片描述

(5) 注意,括号里面的字符串需要与你数据库上能够对应上,否者查不出来的,很好理解,就和自己写 SQL 一样,如果查询的字段表中没有,显然也是查不出来的,MP 只是帮我们把 SQL 给写了而已

12.5 删、批删

(1) 这个其实和修改类似的,也是需要传一个 ID ,不然 MP 不知道你要删除的是哪一条数据。

(2) 还是一样,删除单条记录,只需要 new 一个对象然后传一个你要删除的 ID 进去,MP 就会帮你处理了;而批删的话则是一个集合了,也是需要传 ID 的

    @GetMapping("/delete")
    public Boolean delete() {
        User entity = new User();
        entity.setId(1);
        return userService.removeById(entity);
    }

    @GetMapping("/deleteBatch")
    public boolean deleteBatch() {
        List<User> entityList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            User user = new User();
            user.setId(i + 1);
            entityList.add(user);
        }
        return userService.removeBatchByIds(entityList);
    }

(3) 与修改稍微有点不一样的是,你不需要传修改的值过去了,只需要传你要删除的 ID 即可。

(4) 需要注意的是,MP 的这个方法是物理删除的。而在我们实际开发中,有些业务场景是逻辑删除的,也就是说我们的表中有一个类似 is_delete 的字段,删除时只需要修改这个字段由 0 变 1或是由 1 变 0 即可,具体看实际需求。

(5) 在 MP 中同样也封装了逻辑删除的一些配置,当你业务是逻辑删除时,可以在 IDEA 中这样去配置,告诉 MP 我这里使用的是逻辑删除,别真把我的记录给删除掉了~很简单,只需要在逻辑删除的字段上加一个注解就可以搞定

    @TableLogic(value = "0",delval = "1")
    private Integer isDelete;

(6) @TableLogic() 就是这个注解,可以去下载 MP 源码查看:第一个参数是未删除的值,第二个参数是已删除的值,这里就不放图片了,感兴趣的朋友可以点进去瞅瞅。

(7) 除了这种配置方式外,还可以在 application.yaml 或者 application.properties 那些配置文件中进行配置,就是说,当你有好多个实体类,而好多个实体类都是逻辑删除,那么也就意味着每一个注解里面的参数都要写上 value = "0",delval = "1" 这样一段,显然过于冗余。所以我们可以在配置文件中进行一个全局配置,像这样:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: 1 #删除
      logic-not-delete-value: 0 #未删除

(8) 加了这个之后,MP 就会对你当前的实体类所对应的表的 CRUD 操作都是带上逻辑删除的一个逻辑,也就是 is_delete=0 ,就是当前记录是未删除的。

(9) 如果在前面开启了 MP 的执行日志就会发现,MP 在执行 CRUD 的时候,最后都加上了一个 WHERE is_delete=0 的一个判断

1.3 一些小扩展

(1) 一般而言,我们设计表的时候,有一些字段是往往都是亘古不变的,像类似 create_time、update_time 那些,像我们当前的这个表:

在这里插入图片描述

(2) 像刚刚提到的两个字段,在我们每一次添加或者修改的时候都需要去更新一下时间,一个两个还好,如果多起来的话就会发现好多代码都是重复的,特别冗余。对此 MP 给我提供了解决方案,可以在进行插入、修改的时候就自动帮我们进行一个处理了

(3) 也和上面处理逻辑删除一样,一个注解就完事了,但这个需要去配置一下,创建一个配置类,实现 MetaObjectHandler 这个接口,重写他的两个方法就可以了,如下

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        /*
        参数一: 属性的名称
        参数二: 你想给这个属性设置的值
        参数三: 元对象,所有入参的 MetaObject 必定是 entity 或其子类的 MetaObject
         */
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("isDelete", 0, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}

(4) 写好配置类之后,在实体类上使用 @TableField() 这个注解和这个配置类呼应上就可以了,像这样

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

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

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

(5) 这里的意思就是说:当进行 INSERT 操作的时候,自动帮我 createTime、updateTime 字段都设置成当前时间,而 isDelete 则默认设置成 0;当 UPDATE 的时候,只更改 updateTime 的时候改成当前更改的时间

(6) 实践才是检验真理的唯一标准,咱们来试一下。我这里为了省事,就不用 Postman 去发请求访问接口了,直接在单元测试中进行

    @Resource
    private UserMapper userMapper;

    @Test
    public void test08() {
        User user = new User();
        user.setName("伏羲");
        user.setAge(131);
        
        int result = userMapper.insert(user);
        System.out.println(result == 1 ? "添加成功" : "添加失败");
    }

从控制台打印的日志可以看到,create_time, update_time, is_delete 这三个值 MP 默认帮我去添加了数据,而添加的数据正是我们在配置文件上设置的

在这里插入图片描述

(7) UPDATE 也是一样的,这里就不演示了

1.4 小结

  1. 在 MP 中对单表的操作是非常方便的,如果对于需要连表的一对多或多对多,需要在对应的实体类上加一些注解啥的,具体还没去研究过;因为个人觉得,在一些较为复杂的表结构中,还不如自己写 SQL 来的快。但是如果是对单表操作,强烈建议用 MP 自带的方法,谁还写 SQL 呢~

  2. 当然目前为止演示的都是很简单的 CRUD,实际操作上肯定是不一样的。因为在我们实际操作中,可能会频繁的使用 MP 中的条件构造器去根据业务不同来返回数据。这个会在后面继续深入去演示一些 MP 中常用到的方法

  3. 另外需要说明一下的是,上述演示的代码中,都是在 Controller层中进行的。我这里是为了方便,MP 中的 IService 中已经有 CRUD 的方法了,就没去到 service 层去操作。按照代码的规范,还是更建议去到 service 层中处理业务逻辑的。

  4. 可能第三点会有些绕口,基础好的可能明白我说的意思了,而至于基础稍微没那么好的,既然都看到这里了,我还是在简单的演示一下标准版吧...比如要返回一个“用户菜单”

    (1) Controller

        @Resource
        private IUserService userService;
    
        @GetMapping("/userMenus")
        public List<User> userMenus() {
            List<User> result = userService.userMenus();
            //如果结果不为空,返回结果;否之返回 null
            return ObjectUtils.isNotEmpty(result) ? result : null;
        }
    

    (2) Service,注入一个 Mapper,其中也有 MP 自带的方法。正常来说,上面演示的都应该在这里,也就是 Service 层进行处理,再返回到 Controller 中

        @Autowired
        private UserMapper userMapper;
    
        @Override
        public List<User> userMenus() {
            QueryWrapper<User> qw = new QueryWrapper<>();
            qw.lambda().select(User::getId, User::getName, User::getAge);
            return userMapper.selectList(qw);
        }
    

    (3) 如果需要写 SQL ,那么也是一样的,只是跳到 Mapper 这个接口,然后再跳到 MapperXML 中去写而已,当然你也可以在 Mapper 时使用一些 MyBatis 的注解(@Select、@Update...之类的),我这里直接放个图片了,就不一步步演示了

在这里插入图片描述

或者类似这样

在这里插入图片描述

增删改查之后便是条件构造器的使用了,可以看我这篇文章:juejin.cn/post/716874…