增删改查四个操作中,查询是非常重要的也是非常复杂的操作,本次介绍的有:
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper
类,这个类就是用来构建查询条件的,如下图所示:
那么条件查询如何使用Wrapper来构建呢?
在构建条件查询之前,我们先来准备下环境
pom.xml中添加对应的依赖
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.5version>
<relativePath/>
parent>
<groupId>com.dcxuexigroupId>
<artifactId>springboot_mp_03_dqlartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springboot_mp_03_dqlname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>com.mysqlgroupId>
<artifactId>mysql-connector-jartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
编写模型类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
private String branchName;
}
编写引导类
@SpringBootApplication
public class SpringbootMp03DqlApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMp03DqlApplication.class, args);
}
}
编写配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
编写测试类
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectById() {
User user = userDao.selectById(146);
System.out.println("user = " + user);
}
}
最终创建的项目结构为:
测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以接下来我们把这个日志处理下:
取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
<configuration>
configuration>
取消MybatisPlus启动banner图标
application.yml添加如下内容:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
global-config:
banner: false # 关闭mybatisplus启动图标
取消SpringBoot的log打印
application.yml添加如下内容:
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
解决控制台打印日志过多的相关操作可以不用去做,一般会被用来方便我们查看程序运行的结果。
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.lt("user_id",10);
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
lt:小于(<) ,最终的sql语句为
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_id < ?)
第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如user_id写错,就会导致查询不成功
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//queryWrapper.lt("user_id",10);
queryWrapper.lambda().lt(User::getUserId,10);
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_id < ?)
注意: 构建LambdaQueryWrapper的时候泛型不能省。
此时我们再次编写条件的时候,就不会存在写错名称的情况,但是qw后面多了一层lambda()调用
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
//QueryWrapper queryWrapper = new QueryWrapper();
//queryWrapper.lt("user_id",10);
//queryWrapper.lambda().lt(User::getUserId,10);
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(User::getUserId,10);
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
这种方式就解决了上一种方式所存在的问题。
上面的三种构建查询对象的方式,每一种都有自己的特点,所以用哪一种都行,刚才都是一个条件,那如果有多个条件该如何构建呢?
需求:查询数据库表中,用户ID在10到20之间的用户信息
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.gt(User::getUserId,10);
lambdaQueryWrapper.lt(User::getUserId,20);
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
gt:大于(>),最终的SQL语句为
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_id > ? AND user_id < ?)
构建多条件的时候,可以支持链式编程
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.gt(User::getUserId,10).lt(User::getUserId,20);
//lambdaQueryWrapper.lt(User::getUserId,20);
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
需求:查询数据库表中,用户ID小于10或大于20的数据
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(User::getUserId,10).or().gt(User::getUserId,20);
//lambdaQueryWrapper.lt(User::getUserId,20);
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
or()就相当于我们sql语句中的or
关键字,不加默认是and
,最终的sql语句为:
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_id < ? OR user_id > ?)
需求:查询数据库表中,根据输入用户ID范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该用户ID的用户
如果只输入第二个框,说明要查询小于该用户ID的用户
如果两个框都输入了,说明要查询用户ID在两个范围之间的用户
思考第一个问题:后台如果想接收前端的两个数据,该如何接收?
我们可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个userId属性,如:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
private String branchName;
}
使用一个userId属性,如何去接收页面上的两个值呢?这个时候我们有两个解决方案
方案一:添加属性userId2,这种做法可以但是会影响到原模型类的属性内容
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
private String branchName;
private Integer userId2;
}
方案二:新建一个模型类,让其继承User类,并在其中添加userId2属性,UserQuery在拥有User属性后同时添加了userId2属性。
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
private String branchName;
}
@Data
public class QueryUser extends User {
private Integer userId2;
}
环境准备好后,我们来实现下刚才的需求:
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
//模拟页面传递过来的查询数据
QueryUser queryUser = new QueryUser();
queryUser.setUserId2(50);
queryUser.setUserId(20);
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
if (null != queryUser.getUserId2()){
lambdaQueryWrapper.lt(User::getUserId,queryUser.getUserId2());
}
if (null != queryUser.getUserId()){
lambdaQueryWrapper.gt(User::getUserId,queryUser.getUserId());
}
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,代码量就比较大,来看MP给我们提供的简化方式:
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
//模拟页面传递过来的查询数据
QueryUser queryUser = new QueryUser();
queryUser.setUserId2(50);
queryUser.setUserId(20);
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(null != queryUser.getUserId2(),User::getUserId,queryUser.getUserId2());
lambdaQueryWrapper.gt(null != queryUser.getUserId(),User::getUserId,queryUser.getUserId());
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
lt()方法
condition为boolean类型,返回true,则添加条件,返回false则不添加条件
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。
具体如何来实现:
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(User::getUserName,User::getEmail);
List<User> userList = userDao.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
}
select(…)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT user_name,email FROM platform_user
如果使用的不是lambda,就需要手动指定字段
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
//LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.select("user_name","email");
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
SELECT user_name,email FROM platform_user
需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//queryWrapper.select("count(*) as num");
//queryWrapper.select("max(user_id) as maxId");
//queryWrapper.select("min(user_id) as minId");
//queryWrapper.select("avg(user_id) as avgId");
queryWrapper.select("sum(user_id) as sumId");
List<Map<String, Object>> mapList = userDao.selectMaps(queryWrapper);
System.out.println(mapList);
}
}
为了在做结果封装的时候能够更简单,我们将上面的聚合函数都起了个名称,方面后期来获取这些数据
需求:分组查询,完成 group by的查询使用
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.select("count(*) as num , branch_name");
queryWrapper.groupBy("branch_name");
List<Map<String, Object>> mapList = userDao.selectMaps(queryWrapper);
System.out.println(mapList);
}
}
groupBy为分组,最终的sql语句为
SELECT count(*) as num , branch_name FROM platform_user GROUP BY branch_name
注意:
前面我们只使用了lt()和gt(),除了这两个方法外,MP还封装了很多条件对应的方法,重点把MP提供的查询条件方法进行介绍下。
MP的查询条件有很多:
需求:根据用户名和归属公司查询用户信息
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.lambda().eq(User::getUserName,"fenjinba").eq(User::getBranchName, "奋进吧");
User user = userDao.selectOne(queryWrapper);
System.out.println("user = " + user.toString());
}
}
eq(): 相当于 =
,对应的sql语句为
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_name = ? AND branch_name = ?)
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.lambda().between(User::getUserId,10,20);
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
gt():大于(>)
ge():大于等于(>=)
lt():小于(<)
lte():小于等于(<=)
between():between ? and ?
SELECT user_id,email,user_name,branch_name FROM platform_user WHERE (user_id BETWEEN ? AND ?)
需求:查询表中branch_name属性的值以
上海
开头的用户信息,使用like进行模糊查询
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//queryWrapper.lambda().between(User::getUserId,10,20);
queryWrapper.lambda().like(User::getBranchName,"上海");
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
需求上海查询所有数据,然后按照user_id降序
@SpringBootTest
class SpringbootMp03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSelectAll(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//condition :条件,返回boolean,当condition为true,进行排序,如果为false,则不排序
//isAsc:是否为升序,true为升序,false为降序
//columns:需要操作的列
queryWrapper.lambda().orderBy(true,true,User::getUserId);
List<User> userList = userDao.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:
除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档的条件构造器来使用,暂时不介绍了。
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
CREATE TABLE `user` (
`user_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(60) NOT NULL DEFAULT '',
`user_name` varchar(60) NOT NULL DEFAULT '',
`branch_name` varchar(30) DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE,
KEY `idx_platform_user_id_name` (`user_name`) USING BTREE,
KEY `idx_platform_user_email` (`email`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class User {
private Integer userId;
private String email;
private String userName;
private String company;
}
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
那么问题就来了:
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP给我们提供了一个注解@TableField
,使用该注解可以实现模型类属性名和表的列名之间的映射关系
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
@TableId(value = "branch_name")
private String company;
}
当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:nested exception is java.sql.SQLSyntaxErrorException: Unknown column '多出来的字段名称' in 'field list'
具体的解决方案用到的还是@TableField
注解,它有一个属性叫exist
,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
@TableField(select = false)
private String email; //不查询该字段
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}
名称 | @TableField |
---|---|
类型 | 属性注解 |
位置 | 模型类属性定义上方 |
作用 | 设置当前属性对应的数据库表中的字段关系 |
相关属性 | value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select:设置属性是否参与查询,此属性与select()映射配置不冲突 |
该问题主要是表的名称和模型类的名称不一致,导致查询失败,这个时候通常会报如下错误信息:
CREATE TABLE `platform_user` (
`user_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(60) NOT NULL DEFAULT '',
`user_name` varchar(60) NOT NULL DEFAULT '',
`branch_name` varchar(30) DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE,
KEY `idx_platform_user_id_name` (`user_name`) USING BTREE,
KEY `idx_platform_user_email` (`email`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
@TableField(select = false)
private String email; //不查询该字段
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}
nested exception is java.sql.SQLSyntaxErrorException: Table 'mybatis.user' doesn't exist
翻译过来就是数据库中的表不存在。
解决方案是使用MP提供的另外一个注解@TableName
来设置表与模型类之间的对应关系。
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
@TableField(select = false)
private String email; //不查询该字段
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}
名称 | @TableName |
---|---|
类型 | 类注解 |
位置 | 模型类定义上方 |
作用 | 设置当前类对应于数据库表关系 |
相关属性 | value(默认):设置数据库表名称 |
接下来我们使用案例的方式把刚才的知识演示下:
直接查询会报错,原因是MP默认情况下会使用模型类的类名首字母小写当表名使用。
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
private String branchName;
}
直接查询会报错,原因是MP默认情况下会使用模型类的属性名当做表的列名使用
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
@TableField(value = "branch_name")
private String company;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
@TableField(value = "branch_name")
private String company;
private String address; //该字段数据表中不存在
}
直接查询会报错,原因是MP默认情况下会查询模型类的所有属性对应的数据库表的列,而address不存在
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
private String email;
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "platform_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer userId;
@TableField(select = false)
private String email; //不查询该字段
private String userName;
@TableField(value = "branch_name")
private String company;
@TableField(exist = false)
private String address; //该字段数据表中不存在
}