HM2022ssm-mp3【DQL查询编程控制】
1. 条件查询
1.1 条件查询的类
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper
类,这个类就是用来构建查询条件的,如下图所示:
那么条件查询如何使用Wrapper来构建呢?
1.2 构建环境
idea如何在一个项目project中创建多个模块module(小项目)
在构建条件查询之前,我们先来准备下环境
1.2.1 空项目中创建一个SpringBoot模块(子项目)
1.2.2 pom.xml中添加对应的依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yppah</groupId>
<artifactId>mp_02_dql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mp_02_dql</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<scope>runtime</scope>-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<optional>true</optional>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2.3 User实体类
package com.yppah.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
1.2.4 UserDao接口
package com.yppah.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yppah.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {
}
1.2.5 yaml配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver # pom中mysql依赖<scope>runtime</scope>注释掉,不然报红
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
#mybatis-plus:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
1.2.6 测试类
package com.yppah;
import com.yppah.dao.UserDao;
import com.yppah.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Mp02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
List<User> userList = userDao.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
最终项目目录为:
最终pom:
<?xml version="1.0" encoding="UTF-8"?>
<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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yppah</groupId>
<artifactId>mp_02_dql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mp_02_dql</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<scope>runtime</scope>-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
测试结果:
1.2.7 清爽日志打印处理
测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:
- 取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
- 取消MybatisPlus和SpringBoot的启动banner图标,application.yml添加如下内容:
mybatis-plus:
global-config:
banner: false # 关闭mybatisplus启动图标
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标
解决控制台打印日志过多的相关操作可以不用去做;一般会被用来方便我们查看程序运行的结果。
1.3 构建条件查询
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式:
1.3.1 第一种:QueryWrapper(偶尔用)
@Test
void testGetByRule() {
//按条件查询
QueryWrapper wrapper = new QueryWrapper();
wrapper.lt("age", 10); // 年龄小于10
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
-
lt: 小于(<) ,最终的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
- 第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如age写错,就会导致查询不成功
1.3.2 第二种:QueryWrapper的基础上使用lambda
-
User::getAget,为lambda表达式中的,类名::方法名,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
-
构建LambdaQueryWrapper的时候泛型不能省
- 此时我们再次编写条件的时候,就不会存在写错名称的情况,缺点是qw后面多了一层lambda()调用
1.3.3 第三种:LambdaQueryWrapper(推荐)
- 这种方式就解决了上一种方式所存在的问题
package com.yppah;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yppah.dao.UserDao;
import com.yppah.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Mp02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetByRule() {
//方式1:按条件查询
/*QueryWrapper wrapper = new QueryWrapper(); //可以不指定泛型
wrapper.lt("age", 10); // 年龄小于10*/
//方式2:lambda格式按条件查询
/*QueryWrapper<User> wrapper = new QueryWrapper<User>(); //必须指定泛型
wrapper.lambda().lt(User::getAge, 10);*/
//方式3:lambda格式按条件查询2
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
wrapper.lt(User::getAge, 10);
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
}
1.4 多条件构建
学完了三种构建查询对象的方式,每一种都有自己的特点,所以用哪一种都行,刚才都是一个条件,那如果有多个条件该如何构建呢?
需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
@Test
void testGetByMultiRule() {
//多条件查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
/*wrapper.lt(User::getAge, 30);
wrapper.gt(User::getAge, 10);*/
wrapper.lt(User::getAge, 30).gt(User::getAge, 10); //建议链式编程,防止条件过多可读性变差
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
-
gt:大于(>),最终的SQL语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
-
构建多条件的时候,可以支持链式编程
需求:查询数据库表中,年龄小于10或年龄大于30的数据
@Test
void testGetByMultiRule2() {
//多条件查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
wrapper.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
-
or()就相当于我们sql语句中的
or
关键字,不加默认是and
,最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
1.5 null判定
先来看一张图
- 我们在做条件查询的时候,一般会有很多条件可以供用户进行选择查询。
- 这些条件用户可以选择使用也可以选择不使用,比如我要查询价格在8000以上的手机
- 在输入条件的时候,价格有一个区间范围,按照需求只需要在第一个价格输入框中输入8000
- 后台在做价格查询的时候,一般会让 price>值1 and price <值2
- 因为前端没有输入值2,所以如果不处理的话,就会出现 price>8000 and price < null问题
- 这个时候查询的结果就会出问题,具体该如何解决?
需求:查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
package com.yppah.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
package com.yppah.domain.query;
import com.yppah.domain.User;
import lombok.Data;
@Data
public class UserQuery extends User {
private Integer age2; //age2表示年龄上限,原先User中的age表示年龄下限
}
不判定的情况:
正解null值判定:
@Test
void testJudgeNull() {
//模拟页面传递过来的查询数据区间
UserQuery uq = new UserQuery();
// uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
/*wrapper.lt(User::getAge, uq.getAge2());
wrapper.gt(User::getAge, uq.getAge()); //null*/
//null判定(先判断第一个参数是否为true,是才会连接后面的查询条件)
wrapper.lt(null!=uq.getAge2(), User::getAge, uq.getAge2()); //<30
wrapper.gt(null!=uq.getAge(), User::getAge, uq.getAge()); //此时为null-->不被加入到查询条件中
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
//也可以链式编程
wrapper.lt(null!=uq.getAge2(), User::getAge, uq.getAge2())
.gt(null!=uq.getAge(), User::getAge, uq.getAge());
2. 查询投影
2.1 查询指定字段
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。
具体如何来实现?
/**
* 查询投影:查询指定字段方式
*/
@Test
void testProjection(){
// 查询指定字段方式1:LambdaQueryWrapper格式
/*LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
wrapper.select(User::getId, User::getName, User::getAge);*/
// 查询指定字段方式2:QueryWrapper格式
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.select("id", "name", "age");
List<User> userList = userDao.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
-
select(...)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT id,name,age FROM user
-
注意:如果使用的不是lambda,就需要手动指定字段
QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.select("id", "name", "age");
2.2 聚合查询
需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
/**
* 查询投影:聚合查询
*/
@Test
void testProjection2(){
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.select("count(*) as count, max(age), min(age), avg(age), sum(age)");
List<Map<String, Object>> mapList = userDao.selectMaps(wrapper);
for (Map<String, Object> stringObjectMap : mapList) {
System.out.println(stringObjectMap);
}
}
2.3 分组查询
需求:分组查询,完成 group by的查询使用
/**
* 查询投影:分组查询
*/
@Test
void testProjection3(){
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.select("count(*) as count, tel").groupBy("tel");
List<Map<String, Object>> mapList = userDao.selectMaps(wrapper);
for (Map<String, Object> stringObjectMap : mapList) {
System.out.println(stringObjectMap);
}
}
-
sql
SELECT count(*) as count, tel FROM user GROUP BY tel
-
聚合与分组查询,无法使用lambda表达式来完成
-
MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现
3. 查询条件
前面我们只使用了lt()和gt(),除了这两个方法外,MP还封装了很多条件对应的方法,这一节我们重点把MP提供的查询条件方法进行学习下。
MP的查询条件有很多:
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
- ……
3.1 等值查询
-
需求:根据用户名和密码查询用户信息
-
代码
@Test void testRelus(){ LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>(); wrapper.eq(User::getName, "Jock").eq(User::getPassword, "123456"); User loginUser = userDao.selectOne(wrapper); System.out.println(loginUser); }
-
测试
-
注意
- eq(): 相当于
=
,对应的sql语句为SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
- selectList:查询结果为多个或者单个
- selectOne:查询结果为单个
- eq(): 相当于
3.2 范围查询
-
需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
-
代码
@Test void testRelus2(){ LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>(); wrapper.between(User::getAge, 10, 30); List<User> userList = userDao.selectList(wrapper); for (User user : userList) { System.out.println(user); } }
-
测试
-
注意
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
3.3 模糊查询
-
需求:查询表中name属性的值以
J
开头的用户信息,使用like进行模糊查询 -
代码
@Test void testRelus3(){ LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>(); wrapper.like(User::getName, "J"); List<User> userList = userDao.selectList(wrapper); for (User user : userList) { System.out.println(user); } }
-
测试
-
注意
- like():前后加百分号,如 %J%
- likeLeft():前面加百分号,如 %J,以J结尾
- likeRight():后面加百分号,如 J%,以J开头
3.4 排序查询
-
需求:查询所有数据,然后按照id降序
-
代码
@Test void testRelus4(){ LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>(); /** * condition :条件,返回boolean, * 当condition为true,进行排序,如果为false,则不排序 * isAsc:是否为升序,true为升序,false为降序 * columns:需要操作的列 */ wrapper.orderBy(true,false, User::getId); List<User> userList = userDao.selectList(wrapper); for (User user : userList) { System.out.println(user); } }
-
测试
-
注意
-
除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:
-
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
-
条件构造器 | MyBatis-Plus (baomidou.com)
-
4. 映射匹配兼容性
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
4.1 问题
4.1.1 问题1:表字段与编码属性设计不同步
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP给我们提供了一个注解@TableField
,使用该注解可以实现模型类属性名和表的列名之间的映射关系
4.1.2 问题2:编码中添加了数据库中未定义的属性
当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:Unknown column '多出来的字段名称' in 'field list'
具体的解决方案用到的还是@TableField
注解,它有一个属性叫exist
,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
4.1.3 问题3:采用默认查询开放了更多的字段查看权限
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
@Data
@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 Integer online;
}
4.1.4 问题4:表名与编码开发设计不同步
该问题主要是表的名称和模型类的名称不一致,导致查询失败,这个时候通常会报如下错误信息:
Table 'databaseName.tableNaem' doesn't exist,翻译过来就是数据库中的表不存在。
解决方案是使用MP提供的另外一个注解@TableName
来设置表与模型类之间的对应关系。
4.2 知识点
4.2.1 知识点1:@TableField
名称 | @TableField |
---|---|
类型 | 属性注解 |
位置 | 模型类属性定义上方 |
作用 | 设置当前属性对应的数据库表中的字段关系 |
相关属性 | value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select:设置属性是否参与查询,此属性与select()映射配置不冲突 |
4.2.2 知识点2:@TableName
名称 | @TableName |
---|---|
类型 | 类注解 |
位置 | 模型类定义上方 |
作用 | 设置当前类对应于数据库表关系 |
相关属性 | value(默认):设置数据库表名称 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!