springJpa 自定義參數查詢


創建表結構

跟上一章一樣,我們還是使用商品信息表、商品類型表來完成編碼。

商品信息表

-- ---------------------------- -- 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; -- ---------------------------- -- Records of good_infos -- ---------------------------- INSERT INTO `good_infos` VALUES ('1', '金針菇', '5.50', '斤', '1', '3'); INSERT INTO `good_infos` VALUES ('2', '油菜', '12.60', '斤', '2', '1'); 

商品類型信息表

-- ---------------------------- -- 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; -- ---------------------------- -- Records of good_types -- ---------------------------- INSERT INTO `good_types` VALUES ('1', '綠色蔬菜', '1', '1'); INSERT INTO `good_types` VALUES ('2', '根莖類', '1', '2'); INSERT INTO `good_types` VALUES ('3', '菌類', '1', '3'); 

創建實體

我們對應表結構創建實體並且添加對應的SpringDataJPA注解。

商品實體

package com.yuqiyu.querydsl.sample.chapter5.bean; import lombok.Data; import javax.persistence.*; import java.io.Serializable; /** * 商品基本信息實體 * ======================== * Created with IntelliJ IDEA. * User:恆宇少年 * Date:2017/7/10 * Time:22:39 * 碼雲:http://git.oschina.net/jnyqy * ======================== */ @Entity @Table(name = "good_infos") @Data public class GoodInfoBean implements Serializable { //主鍵 @Id @Column(name = "tg_id") @GeneratedValue 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; } 

商品類型實體

package com.yuqiyu.querydsl.sample.chapter5.bean; import lombok.Data; import javax.persistence.*; import java.io.Serializable; /** * 商品類別實體 * ======================== * Created with IntelliJ IDEA. * User:恆宇少年 * Date:2017/7/10 * Time:22:39 * 碼雲:http://git.oschina.net/jnyqy * ======================== */ 

上面實體內的注解@Entity標識該實體被SpringDataJPA所管理,@Table標識該實體對應的數據庫內的表信息,@Data該注解則是lombok內的合並注解,根據idea工具的插件自動添加getter/setter、toString、全參構造函數等。

創建DTO

我們創建一個查詢返回的自定義對象,對象內的字段包含了商品實體、商品類型實體內的部分內容,DTO代碼如下所示:

package com.yuqiyu.querydsl.sample.chapter5.dto; import lombok.Data; import java.io.Serializable; /** * 商品dto * ======================== * Created with IntelliJ IDEA. * User:恆宇少年 * Date:2017/7/10 * Time:22:39 * 碼雲:http://git.oschina.net/jnyqy * ======================== */ 

要注意我們的自定義返回的對象僅僅只是一個實體,並不對應數據庫內的表,所以這里不需要配置@Entity、@Table等JPA注解,僅把@Data注解配置上就可以了,接下來我們編譯下項目讓QueryDSL插件自動生成查詢實體。

生成查詢實體

idea工具為maven project自動添加了對應的功能,我們打開右側的Maven Projects,如下圖1所示:

 
圖1

我們雙擊compile命令執行,執行完成后會在我們pom.xml配置文件內配置生成目錄內生成對應實體的QueryDSL查詢實體。生成的查詢實體如下圖2所示:

 
圖2

QueryDSL配置JPA插件僅會根據@Entity進行生成查詢實體

創建控制器

我們來創建一個測試的控制器讀取商品表內的所有商品,在編寫具體的查詢方法之前我們需要實例化EntityManager對象以及JPAQueryFactory對象,並且通過實例化控制器時就去實例化JPAQueryFactory對象。控制器代碼如下所示:

package com.yuqiyu.querydsl.sample.chapter5.controller; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import com.yuqiyu.querydsl.sample.chapter5.bean.QGoodInfoBean; import com.yuqiyu.querydsl.sample.chapter5.bean.QGoodTypeBean; import com.yuqiyu.querydsl.sample.chapter5.dto.GoodDTO; 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; import java.util.stream.Collectors; /** * 多表查詢返回商品dto控制器 * ======================== * Created with IntelliJ IDEA. * User:恆宇少年 * Date:2017/7/10 * Time:23:04 * 碼雲:http://git.oschina.net/jnyqy * ======================== */ @RestController public class GoodController { //實體管理 @Autowired private EntityManager entityManager; //查詢工廠 private JPAQueryFactory queryFactory; //初始化查詢工廠 @PostConstruct public void init() { queryFactory = new JPAQueryFactory(entityManager); } } 

可以看到我們配置的是一個@RestController該控制器返回的數據都是Json字符串,這也是RestController所遵循的規則。

QueryDSL & Projections

下面我們開始編寫完全基於QueryDSL形式的返回自定義對象方法,代碼如下所示:

 /** * 根據QueryDSL查詢 * @return */ @RequestMapping(value = "/selectWithQueryDSL") public List<GoodDTO> selectWithQueryDSL() { //商品基本信息 QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean; //商品類型 QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean; return queryFactory .select( Projections.bean( GoodDTO.class,//返回自定義實體的類型 _Q_good.id, _Q_good.price, _Q_good.title, _Q_good.unit, _Q_good_type.name.as("typeName"),//使用別名對應dto內的typeName _Q_good_type.id.as("typeId")//使用別名對應dto內的typeId ) ) .from(_Q_good,_Q_good_type)//構建兩表笛卡爾集 .where(_Q_good.typeId.eq(_Q_good_type.id))//關聯兩表 .orderBy(_Q_good.order.desc())//倒序 .fetch(); } 

我們可以看到上面selectWithQueryDSL()查詢方法,里面出現了一個新的類型Projections,這個類型是QueryDSL內置針對處理自定義返回結果集的解決方案,里面包含了構造函數、實體、字段等處理方法,我們今天主要講解下實體。

JPAQueryFactory工廠select方法可以將Projections方法返回的QBean作為參數,我們通過Projections的bean方法來構建返回的結果集映射到實體內,有點像Mybatis內的ResultMap的形式,不過內部處理機制肯定是有着巨大差別的!bean方法第一個參數需要傳遞一個實體的泛型類型作為返回集合內的單個對象類型,如果QueryDSL查詢實體內的字段與DTO實體的字段名字不一樣時,我們就可以采用as方法來處理,為查詢的結果集指定的字段添加別名,這樣就會自動映射到DTO實體內。

運行測試

下面我們來運行下項目,訪問地址:http://127.0.0.1:8080/selectWithQueryDSL查看界面輸出的效果如下代碼塊所示:

[ { "id": 2, "title": "油菜", "unit": "斤", "price": 12.6, "typeName": "綠色蔬菜", "typeId": 1 }, { "id": 1, "title": "金針菇", "unit": "斤", "price": 5.5, "typeName": "菌類", "typeId": 3 } ] 

我們可以看到輸出的Json數組字符串就是我們DTO內的所有字段反序列后的效果,DTO實體內對應的typeName、typeId都已經查詢出並且賦值。
下面我們來查看控制台輸出自動生成的SQL,如下代碼塊所示:

Hibernate: select goodinfobe0_.tg_id as col_0_0_, goodinfobe0_.tg_price as col_1_0_, goodinfobe0_.tg_title as col_2_0_, goodinfobe0_.tg_unit as col_3_0_, goodtypebe1_.tgt_name as col_4_0_, goodtypebe1_.tgt_id as col_5_0_ from good_infos goodinfobe0_ cross join good_types goodtypebe1_ where goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id order by goodinfobe0_.tg_order desc 

生成的SQL是cross join形式關聯查詢,關聯 形式通過where goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id 代替了on goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id,最終查詢結果集返回數據這兩種方式一致。

QueryDSL & Collection

下面我們采用java8新特性返回自定義結果集,我們查詢仍然采用QueryDSL形式,方法代碼如下所示:

 /** * 使用java8新特性Collection內stream方法轉換dto * @return */ @RequestMapping(value = "/selectWithStream") public List<GoodDTO> selectWithStream() { //商品基本信息 QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean; //商品類型 QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean; return queryFactory .select( _Q_good.id, _Q_good.price, _Q_good.title, _Q_good.unit, _Q_good_type.name, _Q_good_type.id ) .from(_Q_good,_Q_good_type)//構建兩表笛卡爾集 .where(_Q_good.typeId.eq(_Q_good_type.id))//關聯兩表 .orderBy(_Q_good.order.desc())//倒序 .fetch() .stream() //轉換集合內的數據 .map(tuple -> { //創建商品dto GoodDTO dto = new GoodDTO(); //設置商品編號 dto.setId(tuple.get(_Q_good.id)); //設置商品價格 dto.setPrice(tuple.get(_Q_good.price)); //設置商品標題 dto.setTitle(tuple.get(_Q_good.title)); //設置單位 dto.setUnit(tuple.get(_Q_good.unit)); //設置類型編號 dto.setTypeId(tuple.get(_Q_good_type.id)); //設置類型名稱 dto.setTypeName(tuple.get(_Q_good_type.name)); //返回本次構建的dto return dto; }) //返回集合並且轉換為List<GoodDTO> .collect(Collectors.toList()); } 

從方法開始到fetch()結束完全跟QueryDSL沒有任何區別,采用了最原始的方式進行返回結果集,但是從fetch()獲取到結果集后我們處理的方式就有所改變了,fetch()方法返回的類型是泛型List(List<T>),List繼承了Collection,完全存在使用Collection內非私有方法的權限,通過調用stream方法可以將集合轉換成Stream<E>泛型對象,該對象的map方法可以操作集合內單個對象的轉換,具體的轉換代碼可以根據業務邏輯進行編寫。
在map方法內有個lambda表達式參數tuple,我們通過tuple對象get方法就可以獲取對應select方法內的查詢字段。

tuple只能獲取select內存在的字段,如果select內為一個實體對象,tuple無法獲取指定字段的值。

運行測試

下面我們重啟下項目,訪問地址:127.0.0.1:8080/selectWithStream,界面輸出的內容如下代碼塊所示:

[ { "id": 2, "title": "油菜", "unit": "斤", "price": 12.6, "typeName": "綠色蔬菜", "typeId": 1 }, { "id": 1, "title": "金針菇", "unit": "斤", "price": 5.5, "typeName": "菌類", "typeId": 3 } ] 

可以看到返回的數據跟上面方法是一致的,當然你們也能猜到自動生成的SQL也是一樣的,這里SQL就不做多解釋了。

總結

以上內容就是本章的全部內容,本章講解的兩種方法都是基於QueryDSL進行查詢只不過一種采用QueryDSL為我們提供的形式封裝自定義對象,而另外一種則是采用java8特性來完成的,Projections與Stream還有很多其他的方法,有興趣的小伙伴可以自行GitHub去查看。



作者:恆宇少年
鏈接:https://www.jianshu.com/p/5c416a780b3e
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM