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