2022黑马MyBatis Plus跟学笔记一
今日目标
基于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工程
创建空项目,配置编码
配置Maven的JDK
配置Maven的路径
配置save actions插件
我们可以参考以前的学习把SpringBoot整合MyBatisPlus来快速实现下,具体的实现步骤为:
步骤1:创建数据库及表
语句如下:
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');
步骤2:创建SpringBoot工程
Group:com.itheima
Artifact:mybatisplus_01_quickstart
步骤3:勾选配置使用技术
- 勾选配置使用的技术,能够实现自动添加起步依赖包
说明:
- 由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加
步骤4:pom.xml补全依赖
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
说明:
- druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源。
- 从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入,我们不需要额外在添加MyBatis的相关jar包。
步骤5:添加MP的相关配置信息
resources默认生成的是properties配置文件,可以将其替换成yml文件,并在文件中配置数据库连接的相关信息: application.yml
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: hsp
步骤6:根据数据库表创建实体类
com.itheima.pojo.User
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
'}';
}
}
步骤7:创建Dao接口(核心)
com.itheima.dao.UserDao这里要继承一个类BaseMapper
public interface UserDao extends BaseMapper<User> {
}
步骤8:编写引导类
com.itheima.Mybatisplus01QuickstartApplication
@SpringBootApplication
@MapperScan("com.itheima.dao")
public class Mybatisplus01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
}
}
说明:Dao接口要想被容器扫描到,有两种解决方案:
- 方案一:在Dao接口上添加@Mapper注解,并且确保Dao处在引导类所在包或其子包中该方案的缺点是需要在每一Dao接口中添加注解
@Mapper
public interface UserDao extends BaseMapper<User> {
}
- 方案二:在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包
该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,@Mapper就可以不写。
步骤9:编写测试类
Mybatisplus01QuickstartApplicationTests
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> list = userDao.selectList(null);
System.out.println(list);
}
}
运行测试类
说明:
userDao注入的时候下面有红线提示的原因是什么?
- UserDao是一个接口,不能实例化对象。
- 只有在服务器启动IOC容器初始化后,由框架创建DAO接口的代理对象来注入。
- 现在服务器并未启动,所以代理对象也未创建,IDEA查找不到对应的对象注入,所以提示报红。
- 一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。
查看运行结果:
跟之前整合MyBatis相比,你会发现我们不需要在DAO接口中编写方法和SQL语句了,只需要继承BaseMapper接口即可。整体来说简化很多。
1.2 MybatisPlus简介
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率。
通过刚才的案例,相信大家能够体会简化开发和提高效率这两个方面的优点。
MyBatisPlus的官网为: MyBatisPlus的官网
说明:
现在的页面中,这一行已经被删除,现在再去访问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都提供了哪些方法可以使用呢?
我们先来看张图:
功能 | 自定义接口 | MP接口 |
---|---|---|
新增 | boolean save(T t) | int insert(T t) |
删除 | boolean delete(int id) | int deleteById(Serializable id) |
修改 | boolean update(T t) | int updateById(T t) |
根据id查询 | T getById(int id) | T selectById(Serializable id) |
查询全部 | List< T > getAll() | List< T > selectList(Wrapper< T > queryWrapper) |
分页查询 | PageInfo< T > getAll(int page,int size) | IPage< T >page,Wrapper< T > queryWrapper |
按条件分页查询 | List< T > getAll(Condition condition) | IPage< T >page,Wrapper< T > queryWrapper |
对于这张图的方法,我们挨个来演示下:
首先说下,案例中的环境就是咱们入门案例的内容,第一个先来完成新增功能
2.2 新增
在进行新增之前,我们可以分析下新增的方法:
int insert (T t)
- T:泛型,新增用来保存新增数据。
- int:返回值,新增成功后返回1,没有新增成功返回的是0。
在测试类中进行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testSave() {
User user = new User();
user.setName("小王");
user.setPassword("abc");
user.setAge(17);
user.setTel("13565781121");
int affectedRow = userDao.insert(user);
System.out.println("affectedRow:" + affectedRow);
}
}
执行测试后,数据库表中就会添加一条数据。
后台展示
但是数据中的主键ID,有点长,那这个主键ID是如何来的?我们更想要的是主键自增,应该是5才对,这个是我们后面要学习的主键ID生成策略,这块的这个问题,我们暂时先放放。
2.3 删除
在进行删除之前,我们可以分析下删除的方法:
int deleteById (Serializable id)
Serializable:参数类型
思考:参数类型为什么是一个序列化类?
我们看一下源码String.class
public final class String implements Serializable, Comparable<String>, CharSequence {}
Number.class
public abstract class Number implements Serializable {}
从这张图可以看出,
- String和Number是Serializable的子类,
- Number又是Float,Double,Integer等类的父类,
- 能作为主键的数据类型都已经是Serializable的子类,
- MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。
- int:返回值类型,数据删除成功返回1,未删除数据返回0。
在测试类中进行删除操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testDelete() {
int i = userDao.deleteById(1621908110012145665L);
System.out.println("i:" + i);
}
}
执行测试类后,后台展示
数据库新加入的那条数据被删除了
2.4 修改
在进行修改之前,我们可以分析下修改的方法:
int updateById(T t);
- T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值。
- int:返回值,修改成功后返回1,未修改数据返回0。
在测试类中进行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testUpdate() {
User user = new User();
user.setPassword("ccc");
user.setName("Tommy");
user.setAge(21);
user.setId(1L);
int i = userDao.updateById(user);
System.out.println("i:" + i);
}
}
修改后,后台
数据库中数据
说明:修改的时候,只修改实体对象中有值的字段。
2.5 根据ID查询
在进行根据ID查询之前,我们可以分析下根据ID查询的方法:
T selectById (Serializable id)
- Serializable:参数类型,主键ID的值
- T:根据ID查询只会返回一条数据
在测试类中进行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetById() {
User user = userDao.selectById(2L);
System.out.println(user);
}
}
后台展示
2.6 查询所有
在进行查询所有之前,我们可以分析下查询所有的方法:
List<T> selectList(Wrapper<T> queryWrapper)
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- List:因为查询的是所有,所以返回的数据是一个集合
在测试类中进行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> list = userDao.selectList(null);
System.out.println(list);
}
}
后台展示
我们所调用的方法都是来自于DAO接口继承的BaseMapper类中。里面的方法有很多,我们后面会慢慢去学习里面的内容。
我们可以简单看一下里面的方法
2.7 Lombok
代码写到这,我们会发现DAO接口类的编写现在变成最简单的了,里面什么都不用写。反过来看看模型类的编写都需要哪些内容:
- 私有属性
- setter…getter…方法
- toString方法
- 构造函数
虽然这些内容不难,同时也都是通过IDEA工具生成的,但是过程还是必须得走一遍,那么对于模型类的编写有没有什么优化方法?就是我们接下来要学习的Lombok。
- 概念:Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
使用步骤
步骤1:添加lombok依赖
<!-- lombok对实体类快速标注 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>
注意:版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
步骤2:安装Lombok的插件
新版本IDEA已经内置了该插件,如果删除setter和getter方法程序有报红,则需要安装插件。
如果在IDEA中找不到lombok插件,可以访问如下网站
lombok官网
根据自己IDEA的版本下载对应的lombok插件,下载成功后,在IDEA中采用离线安装的方式进行安装。
找到下载位置,导入
步骤3:模型类上添加注解
Lombok常见的注解有:
- @Setter:为模型类的属性提供setter方法
- @Getter:为模型类的属性提供getter方法
- @ToString:为模型类的属性提供toString方法
- @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
- @Data:是个组合注解,包含上面的注解的功能
- @NoArgsConstructor:提供一个无参构造函数
- @AllArgsConstructor:提供一个包含所有参数的构造函数
Lombok的注解还有很多,上面标红的三个是比较常用的,其他的大家后期用到了,再去补充学习。
User.java可以快速写为
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
Lombok只是简化模型类的编写,我们之前的方法也能用,比如有人会问:我如果只想要有name和password的构造函数,该如何编写?
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
这种方式是被允许的。
2.8 分页功能
基础的增删改查就已经学习完了,刚才我们在分析基础开发的时候,有一个分页功能还没有实现,在MP中如何实现分页功能,就是咱们接下来要学习的内容。
分页查询使用的方法是:
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage:用来构建分页查询条件
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page。
步骤1:调用方法传入参数获取返回值
//分页查询
@Test
void testSelectPage() {
// 1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
Page<User> page = new Page<>(1, 3);
//2 执行分页查询
userDao.selectPage(page, null);
//3 获取分页结果
System.out.println("当前页码值:" + page.getCurrent());
System.out.println("每页显示数:" + page.getSize());
System.out.println("一共多少页:" + page.getPages());
System.out.println("一共多少条数据:" + page.getTotal());
System.out.println("数据:" + page.getRecords());
}
如果只有这个方法,是实现不了分页的,还需要分页拦截器。
步骤2:设置分页拦截器
这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
说明:上面的代码记不住咋办呢?
这些内容在MP的官方文档中有详细的说明,我们可以查看官方文档类配置
步骤3:运行测试程序
如果想查看MP执行的SQL语句,可以修改application.yml配置文件,
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
打开日志后,就可以在控制台打印出对应的SQL语句,开启日志功能性能就会受到影响,调试完后记得关闭。
3.DQL编程控制
增删改查四个操作中,查询是非常重要的也是非常复杂的操作,这块需要我们重点学习下,这节我们主要学习的内容有:
- 条件查询方式
- 查询投影
- 查询条件设定
- 字段映射与表名映射
3.1 条件查询
3.1.1 条件查询的类
- MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper类,这个类就是用来构建查询条件的,如下图所示:
那么条件查询如何使用Wrapper来构建呢?
3.1.2 环境构建
在构建条件查询之前,我们先来准备下环境
- 创建一个SpringBoot项目
Group:com.itheima
Artifact:mybatisplus_02_dql
- pom.xml中添加对应的依赖
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- lombok对实体类快速标注 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>
- 编写模型类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
- 编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
- 编写配置文件
#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: hsp
#mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 编写测试类
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> list = userDao.selectList(null);
list.forEach(System.out::println);
}
}
最终创建的项目结构为:
- 测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:
取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
之后运行测试类,看到logback的日志看的比较清爽。
说明:logback.xml的配置内容,不是我们学习的重点,如果有兴趣可以自行百度查询。
- 取消MybatisPlus启动banner图标
application.yml添加如下内容:
#mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
启动测试类Mybatisplus02DqlApplicationTests.java,发现上面的图标消失,但是还有一个图标。
- 取消SpringBoot的log打印
application.yml添加如下内容:
application.yml代码如下
#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: hsp
main:
banner-mode: off # 关掉springboot图标
解决控制台打印日志过多的相关操作可以不用去做,一般会被用来方便我们查看程序运行的结果。
最后运行测试类查看控制台
3.1.3 构建条件查询
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式
方式一:QueryWrapper普通格式
1.先来看第一种:QueryWrapper
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetUserByAge() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lt("age", 41);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- lt: 小于(<) ,最终的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
控制台打印
第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如age写错,就会导致查询不成功
方式二:QueryWrapper使用lambda
2.接着来看第二种:QueryWrapper的基础上使用lambda
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetUserByAgeLambda() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().lt(User::getAge, 21);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- User::getAget,为lambda表达式中的,类名::方法名,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
注意:构建LambdaQueryWrapper的时候泛型不能省。
此时我们再次编写条件的时候,就不会存在写错名称的情况,但是wrapper后面多了一层lambda()调用。
lt (小于) less than
le(小于等于)less equal
eq(等于)equal
gt(大于)greater than
ge(大于等于)greater equal
ne (不等于)not equal
方式三:LambdaQueryWrapper使用lambda
3.接着来看第三种:LambdaQueryWrapper
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserByAgeLambdaQuery() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.lt(User::getAge, 41);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
这种方式就解决了上一种方式所存在的问题。
后台结果:
3.1.4 多条件构建
学完了三种构建查询对象的方式,每一种都有自己的特点,所以用哪一种都行,刚才都是一个条件,那如果有多个条件该如何构建呢?
需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
如果采用单条件,很容易想到between来查询
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeBetween() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().between(User::getAge, 10, 30);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
那如果想用多条件呢
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeManWrappers() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().gt(User::getAge, 10);
wrapper.lambda().lt(User::getAge, 30);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
最终的SQL语句为
SELECT id,name,password,age,tel FROM user WHERE (age > ? AND age < ?)
构建多条件的时候,可以支持链式编程
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeManWrappersChain() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().gt(User::getAge, 10).lt(User::getAge, 30);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果
需求:查询数据库表中,年龄小于10或年龄大于30的数据
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeManWrappersChainOr() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- or()就相当于我们sql语句中的or关键字,不加默认是and,最终的sql语句为:
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属性,如:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
使用一个age属性,如何去接收页面上的两个值呢?这个时候我们有两个解决方案。
方案一:添加属性age2,这种做法可以但是会影响到原模型类的属性内容
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private Integer age2;
private String tel;
}
方案二:新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。
UserQuery.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserQuery extends User {
private Integer age2;
}
环境准备好后,我们来实现下刚才的需求:
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeWrappersChain() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 模拟页面传过来的2条数据
UserQuery userQuery = new UserQuery();
userQuery.setAge(10);
userQuery.setAge2(30);
if (null != userQuery.getAge()) {
wrapper.lambda().gt(User::getAge, userQuery.getAge());
}
if (null != userQuery.getAge2()) {
wrapper.lambda().lt(User::getAge, userQuery.getAge2());
}
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果
我们尝试一下,把其中一个age给设置为null
运行一下测试类,发现为null的值就不会拼接到sql中
上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,代码量就比较大,来看MP给我们提供的简化方式:
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetUserAgeWrappers() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 模拟页面传过来的2条数据
UserQuery userQuery = new UserQuery();
userQuery.setAge(10);
userQuery.setAge2(30);
// condition为boolean类型,返回true,则添加条件,返回false则不添加条件
wrapper.lambda().gt(null != userQuery.getAge(), User::getAge, userQuery.getAge());
wrapper.lambda().lt(null != userQuery.getAge2(), User::getAge, userQuery.getAge2());
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果
- lt()方法
condition为boolean类型,返回true,则添加条件,返回false则不添加条件
3.2 查询投影
3.2.1 查询指定字段
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。
具体如何来实现?
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testProjection() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().select(User::getName, User::getAge, User::getTel);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果如下:
- select(…)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT name,age,tel FROM user
- 如果使用的不是lambda,就需要手动指定字段
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testProjectionHand() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("name", "age", "tel");
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- 最终的sql语句为
SELECT name,age,tel FROM user
3.2.2 聚合查询
需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
类Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testPolymerization() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("count(*) as count");
List<Map<String, Object>> maps = userDao.selectMaps(wrapper);
System.out.println(maps);
}
}
注意一下查询的数据类型这里用MP提供的selectMaps,返回值是List<Map<String, Object>>类型的。
查询结果
为了在做结果封装的时候能够更简单,我们将上面的聚合函数都起了个名称,方面后期来获取这些数据。
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testPolymerizationMax() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 起了个别名
//wrapper.select("max(age) as maxAge");
//wrapper.select("avg(age) as avgAge");
//wrapper.select("min(age) as minAge");
wrapper.select("sum(age) as sumAge");
List<Map<String, Object>> maps = userDao.selectMaps(wrapper);
System.out.println(maps);
}
}
3.2.3 分组查询
需求:分组查询,完成 group by的查询使用
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testPolymerizationGroup() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 起了个别名
wrapper.select("count(age) as countAge,tel");
wrapper.groupBy("tel");
List<Map<String, Object>> maps = userDao.selectMaps(wrapper);
System.out.println(maps);
}
}
- groupBy为分组,最终的sql语句为
SELECT count(age) as countAge,tel FROM user GROUP BY tel
注意
聚合与分组查询,无法使用lambda表达式来完成。
MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现。
那如果再加上排序呢
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testPolymerizationGroup() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 起了个别名
wrapper.select("count(age) as countAge,tel");
wrapper.groupBy("tel");
wrapper.orderByDesc("tel");
List<Map<String, Object>> maps = userDao.selectMaps(wrapper);
System.out.println(maps);
}
}
查询sql
SELECT count(age) as countAge,tel FROM user GROUP BY tel ORDER BY tel DESC
结果
3.3 查询条件
前面我们只使用了lt()和gt(),除了这两个方法外,MP还封装了很多条件对应的方法,这一节我们重点把MP提供的查询条件方法进行学习下。
MP的查询条件有很多:
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
3.3.1 等值查询
需求:根据用户名和密码查询用户信息
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testNamePsd() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(User::getName, "Tommy");
wrapper.lambda().eq(User::getPassword, "ccc");
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- eq(): 相当于 = ,对应的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
查询结果
- selectList:查询结果为多个或者单个
- selectOne:查询结果为单个
那么下面看一下selectOne的查询示例
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testselectOne() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("count(age) as countAge");
User user = userDao.selectOne(wrapper);
System.out.println(user);
}
}
查询sql
SELECT count(age) as countAge FROM user
查询结果
3.3.2 范围查询
需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
查询一下小于等于41岁,大于等于15岁的人
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testSelectWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().between(User::getAge, 15, 41);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
注意用between查询的时候,小的值在前面,大的值在后面
查询sql
SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
查询结果
然后我们换一种方式用 <= 和 >= 来实现
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testSelectWrapperLtGt() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().ge(User::getAge, 15).le(User::getAge, 41);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- le():小于等于(<=)
- between():between ? and ?
3.3.3 模糊查询
需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testSelectWrapperLike() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().likeRight(User::getName, "J");
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
- like():前后加百分号,如 %J%
- likeLeft():前面加百分号,如 %J
- likeRight():后面加百分号,如 J%
查询结果
3.3.4 排序查询
需求:查询所有数据,然后按照id降序
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testDesc() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().orderByDesc(User::getId);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询sql
SELECT id,name,password,age,tel FROM user ORDER BY id DESC
查询结果
另一种方式
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testOrderDesc() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(User::getId);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询的sql
SELECT id,name,password,age,tel FROM user ORDER BY id DESC
查询结果
另一种方式,用到orderBy
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testOrderAnother() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
/**
* wrapper.orderBy(condition, isAsc, User::getId);
* condition 当condition为true,进行排序,如果为false,则不排序
* isAsc 是否为升序,true为升序,false为降序
*/
wrapper.orderBy(true, false, User::getId);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询结果
除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:
- orderBy排序
condition:条件,true则添加排序,false则不添加排序
isAsc:是否为升序,true升序,false降序
columns:排序字段,可以有多个 - orderByAsc/Desc(单个column):按照指定字段进行升序/降序
- orderByAsc/Desc(多个column):按照多个字段进行升序/降序
- orderByAsc/Desc
condition:条件,true添加排序,false不添加排序
多个columns:按照多个字段进行排序
除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如
isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档的条件构造器来学习使用。
具体的网址为:
官方文档
比如我们查找电话是空的人
Mybatisplus02DqlApplicationTests.java
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testNull() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda().isNull(User::getTel);
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
查询sql
SELECT id,name,password,age,tel FROM user WHERE (tel IS NULL)
查询结果
3.4 映射匹配兼容性
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型。
类:
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
那么问题就来了:
问题1:表字段与编码属性设计不同步
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP给我们提供了一个注解@TableField ,使用该注解可以实现模型类属性名和表的列名之间的映射关系。
问题2:编码中添加了数据库中未定义的属性
当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:
Unknown column ‘多出来的字段名称’ in ‘field list’
具体的解决方案用到的还是@TableField注解,它有一个属性叫exist,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
问题3:采用默认查询开放了更多的字段查看权限
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField注解的一个属性叫select,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
修改User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
@TableField(select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Double height;
}
现在我们设置了一个表中不存在的字段height,和一个不希望展示的字段password,然后我们查询全表。
查询结果
知识点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
ALTER TABLE mybatisplus_db.`user` RENAME mybatisplus_db.`tbl_user`;
直接查询会报错,原因是MP默认情况下会使用模型类的类名首字母小写当表名使用。
步骤2:模型类添加@TableName注解
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Double height;
}
再查询一下,报错就没有了
步骤3:将字段password修改成pwd
ALTER TABLE mybatisplus_db.`tbl_user` CHANGE `password` `pwd` VARCHAR(32) NOT NULL;
直接查询会报错,原因是MP默认情况下会使用模型类的属性名当做表的列名使用。
步骤4:使用@TableField映射关系
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField("pwd")
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Double height;
}
步骤5:添加一个数据库表不存在的字段
添加字段height
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField("pwd")
private String password;
private Integer age;
private String tel;
private Double height;
}
直接查询会报错,原因是MP默认情况下会查询模型类的所有属性对应的数据库表的列,而height不存在
步骤6:使用@TableField排除字段
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField("pwd")
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Double height;
}
查询结果
步骤7:查询时将pwd隐藏
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value = "pwd", select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Double height;
}
查询结果