mybatis-plus入门
MyBatisPlus
今日目标
基于 MyBatisPlus 完成标准 Dao 的增删改查功能
掌握 MyBatisPlus 中的分页及条件查询构建
掌握主键 ID 的生成策略
了解 MyBatisPlus 的代码生成器
1,MyBatisPlus 入门案例与简介
这一节我们来学习下 MyBatisPlus 的入门案例与简介,这个和其他课程都不太一样,其他的课程都是先介绍概念,然后再写入门案例。而对于 MyBatisPlus 的学习,我们将顺序做了调整,主要的原因 MyBatisPlus 主要是对 MyBatis 的简化,所有我们先体会下它简化在哪,然后再学习它是什么,以及它帮我们都做哪些事。
1.1 入门案例
MybatisPlus(简称 MP)是基于 MyBatis 框架基础上开发的增强型工具,旨在简化开发、提供效率。
开发方式
- 基于 MyBatis 使用 MyBatisPlus
- 基于 Spring 使用 MyBatisPlus
- ==基于 SpringBoot 使用 MyBatisPlus==
SpringBoot 刚刚我们学习完成,它能快速构建 Spring 开发环境用以整合其他技术,使用起来是非常简单,对于 MP 的学习,我们也基于 SpringBoot 来构建学习。
学习之前,我们先来回顾下,SpringBoot 整合 Mybatis 的开发过程:
创建 SpringBoot 工程
勾选配置使用的技术,能够实现自动添加起步依赖包
设置 dataSource 相关属性(JDBC 参数)
定义数据层接口映射配置
我们可以参考着上面的这个实现步骤把 SpringBoot 整合 MyBatisPlus 来快速实现下,具体的实现步骤为:
步骤 1:创建数据库及表
1 | create database if not exists mybatisplus_db character set utf8; |
步骤 2:创建 SpringBoot 工程
步骤 3:勾选配置使用技术
说明:
- 由于 MP 并未被收录到 idea 的系统内置配置,无法直接选择加入,需要手动在 pom.xml 中配置添加
步骤 4:pom.xml 补全依赖
1 | <dependency> |
说明:
druid 数据源可以加也可以不加,SpringBoot 有内置的数据源,可以配置成使用 Druid 数据源
从 MP 的依赖关系可以看出,通过依赖传递已经将 MyBatis 与 MyBatis 整合 Spring 的 jar 包导入,我们不需要额外在添加 MyBatis 的相关 jar 包
步骤 5:添加 MP 的相关配置信息
resources 默认生成的是 properties 配置文件,可以将其替换成 yml 文件,并在文件中配置数据库连接的相关信息:application.yml
1 | spring: |
说明:==serverTimezone 是用来设置时区,UTC 是标准时区,和咱们的时间差 8 小时,所以可以将其修改为Asia/Shanghai
==
步骤 6:根据数据库表创建实体类
1 | public class User { |
步骤 7:创建 Dao 接口
1 |
|
步骤 8:编写引导类
1 |
|
说明:Dao 接口要想被容器扫描到,有两种解决方案:
- 方案一:在 Dao 接口上添加
@Mapper
注解,并且确保 Dao 处在引导类所在包或其子包中- 该方案的缺点是需要在每一 Dao 接口中添加注解
- 方案二:在引导类上添加
@MapperScan
注解,其属性为所要扫描的 Dao 所在包- 该方案的好处是只需要写一次,则指定包下的所有 Dao 接口都能被扫描到,
@Mapper
就可以不写。
- 该方案的好处是只需要写一次,则指定包下的所有 Dao 接口都能被扫描到,
步骤 9:编写测试类
1 |
|
说明:
userDao 注入的时候下面有红线提示的原因是什么?
UserDao 是一个接口,不能实例化对象
只有在服务器启动 IOC 容器初始化后,由框架创建 DAO 接口的代理对象来注入
- 现在服务器并未启动,所以代理对象也未创建,IDEA 查找不到对应的对象注入,所以提示报红
- 一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。
查看运行结果:
跟之前整合 MyBatis 相比,你会发现我们不需要在 DAO 接口中编写方法和 SQL 语句了,只需要继承BaseMapper
接口即可。整体来说简化很多。
1.2 MybatisPlus 简介
MyBatisPlus(简称 MP)是基于 MyBatis 框架基础上开发的增强型工具,旨在==简化开发、提高效率==
通过刚才的案例,相信大家能够体会简化开发和提高效率这两个方面的优点。
MyBatisPlus 的官网为:https://mp.baomidou.com/
说明:
现在的页面中,这一行已经被删除,现在再去访问https://mybatis.plus
会发现访问不到,这个就有很多可能性供我们猜想了,所以大家使用 baomidou 的网址进行访问即可。
官方文档中有一张很多小伙伴比较熟悉的图片:
从这张图中我们可以看出 MP 旨在成为 MyBatis 的最好搭档,而不是替换 MyBatis,所以可以理解为 MP 是 MyBatis 的一套增强工具,它是在 MyBatis 的基础上进行开发的,我们虽然使用 MP 但是底层依然是 MyBatis 的东西,也就是说我们也可以在 MP 中写 MyBatis 的内容。
对于 MP 的学习,大家可以参考着官方文档来进行学习,里面都有详细的代码案例。
MP 的特性:
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表 CRUD 操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
- ……
2,标准数据层开发
在这一节中我们重点学习的是数据层标准的 CRUD(增删改查)的实现与分页功能。代码比较多,我们一个个来学习。
2.1 标准 CRUD 使用
对于标准的 CRUD 功能都有哪些以及 MP 都提供了哪些方法可以使用呢?
我们先来看张图:
对于这张图的方法,我们挨个来演示下:
首先说下,案例中的环境就是咱们入门案例的内容,第一个先来完成新增
功能
2.2 新增
在进行新增之前,我们可以分析下新增的方法:
1 | int insert (T t) |
T:泛型,新增用来保存新增数据
int:返回值,新增成功后返回 1,没有新增成功返回的是 0
在测试类中进行新增操作:
1 |
|
执行测试后,数据库表中就会添加一条数据。
但是数据中的主键 ID,有点长,那这个主键 ID 是如何来的?我们更想要的是主键自增,应该是 5 才对,这个是我们后面要学习的主键 ID 生成策略,这块的这个问题,我们暂时先放放。
2.3 删除
在进行删除之前,我们可以分析下删除的方法:
1 | int deleteById (Serializable id) |
Serializable:参数类型
思考:参数类型为什么是一个序列化类?
从这张图可以看出,
- String 和 Number 是 Serializable 的子类,
- Number 又是 Float,Double,Integer 等类的父类,
- 能作为主键的数据类型都已经是 Serializable 的子类,
- MP 使用 Serializable 作为参数类型,就好比我们可以用 Object 接收任何数据类型一样。
int:返回值类型,数据删除成功返回 1,未删除数据返回 0。
在测试类中进行新增操作:
1 |
|
2.4 修改
在进行修改之前,我们可以分析下修改的方法:
1 | int updateById(T t); |
T:泛型,需要修改的数据内容,注意因为是根据 ID 进行修改,所以传入的对象中需要有 ID 属性值
int:返回值,修改成功后返回 1,未修改数据返回 0
在测试类中进行新增操作:
1 |
|
说明:修改的时候,只修改实体对象中有值的字段。
2.5 根据 ID 查询
在进行根据 ID 查询之前,我们可以分析下根据 ID 查询的方法:
1 | T selectById (Serializable id) |
- Serializable:参数类型,主键 ID 的值
- T:根据 ID 查询只会返回一条数据
在测试类中进行新增操作:
1 |
|
2.6 查询所有
在进行查询所有之前,我们可以分析下查询所有的方法:
1 | List<T> selectList(Wrapper<T> queryWrapper) |
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为 Null
- List
:因为查询的是所有,所以返回的数据是一个集合
在测试类中进行新增操作:
1 |
|
我们所调用的方法都是来自于 DAO 接口继承的 BaseMapper 类中。里面的方法有很多,我们后面会慢慢去学习里面的内容。
2.7 Lombok
代码写到这,我们会发现 DAO 接口类的编写现在变成最简单的了,里面什么都不用写。反过来看看模型类的编写都需要哪些内容:
- 私有属性
- setter…getter…方法
- toString 方法
- 构造函数
虽然这些内容不难,同时也都是通过 IDEA 工具生成的,但是过程还是必须得走一遍,那么对于模型类的编写有没有什么优化方法?就是我们接下来要学习的 Lombok。
概念
- Lombok,一个 Java 类库,提供了一组注解,简化 POJO 实体类开发。
使用步骤
步骤 1:添加 lombok 依赖
1 | <dependency> |
注意:版本可以不用写,因为 SpringBoot 中已经管理了 lombok 的版本。
步骤 2:安装 Lombok 的插件
==新版本 IDEA 已经内置了该插件,如果删除 setter 和 getter 方法程序有报红,则需要安装插件==
如果在 IDEA 中找不到 lombok 插件,可以访问如下网站
https://plugins.jetbrains.com/plugin/6317-lombok/versions
根据自己 IDEA 的版本下载对应的 lombok 插件,下载成功后,在 IDEA 中采用离线安装的方式进行安装。
步骤 3:模型类上添加注解
Lombok 常见的注解有:
- @Setter:为模型类的属性提供 setter 方法
- @Getter:为模型类的属性提供 getter 方法
- @ToString:为模型类的属性提供 toString 方法
- @EqualsAndHashCode:为模型类的属性提供 equals 和 hashcode 方法
- ==@Data:是个组合注解,包含上面的注解的功能==
- ==@NoArgsConstructor:提供一个无参构造函数==
- ==@AllArgsConstructor:提供一个包含所有参数的构造函数==
Lombok 的注解还有很多,上面标红的三个是比较常用的,其他的大家后期用到了,再去补充学习。
1 |
|
说明:
Lombok 只是简化模型类的编写,我们之前的方法也能用,比如有人会问:我如果只想要有 name 和 password 的构造函数,该如何编写?
1 |
|
这种方式是被允许的。
2.8 分页功能
基础的增删改查就已经学习完了,刚才我们在分析基础开发的时候,有一个分页功能还没有实现,在 MP 中如何实现分页功能,就是咱们接下来要学习的内容。
分页查询使用的方法是:
1 | IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper) |
- IPage:用来构建分页查询条件
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为 Null
- IPage:返回值,你会发现构建分页条件和方法的返回值都是 IPage
IPage 是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到 IPage 类中按 ctrl+h,会找到其有一个实现类为Page
。
步骤 1:调用方法传入参数获取返回值
1 |
|
步骤 2:设置分页拦截器
这个拦截器 MP 已经为我们提供好了,我们只需要将其配置成 Spring 管理的 bean 对象即可。
1 |
|
说明:上面的代码记不住咋办呢?
这些内容在 MP 的官方文档中有详细的说明,我们可以查看官方文档类配置
步骤 3:运行测试程序
如果想查看 MP 执行的 SQL 语句,可以修改 application.yml 配置文件,
1 | mybatis-plus: |
打开日志后,就可以在控制台打印出对应的 SQL 语句,开启日志功能性能就会受到影响,调试完后记得关闭。
3,DQL 编程控制
增删改查四个操作中,查询是非常重要的也是非常复杂的操作,这块需要我们重点学习下,这节我们主要学习的内容有:
- 条件查询方式
- 查询投影
- 查询条件设定
- 字段映射与表名映射
3.1 条件查询
3.1.1 条件查询的类
- MyBatisPlus 将书写复杂的 SQL 查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper
类,这个类就是用来构建查询条件的,如下图所示:
那么条件查询如何使用 Wrapper 来构建呢?
3.1.2 环境构建
在构建条件查询之前,我们先来准备下环境
创建一个 SpringBoot 项目
pom.xml 中添加对应的依赖
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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_02_dql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>编写 UserDao 接口
1
2
3
public interface UserDao extends BaseMapper<User> {
}编写模型类
1
2
3
4
5
6
7
8
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}编写引导类
1
2
3
4
5
6
7
8
public class Mybatisplus02DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus02DqlApplication.class, args);
}
}编写配置文件
1
2
3
4
5
6
7
8
9
10
11
12# dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
# mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl编写测试类
1
2
3
4
5
6
7
8
9
10
11
12
class Mybatisplus02DqlApplicationTests {
private UserDao userDao;
void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}最终创建的项目结构为:
测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:
取消初始化 spring 日志打印,resources 目录下添加 logback.xml,名称固定,内容如下:
1
2
3
<configuration>
</configuration>说明:logback.xml 的配置内容,不是我们学习的重点,如果有兴趣可以自行百度查询。
取消 MybatisPlus 启动 banner 图标
application.yml 添加如下内容:
1
2
3
4
5
6# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标取消 SpringBoot 的 log 打印
application.yml 添加如下内容:
1
2
3spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
解决控制台打印日志过多的相关操作可以不用去做,一般会被用来方便我们查看程序运行的结果。
3.1.3 构建条件查询
在进行查询的时候,我们的入口是在 Wrapper 这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
- 先来看第一种:==QueryWrapper==
1 |
|
lt: 小于(<) ,最终的 sql 语句为
1
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如 age 写错,就会导致查询不成功
- 接着来看第二种:==QueryWrapper 的基础上使用 lambda==
1 |
|
- User::getAget,为 lambda 表达式中的,类名::方法名,最终的 sql 语句为:
1 | SELECT id,name,password,age,tel FROM user WHERE (age < ?) |
注意:构建 LambdaQueryWrapper 的时候泛型不能省。
此时我们再次编写条件的时候,就不会存在写错名称的情况,但是 qw 后面多了一层 lambda()调用
- 接着来看第三种:==LambdaQueryWrapper==
1 |
|
这种方式就解决了上一种方式所存在的问题。
3.1.4 多条件构建
学完了三种构建查询对象的方式,每一种都有自己的特点,所以用哪一种都行,刚才都是一个条件,那如果有多个条件该如何构建呢?
需求:查询数据库表中,年龄在 10 岁到 30 岁之间的用户信息
1 |
|
gt:大于(>),最终的 SQL 语句为
1
SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
构建多条件的时候,可以支持链式编程
1
2
3
4LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
需求:查询数据库表中,年龄小于 10 或年龄大于 30 的数据
1 |
|
or()就相当于我们 sql 语句中的
or
关键字,不加默认是and
,最终的 sql 语句为:1
SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
3.1.5 null 判定
先来看一张图,
- 我们在做条件查询的时候,一般会有很多条件可以供用户进行选择查询。
- 这些条件用户可以选择使用也可以选择不使用,比如我要查询价格在 8000 以上的手机
- 在输入条件的时候,价格有一个区间范围,按照需求只需要在第一个价格输入框中输入 8000
- 后台在做价格查询的时候,一般会让 price>值 1 and price <值 2
- 因为前端没有输入值 2,所以如果不处理的话,就会出现 price>8000 and price < null 问题
- 这个时候查询的结果就会出问题,具体该如何解决?
需求:查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
思考第一个问题:后台如果想接收前端的两个数据,该如何接收?
我们可以使用两个简单数据类型,也可以使用一个模型类,但是 User 类中目前只有一个 age 属性,如:
1 |
|
使用一个 age 属性,如何去接收页面上的两个值呢?这个时候我们有两个解决方案
方案一:添加属性 age2,这种做法可以但是会影响到原模型类的属性内容
1 |
|
方案二:新建一个模型类,让其继承 User 类,并在其中添加 age2 属性,UserQuery 在拥有 User 属性后同时添加了 age2 属性。
1 |
|
环境准备好后,我们来实现下刚才的需求:
1 |
|
上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,代码量就比较大,来看 MP 给我们提供的简化方式:
1 |
|
lt()方法
condition 为 boolean 类型,返回 true,则添加条件,返回 false 则不添加条件
3.2 查询投影
3.2.1 查询指定字段
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。
具体如何来实现?
1 |
|
select(…)方法用来设置查询的字段列,可以设置多个,最终的 sql 语句为:
1
SELECT id,name,age FROM user
如果使用的不是 lambda,就需要手动指定字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Mybatisplus02DqlApplicationTests {
private UserDao userDao;
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("id","name","age","tel");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}- 最终的 sql 语句为:SELECT id,name,age,tel FROM user
3.2.2 聚合查询
需求:聚合函数查询,完成 count、max、min、avg、sum 的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
1 |
|
为了在做结果封装的时候能够更简单,我们将上面的聚合函数都起了个名称,方面后期来获取这些数据
3.2.3 分组查询
需求:分组查询,完成 group by 的查询使用
1 |
|
groupBy 为分组,最终的 sql 语句为
1
SELECT count(*) as count,tel FROM user GROUP BY tel
注意:
- 聚合与分组查询,无法使用 lambda 表达式来完成
- MP 只是对 MyBatis 的增强,如果 MP 实现不了,我们可以直接在 DAO 接口中使用 MyBatis 的方式实现
3.3 查询条件
前面我们只使用了 lt()和 gt(),除了这两个方法外,MP 还封装了很多条件对应的方法,这一节我们重点把 MP 提供的查询条件方法进行学习下。
MP 的查询条件有很多:
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
- ……
3.3.1 等值查询
需求:根据用户名和密码查询用户信息
1 |
|
eq(): 相当于
=
,对应的 sql 语句为1
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
3.3.2 范围查询
需求:对年龄进行范围查询,使用 lt()、le()、gt()、ge()、between()进行范围查询
1 |
|
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
3.3.3 模糊查询
需求:查询表中 name 属性的值以
J
开头的用户信息,使用 like 进行模糊查询
1 |
|
- like():前后加百分号,如 %J%
- likeLeft():前面加百分号,如 %J
- likeRight():后面加百分号,如 J%
3.3.4 排序查询
需求:查询所有数据,然后按照 id 降序
1 |
|
除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:
- orderBy 排序
- condition:条件,true 则添加排序,false 则不添加排序
- isAsc:是否为升序,true 升序,false 降序
- columns:排序字段,可以有多个
- orderByAsc/Desc(单个 column):按照指定字段进行升序/降序
- orderByAsc/Desc(多个 column):按照多个字段进行升序/降序
- orderByAsc/Desc
- condition:条件,true 添加排序,false 不添加排序
- 多个 columns:按照多个字段进行排序
除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如 isNull,isNotNull,in,notIn 等等方法可供选择,具体参考官方文档的条件构造器来学习使用,具体的网址为:
https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
3.4 映射匹配兼容性
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
那么问题就来了:
问题 1:表字段与编码属性设计不同步
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP 给我们提供了一个注解@TableField
,使用该注解可以实现模型类属性名和表的列名之间的映射关系
问题 2:编码中添加了数据库中未定义的属性
当模型类中多了一个数据库表不存在的字段,就会导致生成的 sql 语句中在 select 的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:
==Unknown column ‘多出来的字段名称’ in ‘field list’==
具体的解决方案用到的还是@TableField
注解,它有一个属性叫exist
,设置该字段是否在数据库表中存在,如果设置为 false 则不存在,生成 sql 语句查询的时候,就不会再查询该字段了。
问题 3:采用默认查询开放了更多的字段查看权限
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false 表示默认不查询该字段。
知识点 1:@TableField
名称 | @TableField |
---|---|
类型 | ==属性注解== |
位置 | 模型类属性定义上方 |
作用 | 设置当前属性对应的数据库表中的字段关系 |
相关属性 | value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为 true,此属性不能与 value 合并使用 select:设置属性是否参与查询,此属性与 select()映射配置不冲突 |
问题 4:表名与编码开发设计不同步
该问题主要是表的名称和模型类的名称不一致,导致查询失败,这个时候通常会报如下错误信息:
==Table ‘databaseName.tableNaem’ doesn’t exist==,翻译过来就是数据库中的表不存在。
解决方案是使用 MP 提供的另外一个注解@TableName
来设置表与模型类之间的对应关系。
知识点 2:@TableName
名称 | @TableName |
---|---|
类型 | ==类注解== |
位置 | 模型类定义上方 |
作用 | 设置当前类对应于数据库表关系 |
相关属性 | value(默认):设置数据库表名称 |
代码演示
接下来我们使用案例的方式把刚才的知识演示下:
步骤 1:修改数据库表 user 为 tbl_user
直接查询会报错,原因是 MP 默认情况下会使用模型类的类名首字母小写当表名使用。
步骤 2:模型类添加@TableName 注解
1 |
|
步骤 3:将字段 password 修改成 pwd
直接查询会报错,原因是 MP 默认情况下会使用模型类的属性名当做表的列名使用
步骤 4:使用@TableField 映射关系
1 |
|
步骤 5:添加一个数据库表不存在的字段
1 |
|
直接查询会报错,原因是 MP 默认情况下会查询模型类的所有属性对应的数据库表的列,而 online 不存在
步骤 6:使用@TableField 排除字段
1 |
|
步骤 7:查询时将 pwd 隐藏
1 |
|
4,DML 编程控制
查询相关的操作我们已经介绍完了,紧接着我们需要对另外三个,增删改进行内容的讲解。挨个来说明下,首先是新增(insert)中的内容。
4.1 id 生成策略控制
前面我们在新增的时候留了一个问题,就是新增成功后,主键 ID 是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下 ID 该如何选择:
- 不同的表应用不同的 id 生成策略
- 日志:自增(1,2,3,4,……)
- 购物订单:特殊规则(FQ23948AK3843)
- 外卖单:关联地区日期等信息(10 04 20200314 34 91)
- 关系表:可省略 id
- ……
不同的业务采用的 ID 生成方式应该是不一样的,那么在 MP 中都提供了哪些主键生成策略,以及我们该如何进行选择?
在这里我们又需要用到 MP 的一个注解叫@TableId
知识点 1:@TableId
名称 | @TableId |
---|---|
类型 | ==属性注解== |
位置 | 模型类中用于表示主键的属性定义上方 |
作用 | 设置当前类中主键属性的生成策略 |
相关属性 | value(默认):设置数据库表主键名称 type:设置主键属性的生成策略,值查照 IdType 的枚举值 |
4.1.1 环境构建
在构建条件查询之前,我们先来准备下环境
创建一个 SpringBoot 项目
pom.xml 中添加对应的依赖
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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_03_dml</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>编写 UserDao 接口
1
2
3
public interface UserDao extends BaseMapper<User> {
}编写模型类
1
2
3
4
5
6
7
8
9
10
11
12
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer online;
}编写引导类
1
2
3
4
5
6
7
8
public class Mybatisplus03DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus03DqlApplication.class, args);
}
}编写配置文件
1
2
3
4
5
6
7
8
9
10
11
12# dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
# mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl编写测试类
1
2
3
4
5
6
7
8
9
10
11
12
class Mybatisplus02DqlApplicationTests {
private UserDao userDao;
void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}测试
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
class Mybatisplus03DqlApplicationTests {
private UserDao userDao;
void testSave(){
User user = new User();
user.setName("黑马程序员");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
void testDelete(){
userDao.deleteById(1401856123925713409L)
}
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);
}
}最终创建的项目结构为:
4.1.2 代码演示
AUTO 策略
步骤 1:设置生成策略为 AUTO
1 |
|
步骤 2:删除测试数据并修改自增值
删除测试数据
因为之前生成主键 ID 的值比较长,会把 MySQL 的自动增长的值变的很大,所以需要将其调整为目前最新的 id 值。
步骤 3:运行新增方法
会发现,新增成功,并且主键 id 也是从 5 开始
经过这三步的演示,会发现AUTO
的作用是==使用数据库 ID 自增==,在使用该策略的时候一定要确保对应的数据库表设置了 ID 主键自增,否则无效。
接下来,我们可以进入源码查看下 ID 的生成策略有哪些?
打开源码后,你会发现并没有看到中文注释,这就需要我们点击右上角的Download Sources
,会自动帮你把这个类的 java 文件下载下来,我们就能看到具体的注释内容。因为这个技术是国人制作的,所以他代码中的注释还是比较容易看懂的。
当把源码下载完后,就可以看到如下内容:
从源码中可以看到,除了 AUTO 这个策略以外,还有如下几种生成策略:
- NONE: 不设置 id 生成策略
- INPUT:用户手工输入 id
- ASSIGN_ID:雪花算法生成 id(可兼容数值型与字符串型)
- ASSIGN_UUID:以 UUID 生成算法作为 id 生成策略
- 其他的几个策略均已过时,都将被 ASSIGN_ID 和 ASSIGN_UUID 代替掉。
拓展:
分布式 ID 是什么?
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
- 比如订单表就有可能被存储在不同的服务器上
- 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
- 这个时候就需要一个全局唯一 ID,这个 ID 就是分布式 ID。
INPUT 策略
步骤 1:设置生成策略为 INPUT
1 |
|
注意:这种 ID 生成策略,需要将表的自增策略删除掉
步骤 2:添加数据手动设置 ID
1 |
|
步骤 3:运行新增方法
如果没有设置主键 ID 的值,则会报错,错误提示就是主键 ID 没有给值:
如果设置了主键 ID,则数据添加成功,如下:
ASSIGN_ID 策略
步骤 1:设置生成策略为 ASSIGN_ID
1 |
|
步骤 2:添加数据不设置 ID
1 |
|
注意:这种生成策略,不需要手动设置 ID,如果手动设置 ID,则会使用自己设置的值。
步骤 3:运行新增方法
生成的 ID 就是一个 Long 类型的数据。
ASSIGN_UUID 策略
步骤 1:设置生成策略为 ASSIGN_UUID
使用 uuid 需要注意的是,主键的类型不能是 Long,而应该改成 String 类型
1 |
|
步骤 2:修改表的主键类型
主键类型设置为 varchar,长度要大于 32,因为 UUID 生成的主键为 32 位,如果长度小的话就会导致插入失败。
步骤 3:添加数据不设置 ID
1 |
|
步骤 4:运行新增方法
接下来我们来聊一聊雪花算法:
雪花算法(SnowFlake),是 Twitter 官方给出的算法实现 是用 Scala 写的。其生成的结果是一个 64bit 大小整数,它的结构如下图:
- 1bit,不用,因为二进制中最高位是符号位,1 表示负数,0 表示正数。生成的 id 一般都是用整数,所以最高位固定为 0。
- 41bit-时间戳,用来记录时间戳,毫秒级
- 10bit-工作机器 id,用来记录工作机器 id,其中高位 5bit 是数据中心 ID 其取值范围 0-31,低位 5bit 是工作节点 ID 其取值范围 0-31,两个组合起来最多可以容纳 1024 个节点
- 序列号占用 12bit,每个节点每毫秒 0 开始不断累加,最多可以累加到 4095,一共可以产生 4096 个 ID
4.1.3 ID 生成策略对比
介绍了这些主键 ID 的生成策略,我们以后该用哪个呢?
- NONE: 不设置 id 生成策略,MP 不自动生成,约等于 INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的 ID 造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂
- AUTO:数据库 ID 自增,这种策略适合在数据库服务器只有 1 台的情况下使用,不可作为分布式 ID 使用
- ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是 32 位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
- ASSIGN_ID:可以在分布式的情况下使用,生成的是 Long 类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
- 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。
4.1.4 简化配置
前面我们已经完成了表关系映射、数据库主键策略的设置,接下来对于这两个内容的使用,我们再讲下他们的简化配置:
模型类主键策略设置
对于主键 ID 的策略已经介绍完,但是如果要在项目中的每一个模型类上都需要使用相同的生成策略,如:
确实是稍微有点繁琐,我们能不能在某一处进行配置,就能让所有的模型类都可以使用该主键 ID 策略呢?
答案是肯定有,我们只需要在配置文件中添加如下内容:
1 | mybatis-plus: |
配置完成后,每个模型类的主键 ID 策略都将成为 assign_id.
数据库表与模型类的映射关系
MP 会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以tbl_
开头,那么我们就需要将所有的模型类上添加@TableName
,如:
配置起来还是比较繁琐,简化方式为在配置文件中配置如下内容:
1 | mybatis-plus: |
设置表的前缀内容,这样 MP 就会拿 tbl_
加上模型类的首字母小写,就刚好组装成数据库的表名。
4.2 多记录操作
先来看下问题:
之前添加了很多商品到购物车,过了几天发现这些东西又不想要了,该怎么办呢?
很简单删除掉,但是一个个删除的话还是比较慢和费事的,所以一般会给用户一个批量操作,也就是前面有一个复选框,用户一次可以勾选多个也可以进行全选,然后删一次就可以将购物车清空,这个就需要用到批量删除
的操作了。
具体该如何实现多条删除,我们找找对应的 API 方法
1 | int deleteBatchIds(; Collection<? extends Serializable> idList) |
翻译方法的字面意思为:删除(根据 ID 批量删除),参数是一个集合,可以存放多个 id 值。
需求:根据传入的 id 集合将数据库表中的数据删除掉。
1 |
|
执行成功后,数据库表中的数据就会按照指定的 id 进行删除。
除了按照 id 集合进行批量删除,也可以按照 id 集合进行批量查询,还是先来看下 API
1 | List<T> selectBatchIds(; Collection<? extends Serializable> idList) |
方法名称翻译为:查询(根据 ID 批量查询),参数是一个集合,可以存放多个 id 值。
需求:根据传入的 ID 集合查询用户信息
1 |
|
查询结果就会按照指定传入的 id 值进行查询
4.3 逻辑删除
接下来要讲解是删除中比较重要的一个操作,逻辑删除,先来分析下问题:
这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同)的表
员工 ID 为 1 的张业绩,总共签了三个合同,如果此时他离职了,我们需要将员工表中的数据进行删除,会执行 delete 操作
如果表在设计的时候有主外键关系,那么同时也得将合同表中的前三条数据也删除掉
后期要统计所签合同的总金额,就会发现对不上,原因是已经将员工 1 签的合同信息删除掉了
如果只删除员工不删除合同表数据,那么合同的员工编号对应的员工信息不存在,那么就会出现垃圾数据,就会出现无主合同,根本不知道有张业绩这个人的存在
所以经过分析,我们不应该将表中的数据删除掉,而是需要进行保留,但是又得把离职的人和在职的人进行区分,这样就解决了上述问题,如:
区分的方式,就是在员工表中添加一列数据
deleted
,如果为 0 说明在职员工,如果离职则将其改完 1,(0 和 1 所代表的含义是可以自定义的)
所以对于删除操作业务问题来说有:
- 物理删除:业务数据从数据库中丢弃,执行的是 delete 操作
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是 update 操作
MP 中逻辑删除具体该如何实现?
步骤 1:修改数据库表添加deleted
列
字段名可以任意,内容也可以自定义,比如0
代表正常,1
代表删除,可以在添加列的同时设置其默认值为0
正常。
步骤 2:实体类添加属性
(1)添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用@TableField 进行关系映射,如果一致,则会自动对应。
(2)标识新增的字段为逻辑删除字段,使用@TableLogic
1 |
|
步骤 3:运行删除方法
1 |
|
从测试结果来看,逻辑删除最后走的是 update 操作,会将指定的字段修改成删除状态对应的值。
思考
逻辑删除,对查询有没有影响呢?
执行查询操作
1
2
3
4
5
6
7
8
9
10
11
class Mybatisplus03DqlApplicationTests {
private UserDao userDao;
void testFind(){
System.out.println(userDao.selectList(null));
}
}运行测试,会发现打印出来的 sql 语句中会多一个查询条件,如:
可想而知,MP 的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。
如果还是想把已经删除的数据都查询出来该如何实现呢?
1
2
3
4
5
6
public interface UserDao extends BaseMapper<User> {
//查询所有数据包含已经被删除的数据
public List<User> selectAll();
}如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加
@TableLogic
注解,如何优化?在配置文件中添加全局配置,如下:
1
2
3
4
5
6
7
8
9mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
介绍完逻辑删除,逻辑删除的本质为:
逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
执行的 SQL 语句为:
UPDATE tbl_user SET ==deleted===1 where id = ? AND ==deleted===0
执行数据结果为:
知识点 1:@TableLogic
名称 | @TableLogic |
---|---|
类型 | ==属性注解== |
位置 | 模型类中用于表示删除字段的属性定义上方 |
作用 | 标识该字段为进行逻辑删除的字段 |
相关属性 | value:逻辑未删除值 delval:逻辑删除值 |
4.4 乐观锁
4.4.1 概念
在讲解乐观锁之前,我们还是先来分析下问题:
业务并发现象带来的问题:==秒杀==
- 假如有 100 个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖
- 对于这一类问题,其实有很多的解决方案可以使用
- 第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如 12306 有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题
- 我们接下来介绍的这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过 2000 以上的就需要考虑其他的解决方案了。
简单来说,乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。
4.4.2 实现思路
乐观锁的实现方式:
- 数据库表中添加 version 列,比如默认值给 1
- 第一个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1
- 第二个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1
- 第一个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
- 假如第一个线程先执行更新,会把 version 改为 2,
- 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据 version 已经为 2,所以第二个线程会修改失败
- 假如第二个线程先执行更新,会把 version 改为 2,
- 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据 version 已经为 2,所以第一个线程会修改失败
- 不管谁先执行都会确保只能有一个线程更新数据,这就是 MP 提供的乐观锁的实现原理分析。
上面所说的步骤具体该如何实现呢?
4.4.3 实现步骤
分析完步骤后,具体的实现步骤如下:
步骤 1:数据库表添加列
列名可以任意,比如使用version
,给列设置默认值为1
步骤 2:在模型类中添加对应的属性
根据添加的字段列名,在模型类中添加对应的属性值
1 |
|
步骤 3:添加乐观锁的拦截器
1 |
|
步骤 4:执行更新操作
1 |
|
你会发现,这次修改并没有更新 version 字段,原因是没有携带 version 数据。
添加 version 数据
1 |
|
你会发现,我们传递的是 1,MP 会将 1 进行加 1,然后,更新回到数据库表中。
所以要想实现乐观锁,首先第一步应该是拿到表中的 version,然后拿 version 当条件在将 version 加 1 更新回到数据库表中,所以我们在查询的时候,需要对其进行查询
1 |
|
大概分析完乐观锁的实现步骤以后,我们来模拟一种加锁的情况,看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。
1 |
|
运行程序,分析结果:
乐观锁就已经实现完成了,如果对于上面的这些步骤记不住咋办呢?
参考官方文档来实现:
https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
5,快速开发
5.1 代码生成器原理分析
造句:
我们可以往空白内容进行填词造句,比如:
在比如:
观察我们之前写的代码,会发现其中也会有很多重复内容,比如:
那我们就想,如果我想做一个 Book 模块的开发,是不是只需要将红色部分的内容全部更换成Book
即可,如:
所以我们会发现,做任何模块的开发,对于这段代码,基本上都是对红色部分的调整,所以我们把去掉红色内容的东西称之为==模板==,红色部分称之为==参数==,以后只需要传入不同的参数,就可以根据模板创建出不同模块的 dao 代码。
除了 Dao 可以抽取模块,其实我们常见的类都可以进行抽取,只要他们有公共部分即可。再来看下模型类的模板:
- ① 可以根据数据库表的表名来填充
- ② 可以根据用户的配置来生成 ID 生成策略
- ③ 到 ⑨ 可以根据数据库表字段名称来填充
所以只要我们知道是对哪张表进行代码生成,这些内容我们都可以进行填充。
分析完后,我们会发现,要想完成代码自动生成,我们需要有以下内容:
- 模板: MyBatisPlus 提供,可以自己提供,但是麻烦,不建议
- 数据库相关配置:读取数据库获取表和字段信息
- 开发者自定义配置:手工配置,比如 ID 生成策略
5.2 代码生成器实现
步骤 1:创建一个 Maven 项目
代码 2:导入对应的 jar 包
1 |
|
步骤 3:编写引导类
1 |
|
步骤 4:创建代码生成类
1 | public class CodeGenerator { |
对于代码生成器中的代码内容,我们可以直接从官方文档中获取代码进行修改,
https://mp.baomidou.com/guide/generator.html
步骤 5:运行程序
运行成功后,会在当前项目中生成很多代码,代码包含controller
,service
,mapper
和entity
至此代码生成器就已经完成工作,我们能快速根据数据库表来创建对应的类,简化我们的代码开发。
5.3 MP 中 Service 的 CRUD
回顾我们之前业务层代码的编写,编写接口和对应的实现类:
1 | public interface UserService{ |
接口和实现类有了以后,需要在接口和实现类中声明方法
1 | public interface UserService{ |
MP 看到上面的代码以后就说这些方法也是比较固定和通用的,那我来帮你抽取下,所以 MP 提供了一个 Service 接口和实现类,分别是:IService
和ServiceImpl
,后者是对前者的一个具体实现。
以后我们自己写的 Service 就可以进行如下修改:
1 | public interface UserService extends IService<User>{ |
修改以后的好处是,MP 已经帮我们把业务层的一些基础的增删改查都已经实现了,可以直接进行使用。
编写测试类进行测试:
1 |
|
注意:mybatisplus_04_generator 项目中对于 MyBatis 的环境是没有进行配置,如果想要运行,需要提取将配置文件中的内容进行完善后在运行。
思考:在 MP 封装的 Service 层都有哪些方法可以用?
查看官方文档:https://mp.baomidou.com/guide/crud-interface.html
,这些提供的方法大家可以参考官方文档进行学习使用,方法的名称可能有些变化,但是方法对应的参数和返回值基本类似。