在企業(yè)開發(fā)中ORM框架有很多種如:Hibernate,Mybatis,JdbcTemplate等。每一種框架的設(shè)計(jì)理念是不一樣的,Hibernate跟我們本章講解的SpringDataJPA是一致的框架都是全自動理念作為設(shè)計(jì)核心,讓用戶更少的去寫SQL語句通過簡單的配置就可以實(shí)現(xiàn)各種查詢。而Mybatis框架則是半自動理念作為設(shè)計(jì)核心,SQL讓用戶自己定義實(shí)現(xiàn)了更好的靈活性。
本章目標(biāo)
本章我們目標(biāo)實(shí)現(xiàn)QueryDSL通用查詢語言整合SpringDataJPA完成單表的查詢多樣化。
構(gòu)建項(xiàng)目
下面我們先來創(chuàng)建一個(gè)SpringBoot項(xiàng)目,具體如何使用Maven整合QueryDSL請?jiān)L問QueryDSL學(xué)習(xí)目錄第一章,創(chuàng)建項(xiàng)目時(shí)的依賴也與第一章一致,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>
<!--阿里巴巴數(shù)據(jù)庫連接池,專為監(jiān)控而生 -->
<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>
上圖代碼塊有關(guān)配置如有不明白的地方請查看第一章:Maven環(huán)境下如何配置QueryDSL環(huán)境。
構(gòu)建數(shù)據(jù)庫信息表
我們先來創(chuàng)建一張普通信息表,創(chuàng)建表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;
測試數(shù)據(jù)這里就不添加了。
添加application.yml配置文件
在resource目錄下我們添加application.yml配置文件來代替application.properties文件,添加對應(yīng)的數(shù)據(jù)庫連接池的配置信息,代碼如下所示:
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
jpa:
properties:
hibernate:
show_sql: true
format_sql: true
創(chuàng)建實(shí)體
我們根據(jù)數(shù)據(jù)庫內(nèi)對應(yīng)的字段創(chuàng)建一個(gè)實(shí)體類并添加對應(yīng)的SpringDataJPA的注解,實(shí)體類代碼如下所示:
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
{
@Id
@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;
}
實(shí)體內(nèi)有個(gè)注解@Data比較特殊,之前也許大家沒有使用過,當(dāng)然你們肯定發(fā)現(xiàn)了我這個(gè)實(shí)體類內(nèi)并沒有對應(yīng)字段的Getter/Setter方法,如果沒有添加@Data注解在SpringDataJPA映射數(shù)據(jù)時(shí)會出現(xiàn)找不到對應(yīng)字段的Setter方法,導(dǎo)致無法完成數(shù)據(jù)映射到實(shí)體的異常!
在上面的實(shí)體源碼中可以看到@Data注解是在lombok包內(nèi),lombok其實(shí)是一個(gè)優(yōu)雅的第三方插件,它可以讓你的實(shí)體變得簡潔,可讀性也大大的得到了提升。在使用這個(gè)插件的時(shí)候需要你們Idea開發(fā)工具支持,必填安裝相應(yīng)的Plugin才可以,這里我就不多說相關(guān)lombok的配置問題了,大家在跟本章聯(lián)系的時(shí)候可以使用Getter/Setter方法的形式代替@Data。
創(chuàng)建基類JPA
這里我們簡單的封裝下JPA,我們添加一個(gè)接口去繼承我們需要的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自動實(shí)例化才添加的。
創(chuàng)建邏輯JPA
接下來我們開始創(chuàng)建對應(yīng)User模塊的數(shù)據(jù)邏輯接口JPA,很簡單,我們只需要創(chuàng)建一個(gè)接口繼承下我們的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的時(shí)候用到了泛型,因?yàn)槲覀冊贐aseJPA內(nèi)所繼承的接口都需要我們傳遞一個(gè)具體的實(shí)體類的類型,所以這塊我們采用了泛型來處理,只有具體邏輯JPA繼承BaseJPA的時(shí)候傳遞具體的實(shí)體類型就可以了。
自動生成Q結(jié)構(gòu)查詢實(shí)體
我們之前說過了QueryDSL很神奇的地方就在于它是一個(gè)可通過Maven插件自動生成實(shí)體類型的結(jié)構(gòu)查詢實(shí)體,那么我們接下來使用maven compile命令來讓我們配置的JPAAnnotationProcessor去做他自己的使命。
idea工具為我們的maven項(xiàng)目自帶了比較全面的命令,我們直接使用就可以了,如下圖1所示:

雙擊紅色框內(nèi)的命令就可以執(zhí)行了,在這之前如果你想使用本地的maven環(huán)境需要在Setting->Build Tools ->maven頁面修改home dirctory。命令執(zhí)行完成后我們可以看到target目錄自動生成了并且為我們創(chuàng)建了一些目錄,展開目錄后可以看到QueryDSL為我們自動生成的查詢實(shí)體,如下圖2所示:

maven插件會為我們自動創(chuàng)建一堆目錄,我們的查詢實(shí)體的位置是以我們pom.xml配置文件內(nèi)配置的目錄為準(zhǔn)。打開自動創(chuàng)建的實(shí)體后可以看到QueryDSL自動為我們創(chuàng)建的查詢字段以及構(gòu)造函數(shù),具體查詢字段的含義后面會有所講解。
編寫查詢
下面我們開始編寫查詢,在編寫之前我們創(chuàng)建一個(gè)控制器用于查看我們查詢到的數(shù)據(jù),代碼如下所示:
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三層的設(shè)計(jì)理念,因?yàn)檫@只是文章的測試Sample編寫,大家在實(shí)際開發(fā)項(xiàng)目中還是需要按照MVC設(shè)計(jì)模式來進(jìn)行設(shè)計(jì)架構(gòu)。
查詢?nèi)繑?shù)據(jù)并排序
我們先來一個(gè)簡單的查詢表內(nèi)的所有數(shù)據(jù)并根據(jù)主鍵進(jìn)行倒序,代碼如下所示:
@RestController
@RequestMapping(value = "/user")
public class UserController
{
@Autowired
private UserJPA usrJPA;
//----以下是新添加內(nèi)容
//實(shí)體管理者
@Autowired
private EntityManager entityManager;
//JPA查詢工廠
private JPAQueryFactory queryFactory;
@PostConstruct
public void initFactory()
{
queryFactory = new JPAQueryFactory(entityManager);
System.out.println("init JPAQueryFactory successfully");
}
/**
* 查詢?nèi)繑?shù)據(jù)并根據(jù)id倒序
* @return
*/
@RequestMapping(value = "/queryAll")
public List<UserBean> queryAll()
{
//使用querydsl查詢
QUserBean _Q_user = QUserBean.userBean;
//查詢并返回結(jié)果集
return queryFactory
.selectFrom(_Q_user)//查詢源
.orderBy(_Q_user.id.desc())//根據(jù)id倒序
.fetch();//執(zhí)行查詢并獲取結(jié)果集
}
在使用QueryDSL進(jìn)行查詢之前我們聲明了EntityManager的注入以及JPAQueryFactory工廠對象的創(chuàng)建,通過@PostConstruct注解在類初始化的時(shí)候完成對JPAQueryFactory對象的實(shí)例化。
我們在queryAll方法內(nèi)首先獲取了對應(yīng)UserBean的查詢實(shí)體QUserBean,通過QUserBean內(nèi)自動生成的字段獲取,我們使用JPAQueryFactory工廠對象的selectFrom方法來簡化查詢,該方法代替了select&from兩個(gè)方法,注意:也是僅限單表操作時(shí)可以使用。
而我們倒序的方式看起來就更簡單了,這種實(shí)現(xiàn)方式完全就像是在編寫原始的SQL一樣,如果是根據(jù)asc的方式進(jìn)行排序則可以修改為: orderBy(_Q_user.id.asc()),看起來是不是特別簡單?
在一系列的條件都添加完成后,調(diào)用fetch方法執(zhí)行我們的條件查詢并且獲取對應(yīng)selectFrom查詢實(shí)體的類型集合,要注意一點(diǎn):這里如果selectFrom參數(shù)的實(shí)體類型不是UserBean那fetch方法返回集合的類型也不是List<UserBean>。
注意:在我們啟動項(xiàng)目之前,我們需要修改pom.xml配置文件添加相關(guān)inject的依賴,SpringBoot內(nèi)部并沒有為我們提供該依賴。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
添加該依賴后才能完成JPAQueryFactory的實(shí)例化,下面我們啟動下項(xiàng)目訪問地址看界面輸出內(nèi)容如下圖3所示:

可以看到上圖3的輸出內(nèi)容數(shù)據(jù)是完全按照我們的查詢條件來執(zhí)行的,我們打開控制臺看看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_
from
t_user userbean0_
order by
userbean0_.t_id desc
可以看到在SQL上面輸出了Hibernate,StringDataJPA生成SQL這一塊是使用的Hibernate,所以我們可以完全使用HQL的查詢語言來編寫JPA的查詢。
根據(jù)主鍵查詢單條數(shù)據(jù)
查詢詳情方法是我們常用到的查詢之一,一般用于刪除、更新。下面我們就來編寫一個(gè)detail方法來看來QueryDSL是如何完成查詢單挑數(shù)據(jù)的。
完全QueryDSL風(fēng)格
代碼如下所示:
/**
* 查詢詳情
* @param id 主鍵編號
* @return
*/
@RequestMapping(value = "/detail/{id}")
public UserBean detail(@PathVariable("id") Long id)
{
//使用querydsl查詢
QUserBean _Q_user = QUserBean.userBean;
//查詢并返回結(jié)果集
return queryFactory
.selectFrom(_Q_user)//查詢源
.where(_Q_user.id.eq(id))//指定查詢具體id的數(shù)據(jù)
.fetchOne();//執(zhí)行查詢并返回單個(gè)結(jié)果
}
SpringDataJPA整合QueryDSL風(fēng)格
代碼如下所示:
/**
* SpringDataJPA & QueryDSL實(shí)現(xiàn)單數(shù)據(jù)查詢
* @param id
* @return
*/
@RequestMapping(value = "/detail_2/{id}")
public UserBean detail_2(@PathVariable("id") Long id)
{
//使用querydsl查詢
QUserBean _Q_user = QUserBean.userBean;
//查詢并返回指定id的單條數(shù)據(jù)
return userJPA.findOne(_Q_user.id.eq(id));
}
上面的兩種風(fēng)格都是可以根據(jù)id字段返回指定的單條數(shù)據(jù),當(dāng)然在上面的編寫看來還是使用SpringDataJPA & QueryDSL要簡單些,也只是簡單的查詢整合風(fēng)格要比純QueryDSL要簡便,但是如果添加排序、模糊查詢時(shí)還是純QueryDSL編寫更簡單一些。
查詢指定主鍵時(shí),我們使用了where方法并且指定了id字段需要eq參數(shù)id,這個(gè)eq是QueryDSL內(nèi)置的一個(gè)方法,用于查詢指定值數(shù)據(jù),當(dāng)然其他字段也同樣可以使用eq方法來完成條件查詢,都是可以變通使用的。
重啟項(xiàng)目后我們來看下查詢詳情的運(yùn)行效果,如下圖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_
from
t_user userbean0_
where
userbean0_.t_id=?
可以看到是根據(jù)我們指定的字段來作為查詢條件來檢索的數(shù)據(jù),我們通過fetchOne方法來返回一個(gè)結(jié)果。
根據(jù)名稱模糊查詢
下面我們來根據(jù)字段name完成模塊查詢,先來看下我們的查詢條件代碼如下:
/**
* 根據(jù)名稱模糊查詢
* @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))//根據(jù)name模糊查詢
.fetch();//執(zhí)行查詢并返回結(jié)果集
}
可以看到我們where條件是根據(jù)name字段的like方法來完成的模糊查詢,like方法也是QueryDSL內(nèi)置的方法,我們只需要傳入查詢的內(nèi)容就可以實(shí)現(xiàn)模糊查詢,下面我們來運(yùn)行訪問看下界面輸出內(nèi)容如下圖5所示:

我們可以看到僅輸出了name跟我們傳入'admin'相關(guān)的數(shù)據(jù),那我們看下控制臺是怎么給我們生成的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_
from
t_user userbean0_
where
userbean0_.t_name like ? escape '!'
也是完全按照我們指定的模糊查詢字段生成的,到目前可以看到QueryDSL為我們減少了太多了查詢繁瑣的事情,讓我們能夠更好的投入到業(yè)務(wù)邏輯處理中。
總結(jié)
以上內(nèi)容就是本章的全部講解,本章主要講述了SpringDataJPA整合QueryDSL后完全使用QueryDSL來進(jìn)行單表的查詢,使用QueryDSL可以完全按照編寫原始SQL的思想來編寫查詢條件,容易上手,方便快捷。由于QueryDSL的功能強(qiáng)大,有很多東西沒有講解到,希望大家可以在后期的項(xiàng)目中得到總結(jié)。
本章源碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter