第二章:使用QueryDSL与SpringDataJPA实现单表普通条件查询
在企业开发中ORM框架有很多种如:Hibernate,Mybatis,JdbcTemplate等。每一种框架的设计理念是不一样的,Hibernate跟我们本章讲解的SpringDataJPA是一致的框架都是全自动理念作为设计核心,让用户更少的去写SQL语句通过简单的配置就可以实现各种查询。而Mybatis框架则是半自动理念作为设计核心,SQL让用户自己定义实现了更好的灵活性。
本章目标
本章我们目标实现QueryDSL通用查询语言整合SpringDataJPA完成单表的查询多样化。
构建项目
下面我们先来创建一个SpringBoot项目,具体如何使用Maven整合QueryDSL请访问 QueryDSL学习目录第一章 ,创建项目时的依赖也与第一章一致,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yuqiyu.querydsl.sample</groupId>
<artifactId>chapter2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>chapter2</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--阿里巴巴数据库连接池,专为监控而生 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
</dependency>
<!-- 阿里巴巴fastjson,解析json视图 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--queryDSL-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--添加QueryDSL插件支持-->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
上图代码块有关配置如有不明白的地方请查看 第一章:Maven环境下如何配置QueryDSL环境 。
构建数据库信息表
我们先来创建一张普通信息表,创建表SQL如下所示:
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`t_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`t_name` varchar(30) DEFAULT NULL COMMENT '名称',
`t_age` int(10) DEFAULT NULL COMMENT '年龄',
`t_address` varchar(100) DEFAULT NULL COMMENT '家庭住址',
`t_pwd` varchar(100) CHARACTER SET latin1 DEFAULT NULL COMMENT '用户登录密码',
PRIMARY KEY (`t_id`)
) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
测试数据这里就不添加了。
添加application.yml配置文件
在resource目录下我们添加application.yml配置文件来代替application.properties文件,添加对应的数据库连接池的配置信息,代码如下所示:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
properties:
hibernate:
show_sql: true
format_sql: true
创建实体
我们根据数据库内对应的字段创建一个实体类并添加对应的SpringDataJPA的注解,实体类代码如下所示:
package com.yuqiyu.querydsl.sample.chapter2.bean;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/2
* Time:18:31
* 码云:http://git.oschina.net/jnyqy
* ========================
@Data
@Entity
@Table(name = "t_user")
public class UserBean implements Serializable
@Column(name = "t_id")
@GeneratedValue
private Long id;
@Column(name = "t_name")
private String name;
@Column(name = "t_age")
private int age;
@Column(name = "t_address")
private String address;
@Column(name = "t_pwd")
private String pwd;
}
实体内有个注解@Data比较特殊,之前也许大家没有使用过,当然你们肯定发现了我这个实体类内并没有对应字段的Getter/Setter方法,如果没有添加@Data注解在SpringDataJPA映射数据时会出现找不到对应字段的Setter方法,导致无法完成数据映射到实体的异常!
在上面的实体源码中可以看到@Data注解是在lombok包内,lombok其实是一个优雅的第三方插件,它可以让你的实体变得简洁,可读性也大大的得到了提升。在使用这个插件的时候需要你们Idea开发工具支持,必填安装相应的Plugin才可以,这里我就不多说相关lombok的配置问题了,大家在跟本章联系的时候可以使用Getter/Setter方法的形式代替@Data。
创建基类JPA
这里我们简单的封装下JPA,我们添加一个接口去继承我们需要的JPA接口并让所有子类继承我们的基类接口就可以了,基类JPA代码如下所示:
package com.yuqiyu.querydsl.sample.chapter2.jpa;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
* 核心JPA
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/2
* Time:18:23
* 码云:http://git.oschina.net/jnyqy
* ========================
@NoRepositoryBean
public interface BaseJPA<T>
extends JpaRepository<T,Long>,
JpaSpecificationExecutor<T>,
QueryDslPredicateExecutor<T>
}
上面的注解@NoRepositoryBean是为了避免SpringDataJPA自动实例化才添加的。
创建逻辑JPA
接下来我们开始创建对应User模块的数据逻辑接口JPA,很简单,我们只需要创建一个接口继承下我们的BaseJPA就可以了,代码如下所示:
package com.yuqiyu.querydsl.sample.chapter2.jpa;
import com.yuqiyu.querydsl.sample.chapter2.bean.UserBean;
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/2
* Time:18:39
* 码云:http://git.oschina.net/jnyqy
* ========================
public interface UserJPA
extends BaseJPA<UserBean>
//../可以添加命名方法查询
}
我们在继承BaseJPA的时候用到了泛型,因为我们在BaseJPA内所继承的接口都需要我们传递一个具体的实体类的类型,所以这块我们采用了泛型来处理,只有具体逻辑JPA继承BaseJPA的时候传递具体的实体类型就可以了。
自动生成Q结构查询实体
我们之前说过了QueryDSL很神奇的地方就在于它是一个可通过Maven插件自动生成实体类型的结构查询实体,那么我们接下来使用maven compile命令来让我们配置的JPAAnnotationProcessor去做他自己的使命。
idea工具为我们的maven项目自带了比较全面的命令,我们直接使用就可以了,如下图1所示:
图1
双击红色框内的命令就可以执行了,在这之前如果你想使用本地的maven环境需要在Setting->Build Tools ->maven页面修改home dirctory。命令执行完成后我们可以看到target目录自动生成了并且为我们创建了一些目录,展开目录后可以看到QueryDSL为我们自动生成的查询实体,如下图2所示:
图2
maven插件会为我们自动创建一堆目录,我们的查询实体的位置是以我们pom.xml配置文件内配置的目录为准。打开自动创建的实体后可以看到QueryDSL自动为我们创建的查询字段以及构造函数,具体查询字段的含义后面会有所讲解。
编写查询
下面我们开始编写查询,在编写之前我们创建一个控制器用于查看我们查询到的数据,代码如下所示:
package com.yuqiyu.querydsl.sample.chapter2.controller;
import com.yuqiyu.querydsl.sample.chapter2.jpa.UserJPA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/2
* Time:18:38
* 码云:http://git.oschina.net/jnyqy
* ========================
@RestController
@RequestMapping(value = "/user")
public class UserController
@Autowired
private UserJPA userJPA;
}
这里我并没有追寻MVC三层的设计理念,因为这只是文章的测试Sample编写,大家在实际开发项目中还是需要按照MVC设计模式来进行设计架构。
查询全部数据并排序
我们先来一个简单的查询表内的所有数据并根据主键进行倒序,代码如下所示:
@RestController
@RequestMapping(value = "/user")
public class UserController
@Autowired
private UserJPA usrJPA;
//----以下是新添加内容
//实体管理者
@Autowired
private EntityManager entityManager;
//JPA查询工厂
private JPAQueryFactory queryFactory;
@PostConstruct
public void initFactory()
queryFactory = new JPAQueryFactory(entityManager);
System.out.println("init JPAQueryFactory successfully");
* 查询全部数据并根据id倒序
* @return
@RequestMapping(value = "/queryAll")
public List<UserBean> queryAll()
//使用querydsl查询
QUserBean _Q_user = QUserBean.userBean;
//查询并返回结果集
return queryFactory
.selectFrom(_Q_user)//查询源
.orderBy(_Q_user.id.desc())//根据id倒序
.fetch();//执行查询并获取结果集
}
在使用QueryDSL进行查询之前我们声明了EntityManager的注入以及JPAQueryFactory工厂对象的创建,通过@PostConstruct注解在类初始化的时候完成对JPAQueryFactory对象的实例化。
我们在queryAll方法内首先获取了对应UserBean的查询实体QUserBean,通过QUserBean内自动生成的字段获取,我们使用JPAQueryFactory工厂对象的selectFrom方法来简化查询,该方法代替了select&from两个方法,注意:也是仅限单表操作时可以使用。
而我们倒序的方式看起来就更简单了,这种实现方式完全就像是在编写原始的SQL一样,如果是根据asc的方式进行排序则可以修改为: orderBy(_Q_user.id.asc()),看起来是不是特别简单?
在一系列的条件都添加完成后,调用fetch方法执行我们的条件查询并且获取对应selectFrom查询实体的类型集合,要注意一点:这里如果selectFrom参数的实体类型不是UserBean那fetch方法返回集合的类型也不是List<UserBean>。
注意:在我们启动项目之前,我们需要修改pom.xml配置文件添加相关inject的依赖,SpringBoot内部并没有为我们提供该依赖。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
添加该依赖后才能完成JPAQueryFactory的实例化,下面我们启动下项目访问地址看界面输出内容如下图3所示:
图3
可以看到上图3的输出内容数据是完全按照我们的查询条件来执行的,我们打开控制台看看SpringDataJPA为我们自动生成的SQL
Hibernate:
select
userbean0_.t_id as t_id1_0_,
userbean0_.t_address as t_addres2_0_,
userbean0_.t_age as t_age3_0_,
userbean0_.t_name as t_name4_0_,
userbean0_.t_pwd as t_pwd5_0_
t_user userbean0_
order by
userbean0_.t_id desc
可以看到在SQL上面输出了Hibernate,StringDataJPA生成SQL这一块是使用的Hibernate,所以我们可以完全使用HQL的查询语言来编写JPA的查询。
根据主键查询单条数据
查询详情方法是我们常用到的查询之一,一般用于删除、更新。下面我们就来编写一个detail方法来看来QueryDSL是如何完成查询单挑数据的。
完全QueryDSL风格
代码如下所示:
/**
* 查询详情
* @param id 主键编号
* @return
@RequestMapping(value = "/detail/{id}")
public UserBean detail(@PathVariable("id") Long id)
//使用querydsl查询
QUserBean _Q_user = QUserBean.userBean;
//查询并返回结果集
return queryFactory
.selectFrom(_Q_user)//查询源
.where(_Q_user.id.eq(id))//指定查询具体id的数据
.fetchOne();//执行查询并返回单个结果
}
SpringDataJPA整合QueryDSL风格
代码如下所示:
/**
* SpringDataJPA & QueryDSL实现单数据查询
* @param id
* @return
@RequestMapping(value = "/detail_2/{id}")
public UserBean detail_2(@PathVariable("id") Long id)
//使用querydsl查询
QUserBean _Q_user = QUserBean.userBean;
//查询并返回指定id的单条数据
return userJPA.findOne(_Q_user.id.eq(id));
}
上面的两种风格都是可以根据id字段返回指定的单条数据,当然在上面的编写看来还是使用SpringDataJPA & QueryDSL要简单些,也只是简单的查询整合风格要比纯QueryDSL要简便,但是如果添加排序、模糊查询时还是纯QueryDSL编写更简单一些。
查询指定主键时,我们使用了where方法并且指定了id字段需要eq参数id,这个eq是QueryDSL内置的一个方法,用于查询指定值数据,当然其他字段也同样可以使用eq方法来完成条件查询,都是可以变通使用的。
重启项目后我们来看下查询详情的运行效果,如下图4所示:
图4
我们再来看下控制台输出的生成SQL,如下代码所示:
Hibernate:
select
userbean0_.t_id as t_id1_0_,
userbean0_.t_address as t_addres2_0_,
userbean0_.t_age as t_age3_0_,
userbean0_.t_name as t_name4_0_,
userbean0_.t_pwd as t_pwd5_0_
t_user userbean0_
where
userbean0_.t_id=?
可以看到是根据我们指定的字段来作为查询条件来检索的数据,我们通过fetchOne方法来返回一个结果。
根据名称模糊查询
下面我们来根据字段name完成模块查询,先来看下我们的查询条件代码如下:
/**
* 根据名称模糊查询
* @param name
* @return
@RequestMapping(value = "/likeQueryWithName")
public List<UserBean> likeQueryWithName(String name)
//使用querydsl查询
QUserBean _Q_user = QUserBean.userBean;
return queryFactory
.selectFrom(_Q_user)//查询源
.where(_Q_user.name.like(name))//根据name模糊查询
.fetch();//执行查询并返回结果集
}
可以看到我们where条件是根据name字段的like方法来完成的模糊查询,like方法也是QueryDSL内置的方法,我们只需要传入查询的内容就可以实现模糊查询,下面我们来运行访问看下界面输出内容如下图5所示:
图5
我们可以看到仅输出了name跟我们传入'admin'相关的数据,那我们看下控制台是怎么给我们生成的SQL:
Hibernate:
select
userbean0_.t_id as t_id1_0_,
userbean0_.t_address as t_addres2_0_,
userbean0_.t_age as t_age3_0_,