在上一章我們講到了QueryDSL的聚合函數(shù),讓我們重新認識了QueryDSL的便利之處,它可以很好的使用原生SQL的思想來進行Java形式的描述,編寫完成也不需要考慮更換數(shù)據(jù)庫存在的不兼容問題。當然QueryDSL還有很多我們沒有發(fā)掘出來的核心技術(shù),我們今天來講解下”子查詢“,看看QueryDSL是怎么完美的詮釋了使用Java寫SQL。
本章目標
基于SpringBoot平臺完成QueryDSL整合JPA實現(xiàn)多表、單表子查詢。
構(gòu)建項目
我們使用idea工具創(chuàng)建一個SpringBoot項目,然后添加部分依賴并配置QueryDSL自動生成QueryBean插件,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>chapter7</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>chapter7</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>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</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>
我們從第一章到本章pom.xml內(nèi)容幾乎沒有變動,所以有之前章節(jié)學習的小伙伴可以直接拿過來使用。
下面我們需要創(chuàng)建兩表,當然為了方便我們直接使用第四章內(nèi)的表結(jié)構(gòu),
商品信息表
-- ----------------------------
-- Table structure for good_infos
-- ----------------------------
DROP TABLE IF EXISTS `good_infos`;
CREATE TABLE `good_infos` (
`tg_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
`tg_title` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品標題',
`tg_price` decimal(8,2) DEFAULT NULL COMMENT '商品單價',
`tg_unit` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '單位',
`tg_order` varchar(255) DEFAULT NULL COMMENT '排序',
`tg_type_id` int(11) DEFAULT NULL COMMENT '類型外鍵編號',
PRIMARY KEY (`tg_id`),
KEY `tg_type_id` (`tg_type_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
商品類型表
-- ----------------------------
-- Table structure for good_types
-- ----------------------------
DROP TABLE IF EXISTS `good_types`;
CREATE TABLE `good_types` (
`tgt_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
`tgt_name` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '類型名稱',
`tgt_is_show` char(1) DEFAULT NULL COMMENT '是否顯示',
`tgt_order` int(2) DEFAULT NULL COMMENT '類型排序',
PRIMARY KEY (`tgt_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
創(chuàng)建實體
我們對應(yīng)上面兩張表的結(jié)構(gòu)創(chuàng)建兩個實體并添加對應(yīng)的SpringDataJPA注解配置,如下所示:
商品類型實體
package com.yuqiyu.querydsl.sample.chapter7.bean;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/14
* Time:10:04
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Entity
@Table(name = "good_types")
@Data
public class GoodTypeBean
implements Serializable
{
//主鍵
@Id
@GeneratedValue
@Column(name = "tgt_id")
private Long id;
//類型名稱
@Column(name = "tgt_name")
private String name;
//是否顯示
@Column(name = "tgt_is_show")
private int isShow;
//排序
@Column(name = "tgt_order")
private int order;
}
商品實體
package com.yuqiyu.querydsl.sample.chapter7.bean;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/14
* Time:10:08
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Entity
@Table(name = "good_infos")
@Data
public class GoodInfoBean
implements Serializable
{
//主鍵
@Id
@GeneratedValue
@Column(name = "tg_id")
private Long id;
//商品標題
@Column(name = "tg_title")
private String title;
//商品價格
@Column(name = "tg_price")
private double price;
//商品單位
@Column(name = "tg_unit")
private String unit;
//商品排序
@Column(name = "tg_order")
private int order;
//類型外鍵
@Column(name = "tg_type_id")
private Long typeId;
}
創(chuàng)建控制器
接下來我們創(chuàng)建一個商品控制器用來我們本章內(nèi)容的講解,在控制器初始化時我們需要實例化JPAQueryFactory對象,在實例化之前需要注入EntityManager對象,代碼如下所示:
package com.yuqiyu.querydsl.sample.chapter7.controller;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.List;
/**
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/7/14
* Time:9:30
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@RestController
public class GoodController
{
//實體管理對象
@Autowired
private EntityManager entityManager;
//jpa查詢工廠對象
private JPAQueryFactory queryFactory;
@PostConstruct
public void init()
{
queryFactory = new JPAQueryFactory(entityManager);
}
}
模糊查詢
我們現(xiàn)在有個需求需要查詢出商品類型名稱包含蔬菜的商品列表,在原生SQL內(nèi)也有多種方式可以實現(xiàn)如:子查詢、關(guān)聯(lián)查詢等。我們在QueryDSL內(nèi)也是一樣的,我們就拿子查詢來處理這個需求吧,方法代碼如下所示:
/**
* 子查詢 模糊查詢
* @return
*/
@RequestMapping(value = "/childLikeSelect")
public List<GoodInfoBean> childLikeSelect()
{
//商品基本信息查詢實體
QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
//商品類型查詢實體
QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;
return queryFactory
.selectFrom(_Q_good)//查詢商品基本信息表
.where(
//查詢類型名稱包含“蔬菜”
_Q_good.typeId.in(
JPAExpressions.select(
_Q_good_type.id
)
.from(_Q_good_type)
.where(_Q_good_type.name.like("%蔬菜%"))
)
).fetch();
}
我們上面的代碼查詢了商品表內(nèi)的全部信息并且根據(jù)類型編號使用了"in"方法來實現(xiàn)子查詢,子查詢是查詢的商品類型表內(nèi)的信息并且類型的名稱包含“蔬菜”,不過子查詢僅僅返回了商品類型的編號。
我們來啟動下項目測試我們這個方法是否是我們預(yù)期的效果查詢出商品類型名稱包含”蔬菜“兩個字的列表。
項目啟動控制臺輸出日志出現(xiàn)Tomcat started on port(s): 8080 (http),表示已經(jīng)啟動成功,日志如下所示:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.4.RELEASE)
......
2017-07-14 10:15:22.255 INFO 11884 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-07-14 10:15:22.259 INFO 11884 --- [ main] c.y.q.s.chapter7.Chapter7Application : Started Chapter7Application in 2.941 seconds (JVM running for 3.578)
2017-07-14 10:15:26.086 INFO 11884 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-14 10:15:26.086 INFO 11884 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-14 10:15:26.104 INFO 11884 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms
2017-07-14 10:15:26.215 INFO 11884 --- [nio-8080-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
訪問我們配置的地址http://127.0.0.1:8080/childLikeSelect查看界面輸出內(nèi)容如下:
[
{
"id": 2,
"title": "油菜",
"price": 12.6,
"unit": "斤",
"order": 2,
"typeId": 1
}
]
我們看到數(shù)據(jù)返回”油菜“對應(yīng)的商品類型編號是"1",對應(yīng)數(shù)據(jù)庫的類型是”綠色蔬菜“,這證明了我們的編碼跟返回的數(shù)據(jù)是一致的,那么接下來我們來看下QueryDSL為我們自動生成的SQL,如下所示:
Hibernate:
select
goodinfobe0_.tg_id as tg_id1_0_,
goodinfobe0_.tg_order as tg_order2_0_,
goodinfobe0_.tg_price as tg_price3_0_,
goodinfobe0_.tg_title as tg_title4_0_,
goodinfobe0_.tg_type_id as tg_type_5_0_,
goodinfobe0_.tg_unit as tg_unit6_0_
from
good_infos goodinfobe0_
where
goodinfobe0_.tg_type_id in (
select
goodtypebe1_.tgt_id
from
good_types goodtypebe1_
where
goodtypebe1_.tgt_name like ? escape '!'
)
價格最高的商品列表
我們又有了新的需求,需要查詢出價格最高的商品列表,代碼如下所示:
/**
* 子查詢 價格最高的商品列表
* @return
*/
@RequestMapping(value = "/childEqSelect")
public List<GoodInfoBean> childEqSelect()
{
//商品基本信息查詢實體
QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
return queryFactory
.selectFrom(_Q_good)
//查詢價格最大的商品列表
.where(_Q_good.price.eq(
JPAExpressions.select(
_Q_good.price.max()
)
.from(_Q_good)
))
.fetch();
}
我們使用JPAExpressions創(chuàng)建一個子查詢,查詢出商品表內(nèi)最大商品價格作為父查詢的查詢條件。
重啟項目后訪問地址http://127.0.0.1:8080/childEqSelect,接口返回內(nèi)容如下所示:
[
{
"id": 4,
"title": "秋葵",
"price": 22.6,
"unit": "斤",
"order": 3,
"typeId": 1
}
]
我們數(shù)據(jù)庫內(nèi)有三條數(shù)據(jù),價格最高的也就是名為“秋葵”的商品了。下面我們再來看下控制臺輸出的SQL如下所示:
Hibernate:
select
goodinfobe0_.tg_id as tg_id1_0_,
goodinfobe0_.tg_order as tg_order2_0_,
goodinfobe0_.tg_price as tg_price3_0_,
goodinfobe0_.tg_title as tg_title4_0_,
goodinfobe0_.tg_type_id as tg_type_5_0_,
goodinfobe0_.tg_unit as tg_unit6_0_
from
good_infos goodinfobe0_
where
goodinfobe0_.tg_price=(
select
max(goodinfobe1_.tg_price)
from
good_infos goodinfobe1_
)
價格高于平均價格的商品列表
現(xiàn)在我們需要查詢高于平均價格的商品列表,那我們該怎么編寫呢?代碼如下所示:
/**
* 子查詢 價格高于平均價格的商品列表
* @return
*/
@RequestMapping(value = "/childGtAvgSelect")
public List<GoodInfoBean> childGtAvgSelect()
{
//商品基本信息查詢實體
QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
return queryFactory
.selectFrom(_Q_good)
//查詢價格高于平均價的商品列表
.where(
_Q_good.price.gt(
JPAExpressions.select(_Q_good.price.avg())
.from(_Q_good)
)
).fetch();
}
我們使用JPAExpressions來創(chuàng)建一個子查詢并且返回商品表內(nèi)價格平均值,查詢到的值作為父查詢的查詢條件。
接下來我們重啟項目后訪問地址http://127.0.0.1:8080/childGtAvgSelect,接口返回的內(nèi)容如下所示:
[
{
"id": 1,
"title": "金針菇",
"price": 5.5,
"unit": "斤",
"order": 1,
"typeId": 3
},
{
"id": 2,
"title": "油菜",
"price": 12.6,
"unit": "斤",
"order": 2,
"typeId": 1
}
]
我們再來看下控制臺輸出的生成SQL內(nèi)容如下所示:
Hibernate:
select
goodinfobe0_.tg_id as tg_id1_0_,
goodinfobe0_.tg_order as tg_order2_0_,
goodinfobe0_.tg_price as tg_price3_0_,
goodinfobe0_.tg_title as tg_title4_0_,
goodinfobe0_.tg_type_id as tg_type_5_0_,
goodinfobe0_.tg_unit as tg_unit6_0_
from
good_infos goodinfobe0_
where
goodinfobe0_.tg_price<(
select
avg(goodinfobe1_.tg_price)
from
good_infos goodinfobe1_
)
我們可以看到生成的SQL完全是按照我們預(yù)期來創(chuàng)建的。
總結(jié)
以上內(nèi)容就是本章的全部內(nèi)容,我們使用三個簡單的例子來講述了QueryDSL子查詢,QueryDSL完美的將原生的SQL編寫方式轉(zhuǎn)移到了Java程序內(nèi),內(nèi)置了幾乎所有的原生SQL的函數(shù)、關(guān)鍵字、語法等。
本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter