1. 程式人生 > 實用技巧 >springJpa 自定義引數查詢

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
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。