第七章:使用QueryDSL與SpringDataJPA實現(xiàn)子查詢

在上一章我們講到了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

作者個人 博客
使用開源框架 ApiBoot 助你成為Api接口服務(wù)架構(gòu)師

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內(nèi)容