简介

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

测试环境

表SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 = '用户信息实体类';

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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。

1
2
3
4
5
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>${p6spy.version}</version>
</dependency>

配置文件

1
2
3
4
5
6
# 数据库配置(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这个库来生成假数据。

1
2
3
4
5
<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
<version>1.8.0</version>
</dependency>

随机生成600条数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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

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

LambdaQueryWrapper

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

  1. 使用QueryWrapperlambda方法获取

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

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

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

防全表更新与删除插件

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

官方插件说明

1
2
3
4
5
6
7
8
9
10
/**
* 插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 防全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}

分页插件

分页时需要用到的插件。

官方插件说明

1
2
3
4
5
6
7
8
9
10
/**
* 插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件,指定数据库为MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

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

添加

添加单条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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模拟生成用户列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 随机生成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循环添加

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

1
2
3
4
5
6
7
8
9
10
   @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的打印,可以看到执行的其中一条插入语句。

1
2
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的事务管理,不然可能会出现警告。(虽然可能不会影响操作)

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

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

1
2
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被称为伪批量插入。

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

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

批量插入

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * <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配置中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    @EnableTransactionManagement
    @MapperScan("com.yww.demo.mapper")
    public class MybatisPlusConfig {

    /**
    * 自定义批量插入 SQL 注入器
    */
    @Bean
    public InsertBatchSqlInjector insertBatchSqlInjector() {
    return new InsertBatchSqlInjector();
    }

    }
  3. 在Mapper类中添加方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @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也是可以的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 批量新增数据,自选字段 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
*/

具体的使用。

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

SQL打印为

1
2
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方法我找了一下源码,好像没见到自带的分片方法,所以需要手动分片然后进行插入。操作如下。

1
2
3
4
5
6
7
8
9
10
11
12
   @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即可。

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

根据其他条件删除

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

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

DELETE FROM user

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

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

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

根据ID列表批量删除

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

removeByIds方法

直接调用API即可。

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

对应的SQL语句如下。

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

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

1
2
3
4
5
6
7
8
9
10
11
  	@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即可。

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

对应的SQL语句如下。

1
2
3
4
5
6
7
8
9
10
11
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的操作,多次删除一次提交。

总结

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

1
2
3
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。

1
2
3
4
5
6
7
8
9
10
11
@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进行编写。

1
2
3
4
5
6
<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即可。

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

根据ID批量更新数据

直接调用API即可。

1
2
3
4
5
6
7
8
9
@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。

以下是个简单的例子。

1
2
3
4
5
6
7
8
@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更新

以下是个简单的例子。

1
2
3
4
5
6
@Override
public boolean update2(User user) {
return this.update(user,
Wrappers.lambdaQuery(User.class).eq(User::getNickname, user.getNickname())
);
}

saveOrUpdate

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

1
2
3
4
5
6
7
8
// 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查询数据

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

根据条件查询单条数据

1
2
3
4
5
6
@Override
public User getByUsername(String username) {
return this.getOne(
Wrappers.lambdaQuery(User.class).eq(User::getUsername, username)
);
}

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

getOne(queryWrapper, false)

根据条件查询数据

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

1
2
3
4
5
6
7
@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批量查询

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

根据其他条件批量查询

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

1
2
3
4
5
6
7
/**
* 批量根据用户名批量删除
*
* @param usernames 用户名列表
* @return 查询到的用户列表
*/
List<User> listByUserNames(@Param("usernames") List<String> usernames);
1
2
3
4
5
6
<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方法。

1
2
3
4
5
6
7
@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)方法去关掉。

根据条件分页查询

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

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

1
2
3
4
5
6
7
8
9
@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,不过还可以吧,将就看一下。