我的技术学习物语果然有问题
(最后更新 )

MybatisPlus常用方法

一些MybatisPlus常见的使用方法。

简介

记录一下常用的一些使用方法。

测试环境

表SQL

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
    `id`            CHAR(19)        NOT NULL                        COMMENT '数据ID',
  	`username`      VARCHAR(200)    NOT NULL                        COMMENT '用户名',
  	`password`      VARCHAR(200)    NOT NULL                        COMMENT '密码',
  	`nickname`      VARCHAR(200)    NULL    DEFAULT '用户昵称'       COMMENT '用户昵称',
  	`avatar`        VARCHAR(200)    NULL    DEFAULT ''              COMMENT '用户头像地址',
  	`email`         VARCHAR(200)    NULL    DEFAULT '123456@qq.com' COMMENT '用户邮箱地址',
  	`status`        BIT DEFAULT 1   NOT NULL                        COMMENT '账号状态,0禁用|1正常',
    `sex`        	BIT DEFAULT 1   NOT NULL                        COMMENT '性别,0是女|1是男',
    `phone`         VARCHAR(200)    NULL    DEFAULT '18888888888' 	COMMENT '用户邮箱地址',
    `create_time`   DATETIME        NOT NULL                        COMMENT '创建时间',
    `create_by`     VARCHAR(200)    NOT NULL                        COMMENT '创建人',
    `update_time`   DATETIME        NOT NULL                        COMMENT '更新时间',
    `update_by`     VARCHAR(200)    NOT NULL                        COMMENT '更新人',
    PRIMARY KEY (`id`),
    CONSTRAINT  username unique (username)
  ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '用户信息实体类';

实体类

package com.yww.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * <p>
 *      (user)用户信息实体类
 * </p>
 *
 * @Author yww
 * @Date 2023-2-20
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
@Schema(name = "User", description = "用户信息实体类")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "数据ID")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;

    @Schema(description = "用户名")
    @TableField("username")
    private String username;

    @Schema(description = "密码")
    @TableField("password")
    private String password;

    @Schema(description = "用户昵称")
    @TableField("nickname")
    private String nickname;

    @Schema(description = "用户头像地址")
    @TableField("avatar")
    private String avatar;

    @Schema(description = "用户邮箱地址")
    @TableField("email")
    private String email;

    @Schema(description = "性别,0是女|1是男")
    @TableField("sex")
    private Boolean sex;

    @Schema(description = "电话号码")
    @TableField("phone")
    private String phone;

    @Schema(description = "账号状态,0禁用|1正常")
    @TableField("status")
    private Boolean status;

    @Schema(description = "创建时间")
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @Schema(description = "创建人")
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    private String createBy;

    @Schema(description = "更新时间")
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @Schema(description = "更新人")
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

}

执行SQL分析打印

使用p6spy来打印SQL。

        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>${p6spy.version}</version>
        </dependency>

配置文件

  # 数据库配置(p6spy)
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/demo?userUnicode=true&useSSL=false&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: password

假数据的生成

这里使用了datafaker这个库来生成假数据。

        <dependency>
            <groupId>net.datafaker</groupId>
            <artifactId>datafaker</artifactId>
            <version>1.8.0</version>
        </dependency>

随机生成600条数据。

    @Override
    public void random(Integer number) {
        Faker cnFaker = new Faker(new Locale("zh-CN"));
        Faker enFaker = new Faker(new Locale("en"));
        Random random = new Random();
        for (int i = 0; i < number; i++) {
            User user = User.builder()
                    .username(enFaker.name().username() + i)
                    .password(enFaker.passport().valid())
                    .nickname(cnFaker.name().name())
                    .avatar(enFaker.avatar().image())
                    .email(StrUtil.cleanBlank(enFaker.name().fullName().toLowerCase()) + "@qq.com")
                    .sex(random.nextBoolean())
                    .phone(StrUtil.cleanBlank(cnFaker.phoneNumber().phoneNumber()))
                    .status(true)
                    .build();
            this.baseMapper.insert(user);
        }
    }

代码结构

环境使用MybatisPlus代码生成器的结构。

  1. service继承IService
  2. serviceImpl继承ServiceImpl,实现service
  3. mapper继承BaseMapper

补充说明

关于服务实现类的API

根据上述的代码结构,在服务的实现类中,会有两套API。

  1. Service CRUD接口
  2. Mapper CRUD接口

前者算是对Mapper接口的封装,所以前者会有Mapper接口里基本所有方法,也会添加很多封装后的方法。后者是基础的Mapper使用。

所以以下大多数使用都是基于前者,当有用到Mapper里的方法才会使用后者。

关于条件构造器

这里以查询的条件构造器为例子。

QueryWrapper

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 1);
        this.getOne(queryWrapper);

LambdaQueryWrapper

这个条件构造器使用了Lambda语法,使用起来更方便,而且使用这个方法不用构造条件,不用填写表的字段,因为字段这种不能修改的对开发来说这是十分不友好的,LambdaQueryWrapper可以直接使用实体类属性来构造条件。

  1. 使用QueryWrapperlambda方法获取

            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", 1);
            LambdaQueryWrapper<User> lambda = queryWrapper.lambda();
            this.getOne(lambda);
  2. 使用Wrappers.lambdaQuery()获取(推荐)

            LambdaQueryWrapper<User> lambda = Wrappers.lambdaQuery(User.builder().id("1").build());
            this.getOne(lambda);

以下的条件构造器,都是基于第二种方法生成。

防全表更新与删除插件

这个插件我感觉还是很有必要的,要是开发出现一点错误,这个插件能多一份保障。当出现全表更新或者是全表删除的SQL时,MybatisPlus能帮我们拦截,并抛出异常。

官方插件说明

    /**
     * 插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 防全表更新与删除插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }

分页插件

分页时需要用到的插件。

官方插件说明

	/**
     * 插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件,指定数据库为MYSQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

多个插件使用的情况,请将分页插件放到 插件执行链 最后面。

添加

添加单条数据

    @Override
    public boolean insert(User user) {
        User insertUser = User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .nickname(user.getNickname())
                .avatar(user.getAvatar())
                .email(user.getEmail())
                .sex(user.getSex())
                .phone(user.getPhone())
                .status(user.getStatus())
                .build();
        // 可以进行一些操作校验数据,比如说username是唯一的等等
        return this.save(insertUser);
    }
  1. 添加的数据尽量自己先提取在添加,避免添加了不该添加的数据,比如说ID,不建议传入ID,更新用户这些字段,尽量避免这些脏数据。
  2. 考虑添加数据的条件,比如username是唯一的,某些数据是不为空的,或者某些数据是有什么格式的,都可以先将提取的元素进行判断后在添加。
  3. 只通过前端校验参数是不合理的。

批量添加

这里就不从请求上输入用户列表了,通过faker模拟生成用户列表。

    /**
     * 随机生成number条用户数据
     *
     * @param number    指定数量
     * @return          用户数据列表
     */
    private List<User> getUsers(int number) {
        Faker cnFaker = new Faker(new Locale("zh-CN"));
        Faker enFaker = new Faker(new Locale("en"));
        Random random = new Random();
        List<User> userList = new ArrayList<>(number);
        for (int i = 0; i < number; i++) {
            User user = User.builder()
                    .username(enFaker.name().username() + i)
                    .password(enFaker.passport().valid())
                    .nickname(cnFaker.name().name())
                    .avatar(enFaker.avatar().image())
                    .email(StrUtil.cleanBlank(enFaker.name().fullName().toLowerCase()) + "@qq.com")
                    .sex(random.nextBoolean())
                    .phone(StrUtil.cleanBlank(cnFaker.phoneNumber().phoneNumber()))
                    .status(true)
                    .build();
            userList.add(user);
        }
        return userList;
    }

for循环添加

这是不推荐的方法,但也算是可行的一种方法,这种方法效率很低。

    @Override
	@Transactional(rollbackFor = Exception.class)
    public boolean insertBatch1(List<User> userList) {
        userList = getUsers(10);
        boolean res = true;
        for (User user : userList) {
            res = res && this.insert(user);
        }
        return res;
    }

通过p6spy的打印,可以看到执行的其中一条插入语句。

 Consume Time:1 ms 2023-02-22 15:30:30
 Execute SQL:INSERT INTO user ( id, username, password, nickname, avatar, email, sex, phone, status, create_time, create_by, update_time, update_by ) VALUES ( '1628296156785729537', 'lonnie.walsh0', 'X75170066', '弓远航', 'https://robohash.org/nnawetiq.png', 'mr.brandonthiel@qq.com', true, '15024489626', true, '2023-02-22T15:30:30.725', 'yww', '2023-02-22T15:30:30.725', 'yww' )

saveBatch的伪批量插入

使用saveBatch最好先通过@EnableTransactionManagement注解开启mybatis-plus的事务管理,不然可能会出现警告。(虽然可能不会影响操作)

    @Override
    public boolean insertBatch2(List<User> userList) {
        userList = getUsers(10);
        return this.saveBatch(userList);
    }

通过p6spy的打印,可以看到执行的其中一条插入语句。

 Consume Time:0 ms 2023-02-22 15:32:05
 Execute SQL:INSERT INTO user ( id, username, password, nickname, avatar, email, sex, phone, status, create_time, create_by, update_time, update_by ) VALUES ( '1628296554896482305', 'aline.aufderhar0', '296752366', '商鑫鹏', 'https://robohash.org/zmvciuuf.png', 'randalwolfiii@qq.com', false, '15067200564', true, '2023-02-22T15:32:05.643', 'yww', '2023-02-22T15:32:05.643', 'yww' )

这里可以发现saveBatch的插入语句和for循环的插入语句是一样的,都是有10条INSERT语句,而不是真正的批量插入,只不过saveBatch是10条SQL语句一起提交,而for循环是10条语句10次提交,这里的效率差距还是很明显的,但由于这样也不算是批量插入,所以saveBatch被称为伪批量插入。

由于是一次提交,如果一次性插入成千上万条数据,也会导致数据库响应缓慢,所以当插入的数据量很大时,还是建议先分片,在提交。

    @Override
    public boolean insertBatch2(List<User> userList) {
        userList = UserUtil.getUsers(10);
        // 按每次500条数据进行插入
        return saveBatch(userList, 500);
    }

批量插入

这种方案效率是最高的,但是相应的需要去配置一下。

  1. 创建批量插入SQL注入器

    /**
     * <p>
     *      批量插入SQL注入器
     * </p>
     *
     * @author yww
     * @since 2023/2/22 15:53
     */
    public class InsertBatchSqlInjector extends DefaultSqlInjector {
        @Override
        public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
            // 获取MybatisPlus的自带方法
            List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
            // 添加自定义批量插入方法,名称为insertBatchSomeColumn
            methodList.add(new InsertBatchSomeColumn());
            return methodList;
        }
    }
  2. 将SQL注入器添加到mybatis-plus配置中

    @Configuration
    @EnableTransactionManagement
    @MapperScan("com.yww.demo.mapper")
    public class MybatisPlusConfig {
    
        /**
         * 自定义批量插入 SQL 注入器
         */
        @Bean
        public InsertBatchSqlInjector insertBatchSqlInjector() {
            return new InsertBatchSqlInjector();
        }
    
    }
  3. 在Mapper类中添加方法

    @Repository
    public interface UserMapper extends BaseMapper<User> {
    
        /**
         * 批量插入 仅适用于mysql
         * @param batchList     实体列表
         * @return              影响行数
         */
        @SuppressWarnings("MybatisMapperMethodInspection")
        int insertBatchSomeColumn(@Param("list") List<User> batchList);
    
    }

注意,insertBatchSomeColumn为内部指定的方法名字,不用在xml中写具体的SQL语句。IDE的警告可以忽略,上述添加的SQL注入器会帮我们注入具体的SQL语句,当然不用这么麻烦的配置,自己去xml中拼接SQL也是可以的。

可以看看官方的一个方法解释。

/**
 * 批量新增数据,自选字段 insert
 * <p> 不同的数据库支持度不一样!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!! </p>
 * <p> 除了主键是 <strong> 数据库自增的未测试 </strong> 外理论上都可以使用!!! </p>
 * <p> 如果你使用自增有报错或主键值无法回写到entity,就不要跑来问为什么了,因为我也不知道!!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int insertBatchSomeColumn(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意: 这是自选字段 insert !!,如果个别字段在 entity 里为 null 但是数据库中有配置默认值, insert 后数据库字段是为 null 而不是默认值 </li>
 *
 * <p>
 * 常用的 {@link Predicate}:
 * </p>
 *
 * <li> 例1: t -> !t.isLogicDelete() , 表示不要逻辑删除字段 </li>
 * <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名为 version 的字段 </li>
 * <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略为 UPDATE 的字段 </li>
 *
 * @author miemie
 * @since 2018-11-29
 */

具体的使用。

    @Override
    public int insertBatch3(List<User> userList) {
        userList = getUsers(10);
        return this.baseMapper.insertBatchSomeColumn(userList);
    }

SQL打印为

 Consume Time:3 ms 2023-02-22 16:04:37
 Execute SQL:INSERT INTO user (id,username,password,nickname,avatar,email,sex,phone,status,create_time,create_by,update_time,update_by) VALUES ('1628304743473872898','tod.jacobs0','Y19482857','房晟睿','https://robohash.org/fmcgfafy.png','marileemacejkovic@qq.com',false,'13069884517',true,'2023-02-22T16:04:37.951','yww','2023-02-22T16:04:37.951','yww') , ('1628304743473872899','leo.wilderman1','X45343338','庾瑞霖','https://robohash.org/calkekge.png','jefferyarmstrong@qq.com',true,'18603800047',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872900','dorris.kutch2','Y41776964','胥擎宇','https://robohash.org/cbrcnhor.png','darwindickinson@qq.com',false,'15855129426',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872901','julio.dubuque3','529376562','辛鸿煊','https://robohash.org/atjuqhwo.png','annemariejerde@qq.com',true,'13764269293',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872902','alvera.gerlach4','Z05050838','骆泽洋','https://robohash.org/kttvknqi.png','vincenzoortiz@qq.com',false,'18764984449',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872903','robert.walter5','Z47884351','茅志强','https://robohash.org/gigofxue.png','missjosphinemarquardt@qq.com',false,'16550365693',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872904','curtis.considine6','X36270777','满明哲','https://robohash.org/zkakbsnh.png','mitzidaugherty@qq.com',false,'18605996014',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872905','stewart.mayert7','Z27480881','舒文轩','https://robohash.org/wnmekekg.png','pierreconsidine@qq.com',false,'13988711400',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872906','winnie.mills8','Z66334442','刘擎苍','https://robohash.org/snbyikas.png','taneshapagac@qq.com',true,'19603769365',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww') , ('1628304743473872907','lanita.walker9','Y43021930','边鸿煊','https://robohash.org/wgypkmjj.png','dorseyrath@qq.com',true,'17297354287',true,'2023-02-22T16:04:37.952','yww','2023-02-22T16:04:37.952','yww')

可以看到这个SQL的格式为

INSERT INTO user (..) VALUES (…),(…),(…),(…)

这种格式才算是批量插入,而且效率会高上很多。

同理,一次性不建议插入太多条数据,所以还是需要分片。insertBatchSomeColumn这个mapper方法我找了一下源码,好像没见到自带的分片方法,所以需要手动分片然后进行插入。操作如下。

    @Override
	@Transactional(rollbackFor = Exception.class)
    public int insertBatch3(List<User> userList) {
        userList = UserUtil.getUsers(10);
        // 按每次500进行插入
        List<List<User>> list = ListUtil.partition(userList, 500);
        int res = 0;
        for (List<User> i : list) {
            res += this.baseMapper.insertBatchSomeColumn(i);
        }
        return res;
    }

总结

使用第三种发放是效率最高的,所以建议使用第三种,当然嫌麻烦配置,直接用第二种也可以。毕竟数据量不是特别大,差距可以忽略。

尽量不要使用for循环的操作进行数据批量操作,消耗的时间是很多的。

所以之后的批量操作,就不记录使用for循环的方案了。

删除

根据ID删除单条数据

直接调用API即可。

    @Override
    public boolean deleteById(String userId) {
        // 有必要的话可以先查出数据,进行处理后在删除
        // entity = select(userId);   this.removeById(entity)
        this.removeById(userId);
        return false;
    }

根据其他条件删除

    @Override
    public boolean deleteByCondition(User user) {
        return this.remove(Wrappers.lambdaQuery(user));
    }

这个方法,这里只是用来记录一下,我感觉这个方法十分的危险,实际开发建议不要这样写,当user为空,或者是里面的字段全是空值,就会导致语句变成

DELETE FROM user

这样的SQL会导致全表数据删除,所以尽量使用ID进行删除。

实在需要用到这种方法,请先判定传入的实体对象条件不为空,而且里面的属性不能全部为空,避免出现全表删除的情况。

或者可以参考补充说明,添加防全表更新与删除插件的插件。

根据ID列表批量删除

这里有两个方法,对应的SQL语句不太一样。

removeByIds方法

直接调用API即可。

    @Override
    public boolean deleteByIds(List<String> userIds) {
        return this.removeByIds(userIds);
    }

对应的SQL语句如下。

 Consume Time:0 ms 2023-02-23 17:07:41
 Execute SQL:DELETE FROM user WHERE id IN ( '1' , '2' , '3' , '4' )

同样,尽量对ID列表分片,不能一次性删除太多数据。

   	@Override
	@Transactional(rollbackFor = Exception.class)
    public boolean deleteByIds(List<String> userIds) {
        // 按每次500条数据进行操作
        List<List<String>> list = ListUtil.partition(userIds, 1000);
        boolean res = true;
        for (List<String> i : list) {
            res = res && this.removeByIds(i);
        }
        return this.removeByIds(userIds);
    }

removeBatchByIds方法

直接调用API即可。

    @Override
    public boolean deleteBatchByIds(List<String> userIds) {
        // 每次最多进行1000条数据删除
        return this.removeBatchByIds(userIds, 1000);
    }

对应的SQL语句如下。

 Consume Time:1 ms 2023-02-23 17:11:09
 Execute SQL:DELETE FROM user WHERE id='1'
 
 Consume Time:0 ms 2023-02-23 17:11:09
 Execute SQL:DELETE FROM user WHERE id='2'
 
 Consume Time:0 ms 2023-02-23 17:11:09
 Execute SQL:DELETE FROM user WHERE id='3'
 
 Consume Time:0 ms 2023-02-23 17:11:09
 Execute SQL:DELETE FROM user WHERE id='4'

这个有点类似与saveBatch的操作,多次删除一次提交。

总结

这两种方法我进行了简单的测试。测试结果打印如下。

com.yww.demo.util.TestController         : 此处测试的两种方法,测试删除的数据数量为10600
com.yww.demo.util.TestController         : deleteByIds测试时间为1349毫秒
com.yww.demo.util.TestController         : deleteBatchByIds测试时间为12956毫秒

这两个差距还算很明显的了。测试中每次删除1000条数据,要是按更大的长度进行分片,我觉得这两种的方法差距会更加的大,所以尽量使用removeByIds这种方法。

根据其他条件批量删除

这里以用户名举例,即传入用户名列表,批量进行删除。

由于MybatisPlus没有对应的API,所以需要手动去编写Mapper。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteBatchByUsernames(List<String> usernames) {
        // 按每次1000条数据进行操作
        List<List<String>> list = ListUtil.partition(usernames, 1000);
        int count = 0;
        for (List<String> i : list) {
            count = count + baseMapper.deleteBatchByUsernames(i);
        }
        return count;
    }

deleteBatchByUsernames方法的mapper可以参考批量删除ID的方法进行编写,这里就模仿removeByIds对应的SQL进行编写。

    <delete id="deleteBatchByUsernames" parameterType="java.util.List">
        DELETE FROM user WHERE username in
        <foreach collection="usernames" index="index" item="username" separator="," open="(" close=")" >
            #{username}
        </foreach>
    </delete>

更新

根据ID更新数据

直接调用API即可。

    @Operation(summary = "根据ID更新数据")
    @PutMapping("/updateById")
    public Result<?> updateById(@RequestBody User user) {
        if (service.updateById(user)) {
            return Result.success("删除成功");
        } else {
            return Result.failure("删除失败");
        }
    }

根据ID批量更新数据

直接调用API即可。

    @Operation(summary = "根据ID批量更新数据")
    @PutMapping("/updateByIds")
    public Result<?> updateByIds(@RequestBody List<User> users) {
        if (service.updateBatchById(users, 1000)) {
            return Result.success("更新成功");
        } else {
            return Result.failure("更新失败");
        }
    }

使用UpdateWrapper更新

使用UpdateWrapper的条件去定位数据,更新条件需要设置sqlset。

以下是个简单的例子。

    @Override
    public boolean update1(User user) {
        // 将名字设置为WHERE条件,将状态设置为SET语句
        return this.update(
            Wrappers.lambdaUpdate(User.class).eq(User::getNickname, user.getNickname())
                    .set(User::getStatus, user.getStatus())
        );
    }

使用Wrapper更新

以下是个简单的例子。

    @Override
    public boolean update2(User user) {
        return this.update(user, 
                Wrappers.lambdaQuery(User.class).eq(User::getNickname, user.getNickname())
        );
    }

saveOrUpdate

这个方法是MybatisPlus里的一个方法,顾名思义,保存或者是更新,先去数据库根据ID查询是否存在该条数据,没有就插入数据,有的话就更新数据。大致用法和update或者是save差不多。

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

这方法确实也挺好用的,不过是更新和插入的混合方法,有点违和就是了,所以这里就不怎么探讨了。

查询

根据ID查询数据

    @Operation(summary = "根据ID获取数据")
    @GetMapping("/getById/{userId}")
    public Result<?> getById(@PathVariable Integer userId) {
        return Result.success(service.getById(userId));
    }

根据条件查询单条数据

    @Override
    public User getByUsername(String username) {
        return this.getOne(
                Wrappers.lambdaQuery(User.class).eq(User::getUsername, username)
        );
    }

这个方法只会返回一条数据,如果查询出多条数据会抛出异常,若是希望查出多条数据不抛出异常,可以自行设置。

getOne(queryWrapper, false)

根据条件查询数据

假设根据用户名和昵称进行查询。

    @Override
    public List<User> getByUser(User user) {
        // this.list() 查询所有数据
        return this.list(
                Wrappers.lambdaQuery(User.class).eq(User::getUsername, user.getUsername()).eq(User::getNickname, user.getNickname())
        );
    }

根据ID批量查询

    @Operation(summary = "根据ID批量查询")
    @GetMapping("/getByIds")
    public Result<?> getByUser(@RequestBody List<String> userIds) {
        return Result.success(service.listByIds(userIds));
    }

根据其他条件批量查询

这里需要自己手写SQL实现才行了,SQL语句和根据其他条件批量删除的差不多,也是通过IN关键字拼接实现。

    /**
     * 批量根据用户名批量删除
     *
     * @param usernames 用户名列表
     * @return 查询到的用户列表
     */
    List<User> listByUserNames(@Param("usernames") List<String> usernames);
    <select id="listByUserNames" resultType="com.yww.demo.entity.User">
        SELECT * FROM user WHERE username IN
        <foreach collection="usernames" index="index" item="username" separator="," open="(" close=")" >
            #{username}
        </foreach>
    </select>

简单分页查询

MybatisPlus提供了一个分页插件,需要先去配置,配置参考上述的补充说明。

MybatisPlus的分页方法需要IPage这分页模型,可以自己去实现这个分页模型,也可以使用官方提供的Page类(Page继承了IPage,实现了一个简单的分页模型)。Page的对象创建可以直接使用其Page.of方法。

@Operation(summary = "简单分页查询")
@GetMapping("/page/{current}/{size}")
public Result<?> page(@PathVariable Integer current,
                      @PathVariable Integer size) {
    Page<User> ipage = Page.of(current, size);
    return Result.success(service.page(ipage));
}

注意,MybatisPlus的分页方法是会先查询所有数据的数量,然后才会进行LIMIT分页,也就是说会有两条SQL语句,从打印的SQL就可以看到。

Consume Time:19 ms Execute SQL:SELECT COUNT(*) AS total FROM user

Consume Time:3 ms Execute SQL:SELECT id,username,password,nickname,avatar,email,sex,phone,status,create_time,create_by,update_time,update_by FROM user LIMIT 10

如果用不到总记录数,不想执行count查询,可以通过Page.of(current, size, false)方法去关掉。

根据条件分页查询

根据条件分页查询,其实只需要添加一个条件构造器即可。

这里拿性别举例,分页查询性别为男的数据。

    @Operation(summary = "根据条件分页查询(查询性别)")
    @GetMapping("/page/{current}/{size}/{sex}")
    public Result<?> page(@PathVariable Integer current,
                          @PathVariable Integer size,
                          @PathVariable Boolean sex) {
        Page<User> ipage = Page.of(current, size);
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.builder().sex(sex).build());
        return Result.success(service.page(ipage, wrapper));
    }

篇末

这篇文章主要是介绍一些MybatisPlus的API吧,其实刚刚开始写的时候,是想记录一些常用的用法的,但是后面写着写着,发现其实都是在写官方的API如何使用(确实这个项目考虑的很周全)。后来又怕别人看得更明白些,又写了很多介绍类和使用的注意事项。emm,怎么说的,是写的有些乱了,本来就是给自己看到,写写感觉就偏题了hh,不过还可以吧,将就看一下。