Hibernate事实:始终检查Criteria API SQL查询
Criteria API對于動態構建查詢非常有用,但這是我使用它的唯一用例。 每當您有一個帶有N個過濾器且可以以任意M個組合到達的UI時,就應該有一個API動態地構造查詢,因為連接字符串始終是我所不愿使用的路徑。
問題是,您是否知道您的Criteria API在后臺生成SQL查詢? 最近,我一直在審查許多這樣的查詢,而弄錯它的難易程度令我震驚。
讓我們從以下實體圖開始:
因此,我們有一個產品,其中ToOne與WareHouseProductInfo關聯,而ToMany與Image實體關聯。
現在讓我們從以下Criteria API查詢開始:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> query = cb.createQuery(Product.class); Root<Product> productRoot = query.from(Product.class);query.select(productRoot).where(cb.and(cb.equal(productRoot.get(Product_.code), "tvCode"), cb.gt(productRoot.get(Product_.warehouseProductInfo) .get(WarehouseProductInfo_.quantity), 50))); Product product = entityManager.createQuery(query).getSingleResult();您能發現以前的查詢有任何問題嗎? 讓我們檢查一下生成SQL:
SELECT product0_.id AS id1_14_,product0_.code AS code2_14_,product0_.company_id AS company_5_14_,product0_.importer_id AS importer6_14_,product0_.name AS name3_14_,product0_.version AS version4_14_ FROM product product0_CROSS JOIN warehouseproductinfo warehousep1_ WHERE product0_.id = warehousep1_.idAND product0_.code = ?AND warehousep1_.quantity > 50我期待一個INNER JOIN,而我卻得到了CROSS JOIN。 笛卡爾積是非常低效的,這就是如果您忘記正確加入對where子句進行過濾感興趣的關聯時所得到的。 因此,編寫Criteria API畢竟不是在公園里散步。
幸運的是,此示例可以固定如下:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> query = cb.createQuery(Product.class); Root<Product> productRoot = query.from(Product.class); Join<Product, WarehouseProductInfo> warehouseProductInfoJoin = productRoot.join(Product_.warehouseProductInfo);query.select(productRoot).where(cb.and(cb.equal(productRoot.get(Product_.code), "tvCode"),cb.gt(warehouseProductInfoJoin.get(WarehouseProductInfo_.quantity), 50))); Product product = entityManager.createQuery(query).getSingleResult();產生預期SQL查詢:
SELECT product0_.id AS id1_14_,product0_.code AS code2_14_,product0_.company_id AS company_5_14_,product0_.importer_id AS importer6_14_,product0_.name AS name3_14_,product0_.version AS version4_14_ FROM product product0_INNER JOIN warehouseproductinfo warehousep1_ON product0_.id = warehousep1_.id WHERE product0_.code = ?AND warehousep1_.quantity > 50因此,請注意在Criteria API中定義聯接的方式。 現在,讓我們將之前的Criteria API查詢與其對應的JPAQL進行比較:
Product product = entityManager.createQuery("select p " +"from Product p " +"inner join p.warehouseProductInfo w " +"where " +" p.code = :code and " +" w.quantity > :quantity ", Product.class) .setParameter("code", "tvCode") .setParameter("quantity", 50) .getSingleResult();我一直發現JPAQL比Criteria API更具描述性,但是在某些項目中Criteria API是默認的JPA查詢機制,因此它不僅用于動態過濾器查詢,甚至用于帶有固定where子句的查詢。
好吧,您最終可以實現相同的結果,但是盡管我可以從JPAQL中預測SQL查詢,但是對于Criteria API而言,我卻一無所知。 每當我查看一個Criteria查詢時,我總是必須運行一個集成測試來檢查輸出SQL,因為小的更改確實可以帶來很大的不同。
即使強加了Criteria API用法,考慮到您要格外小心并查看所有查詢,您仍然可以解決該問題。
現在讓我們回到我碰巧碰到的最奇特的聯接子查詢之一(但次優)條件查詢。 如果您與許多開發人員一起從事大型項目,則不可避免地會遇到這種類型的構造。 這就是為什么我更喜歡JPAQL而不是Criteria API的另一個原因。 借助JPAQL,您將無法像下面的示例那樣使用它:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> query = cb.createQuery(Product.class); Root<Product> product = query.from(Product.class); query.select(product); query.distinct(true);List<Predicate> criteria = new ArrayList<Predicate>(); criteria.add(cb.like(cb.lower(product.get(Product_.name)), "%tv%"));Subquery<Long> subQuery = query.subquery(Long.class); Root<Image> infoRoot = subQuery.from(Image.class); Join<Image, Product> productJoin = infoRoot.join("product"); subQuery.select(productJoin.<Long>get(Product_.id));subQuery.where(cb.gt(infoRoot.get(Image_.index), 0)); criteria.add(cb.in(product.get(Product_.id)).value(subQuery)); query.where(cb.and(criteria.toArray(new Predicate[criteria.size()]))); return entityManager.createQuery(query).getResultList();我發現僅通過查看它們就很難解析這些類型的查詢,但是有一個子選擇聞起來像麻煩,所以讓我們看一下生成SQL查詢:
SELECT DISTINCT product0_.id AS id1_14_,product0_.code AS code2_14_,product0_.company_id AS company_5_14_,product0_.importer_id AS importer6_14_,product0_.name AS name3_14_,product0_.version AS version4_14_ FROM product product0_ WHERE ( Lower(product0_.name) LIKE ? )AND ( product0_.id IN (SELECT product2_.idFROM image image1_INNER JOIN product product2_ON image1_.product_id =product2_.idWHERE image1_.index > 0) )盡管某些用例要求使用SQL子查詢,但這里完全沒有必要,而且只會減慢查詢速度。 但是這次我們實際上需要動態過濾查詢,因此JPAQL毫無疑問。 解決此問題的唯一方法是編寫適當的Criteria查詢。
重構之后就是這樣:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> query = cb.createQuery(Product.class); Root<Image> imageRoot = query.from(Image.class); Join<Image, Product> productJoin = imageRoot.join("product"); query.select(productJoin); query.distinct(true); List<Predicate> criteria = new ArrayList<Predicate>(); criteria.add(cb.like(cb.lower(productJoin.get(Product_.name)), "%tv%")); criteria.add(cb.gt(imageRoot.get(Image_.index), 0)); query.where(cb.and(criteria.toArray(new Predicate[criteria.size()]))); return entityManager.createQuery(query).getResultList();現在,我們SQL查詢看起來更好:
SELECT DISTINCT product1_.id AS id1_14_,product1_.code AS code2_14_,product1_.company_id AS company_5_14_,product1_.importer_id AS importer6_14_,product1_.name AS name3_14_,product1_.version AS version4_14_ FROM image image0_INNER JOIN product product1_ON image0_.product_id = product1_.id WHERE ( Lower(product1_.name) LIKE ? )AND image0_.index > 0我想出了為什么開發人員會在這種特定情況下選擇子查詢的原因,我相信這是因為他不知道他可以通過與JPAQL類似的方式來投影不同于Root的實體。查詢。
現在,讓我們進行DTO預測,因為有時候我們不需要獲取整個實體,而僅需要足夠的信息來滿足我們的業務需求。 這次,我們將創建以下查詢:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<ImageProductDTO> query = cb.createQuery(ImageProductDTO.class); Root<Image> imageRoot = query.from(Image.class); Join<Image, Product> productJoin = imageRoot.join(Image_.product); query.distinct(true); List<Predicate> criteria = new ArrayList<Predicate>(); criteria.add(cb.like(cb.lower(productJoin.get(Product_.name)), "%tv%")); criteria.add(cb.gt(imageRoot.get(Image_.index), 0)); query.where(cb.and(criteria.toArray(new Predicate[criteria.size()]))); query.select(cb.construct(ImageProductDTO.class, imageRoot.get(Image_.name), productJoin.get(Product_.name))).orderBy(cb.asc(imageRoot.get(Image_.name))); return entityManager.createQuery(query).getResultList();生成干凈SQL:
SELECT DISTINCT image0_.name AS col_0_0_,product1_.name AS col_1_0_ FROM image image0_INNER JOIN product product1_ON image0_.product_id = product1_.id WHERE ( Lower(product1_.name) LIKE ? )AND image0_.index > 0 ORDER BY image0_.name ASC但是,請檢查前面的條件查詢以了解JOOQ如何構建這樣的查詢:
jooqContext .select(IMAGE.NAME, PRODUCT.NAME) .from(IMAGE) .join(PRODUCT).on(IMAGE.PRODUCT_ID.equal(PRODUCT.ID)) .where(PRODUCT.NAME.likeIgnoreCase("%tv%")).and(IMAGE.INDEX.greaterThan(0)) .orderBy(IMAGE.NAME.asc()) .fetch().into(ImageProductDTO.class);這種方式更具可讀性,您實際上不必猜測輸出SQL查詢是什么,它甚至可以生成綁定參數,我發現它們非常有價值:
SELECT "PUBLIC"."image"."name","PUBLIC"."product"."name" FROM "PUBLIC"."image"JOIN "PUBLIC"."product"ON "PUBLIC"."image"."product_id" = "PUBLIC"."product"."id" WHERE ( Lower("PUBLIC"."product"."name") LIKE Lower('%tv%')AND "PUBLIC"."image"."index" > 0 ) ORDER BY "PUBLIC"."image"."name" ASC結論
我向您展示的第一種情況是我嘗試學習Criteria API時犯的第一個錯誤。 我發現編寫此類查詢時必須格外小心,因為您很容易獲得意外SQL查詢。
如果您選擇對所有查詢都使用Criteria API,那么您可能也有興趣檢查JOOQ 。 即使您選擇JPAQL,只要您要構建高級的動態過濾查詢,JOOQ都可以為您提供更好的幫助。
您仍然會使用流利的API,不會編寫任何String,并且SQL功能將比Hibernate當前提供的功能更多。 因此,只要您的用例不需要查詢托管實體,就可以使用JOOQ。 我之所以喜歡它,是因為我可以比使用Criteria API更好地預測生成SQL,并且當更易于使用API??時,等待“哇”的“驚喜”就會減少。
代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2013/12/hibernate-facts-always-check-criteria-api-sql-queries.html
總結
以上是生活随笔為你收集整理的Hibernate事实:始终检查Criteria API SQL查询的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Maven将文件上传和下载到S3
- 下一篇: 电脑弹窗广告怎么拦截如何屏蔽电脑广告弹窗