第四章:使用QueryDSL與SpringDataJPA實(shí)現(xiàn)多表關(guān)聯(lián)查詢

對(duì)于業(yè)務(wù)邏輯復(fù)制的系統(tǒng)來(lái)說(shuō)都存在多表關(guān)聯(lián)查詢的情況,查詢的返回對(duì)象內(nèi)容也是根據(jù)具體業(yè)務(wù)來(lái)處理的刁品,我們本章主要是針對(duì)多表關(guān)聯(lián)根據(jù)條件查詢后返回單表對(duì)象,在下一章我們就會(huì)針對(duì)多表查詢返回自定義的對(duì)象實(shí)體。

本章目標(biāo)

基于SpringBoot框架平臺(tái)完成SpringDataJPA與QueryDSL多表關(guān)聯(lián)查詢返回單表對(duì)象實(shí)例蹦漠,查詢時(shí)完全采用QueryDSL語(yǔ)法進(jìn)行編寫。

構(gòu)建項(xiàng)目

我們使用idea工具先來(lái)創(chuàng)建一個(gè)SpringBoot項(xiàng)目车海,添加的依賴跟第三章:使用QueryDSL與SpringDataJPA完成Update&Delete一致笛园。為了方便分離文章源碼,我們創(chuàng)建完成后把第三章的application.yml配置文件以及pom.xml依賴內(nèi)容復(fù)制到本章項(xiàng)目中(配置內(nèi)容請(qǐng)參考第三章)侍芝。

創(chuàng)建數(shù)據(jù)表

我們先來(lái)根據(jù)一個(gè)簡(jiǎn)單的業(yè)務(wù)邏輯來(lái)創(chuàng)建兩張一對(duì)多關(guān)系的表研铆,下面我們先來(lái)創(chuàng)建商品類型信息表,代碼如下:

-- ----------------------------
-- 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;

接下來(lái)我們?cè)賮?lái)創(chuàng)建一個(gè)商品基本信息表州叠,表結(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 '商品標(biāo)題',
  `tg_price` decimal(8,2) DEFAULT NULL COMMENT '商品單價(jià)',
  `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 '類型外鍵編號(hào)',
  PRIMARY KEY (`tg_id`),
  KEY `tg_type_id` (`tg_type_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

創(chuàng)建實(shí)體

我們對(duì)應(yīng)上面兩張表的結(jié)構(gòu)創(chuàng)建兩個(gè)實(shí)體并添加對(duì)應(yīng)的SpringDataJPA注解配置棵红,商品類型實(shí)體如下所示:

package com.yuqiyu.querydsl.sample.chapter4.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/9
 * Time:15: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;
}

商品基本信息實(shí)體如下所示:

package com.yuqiyu.querydsl.sample.chapter4.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/9
 * Time:15: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;
    //商品標(biāo)題
    @Column(name = "tg_title")
    private String title;
    //商品價(jià)格
    @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;
}

我在商品表內(nèi)并沒有使用類型的實(shí)體作為表之間的關(guān)聯(lián)而是只用的具體類型編號(hào),有的時(shí)候也是根據(jù)你的需求來(lái)配置的咧栗,如果你每個(gè)商品讀取基本信息時(shí)都需要獲取商品的類型逆甜,那么這里配置@OneToOne還是比較省事,不需要你再操作多余的查詢楼熄。

構(gòu)建QueryDSL查詢實(shí)體

下面我們使用maven compile命令來(lái)自動(dòng)生成QueryDSL的查詢實(shí)體忆绰,我們?cè)趫?zhí)行命令的時(shí)候會(huì)自動(dòng)去pom.xml配置文件內(nèi)查找JPAAnnotationProcessor插件,如果你的實(shí)體配置了@Entity注解可岂,那么就會(huì)自動(dòng)生成查詢實(shí)體并將生成的實(shí)體放置到target/generated-sources/java內(nèi)错敢。
我們找到idea工具的Maven Projects窗口,如下圖1所示:

圖1

我們雙擊對(duì)應(yīng)的命令就可以執(zhí)行構(gòu)建項(xiàng)目了缕粹,構(gòu)建完成的查詢實(shí)體如下圖2所示:


圖2

如上圖2所示稚茅,QueryDSL在生成時(shí)會(huì)完全根據(jù)實(shí)體的包來(lái)對(duì)應(yīng)創(chuàng)建。

創(chuàng)建控制器

下面我們來(lái)創(chuàng)建一個(gè)控制器平斩,我們?cè)诳刂破鲀?nèi)直接編寫QueryDSL查詢代碼亚享,這里就不去根據(jù)MVC模式進(jìn)行編程了,在正式環(huán)境下還請(qǐng)大家按照MVC模式來(lái)編碼绘面。
控制器代碼如下所示:

package com.yuqiyu.querydsl.sample.chapter4.controller;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yuqiyu.querydsl.sample.chapter4.bean.GoodInfoBean;
import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodInfoBean;
import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodTypeBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
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/9
 * Time:15:24
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class GoodController
{

    @Autowired
    private EntityManager entityManager;

    //查詢工廠實(shí)體
    private JPAQueryFactory queryFactory;

    //實(shí)例化控制器完成后執(zhí)行該方法實(shí)例化JPAQueryFactory
    @PostConstruct
    public void initFactory()
    {
        System.out.println("開始實(shí)例化JPAQueryFactory");
        queryFactory = new JPAQueryFactory(entityManager);
    }

    @RequestMapping(value = "/selectByType")
    public List<GoodInfoBean> selectByType
            (
                    @RequestParam(value = "typeId") Long typeId //類型編號(hào)
            )
    {
        //商品查詢實(shí)體
        QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
        //商品類型查詢實(shí)體
        QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;
        return
                queryFactory
                .select(_Q_good)
                .from(_Q_good,_Q_good_type)
                .where(
                        //為兩個(gè)實(shí)體關(guān)聯(lián)查詢
                        _Q_good.typeId.eq(_Q_good_type.id)
                        .and(
                                //查詢指定typeid的商品
                                _Q_good_type.id.eq(typeId)
                        )
                )
                //根據(jù)排序字段倒序
                .orderBy(_Q_good.order.desc())
                //執(zhí)行查詢
                .fetch();
    }
}

可以看到上面的代碼欺税,我們查詢了兩張表侈沪,僅返回了商品信息內(nèi)的字段(select(_Q_good)),我們?cè)趙here條件內(nèi)進(jìn)行了這兩張表的關(guān)聯(lián),根據(jù)傳遞的類型編號(hào)作為關(guān)聯(lián)商品類型主鍵(相當(dāng)于left join)晚凿,最后根據(jù)排序字段進(jìn)行倒序亭罪。

運(yùn)行測(cè)試

下面我們來(lái)運(yùn)行項(xiàng)目,控制臺(tái)日志輸出內(nèi)容如下所示:

.....

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)

.....
開始實(shí)例化JPAQueryFactory
2017-07-09 15:40:38.454  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1184ab05: startup date [Sun Jul 09 15:40:36 CST 2017]; root of context hierarchy
2017-07-09 15:40:38.495  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/selectByType]}" onto public java.util.List<com.yuqiyu.querydsl.sample.chapter4.bean.GoodInfoBean> com.yuqiyu.querydsl.sample.chapter4.controller.GoodController.selectByType(java.lang.Long)
2017-07-09 15:40:38.498  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-07-09 15:40:38.498  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-07-09 15:40:38.515  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-07-09 15:40:38.515  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-07-09 15:40:38.536  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-07-09 15:40:38.721  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-07-09 15:40:38.723  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2017-07-09 15:40:38.726  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.alibaba.druid.pool:name=dataSource,type=DruidDataSource]
2017-07-09 15:40:38.765  INFO 11776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-07-09 15:40:38.769  INFO 11776 --- [           main] c.y.q.s.chapter4.Chapter4Application     : Started Chapter4Application in 3.027 seconds (JVM running for 3.683)

可以看到我們?cè)陧?xiàng)目啟動(dòng)的時(shí)候JPAQueryFactory查詢工廠對(duì)象就被實(shí)例了歼秽,接下來(lái)我們直接使用JPAQueryFactory實(shí)例對(duì)象就Ok了应役。下面我們來(lái)訪問(wèn) : http://127.0.0.1:8080/selectByType?typeId=1
界面輸出內(nèi)容如下圖3所示:

圖3

下面我們來(lái)看下控制臺(tái)輸出的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_ cross 
    join
        good_types goodtypebe1_ 
    where
        goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id 
        and goodtypebe1_.tgt_id=? 
    order by
        goodinfobe0_.tg_order desc

QueryDSL自動(dòng)生成的SQL采用了Cross Join 獲取兩張表的《笛卡爾集》然后根據(jù)select內(nèi)配置的實(shí)體進(jìn)行返回字段燥筷,我們使用 where goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id 代替了on goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id實(shí)現(xiàn)了相同的效果箩祥。

總結(jié)

本章的內(nèi)容比較簡(jiǎn)單,我們使用QueryDSL完成了兩個(gè)實(shí)體關(guān)聯(lián)查詢并返回單實(shí)體實(shí)例的方法肆氓,QueryDSL內(nèi)也有LeftJoin袍祖、InnerJoin等關(guān)聯(lián)查詢不過(guò)都是基于具體實(shí)體類型來(lái)完成的,本章就不做解釋了做院,用起來(lái)比較繁瑣復(fù)雜它們遵循的是HQL語(yǔ)法盲泛。

本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市键耕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柑营,老刑警劉巖屈雄,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異官套,居然都是意外死亡酒奶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門奶赔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惋嚎,“玉大人,你說(shuō)我怎么就攤上這事站刑×砦椋” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵绞旅,是天一觀的道長(zhǎng)摆尝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)因悲,這世上最難降的妖魔是什么堕汞? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮晃琳,結(jié)果婚禮上讯检,老公的妹妹穿的比我還像新娘琐鲁。我一直安慰自己,他們只是感情好人灼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布围段。 她就那樣靜靜地躺著,像睡著了一般挡毅。 火紅的嫁衣襯著肌膚如雪蒜撮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天跪呈,我揣著相機(jī)與錄音段磨,去河邊找鬼。 笑死耗绿,一個(gè)胖子當(dāng)著我的面吹牛苹支,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播误阻,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼债蜜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了究反?” 一聲冷哼從身側(cè)響起寻定,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎精耐,沒想到半個(gè)月后狼速,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卦停,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年向胡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊完。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡僵芹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出小槐,到底是詐尸還是另有隱情拇派,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布本股,位于F島的核電站攀痊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拄显。R本人自食惡果不足惜苟径,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躬审。 院中可真熱鬧棘街,春花似錦蟆盐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至险污,卻和暖如春痹愚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛔糯。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工拯腮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚁飒。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓动壤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親淮逻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琼懊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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