2015-04-18 30 views
7

我需要修改以下代碼示例。使用JPA標準將條件表達式與「AND」和「OR」謂詞相結合API

我有一個MySQL查詢,它看起來像這樣(2015年5月4日和2015年5月6日是動態的,象徵着時間範圍)

SELECT * FROM cars c WHERE c.id NOT IN (SELECT fkCarId FROM bookings WHERE 
    (fromDate <= '2015-05-04' AND toDate >= '2015-05-04') OR 
    (fromDate <= '2015-05-06' AND toDate >= '2015-05-06') OR 
    (fromDate >= '2015-05-04' AND toDate <= '2015-05-06')) 

我有一個bookings表,和一個cars表。我想知道哪些車在一定時間範圍內可用。 SQL查詢就像一個魅力。

我想將其轉換爲CriteriaBuilder輸出。我在最近3個小時內用這個輸出讀過文檔(顯然,這不起作用)。我甚至跳過了子查詢中的where部分。

CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); 
CriteriaQuery<Cars> query = cb.createQuery(Cars.class); 
Root<Cars> poRoot = query.from(Cars.class); 
query.select(poRoot); 

Subquery<Bookings> subquery = query.subquery(Bookings.class); 
Root<Bookings> subRoot = subquery.from(Bookings.class); 
subquery.select(subRoot); 
Predicate p = cb.equal(subRoot.get(Bookings_.fkCarId),poRoot); 
subquery.where(p); 

TypedQuery<Cars> typedQuery = getEntityManager().createQuery(query); 

List<Cars> result = typedQuery.getResultList(); 

另一個問題:fkCarId未定義爲外鍵,它只是一個整數。任何方式來解決這個問題?

+0

有一點需要注意的是,您的日期條件總結爲'fromDate> ='2015-05-04'AND toDate <='2015-05-06''。 – RealSkeptic

+0

謝謝,我會編輯它。它應該是一個動態的時間範圍。如果從2015-05-05到2015-05-07預訂汽車,當開始日期或結束日期或兩者都與現有預訂衝突時,不應預訂該汽車。 –

回答

16

我已經在MySQL數據庫中創建了以下兩個表格,只有必要的字段。

mysql> desc cars; 
+--------------+---------------------+------+-----+---------+----------------+ 
| Field  | Type    | Null | Key | Default | Extra   | 
+--------------+---------------------+------+-----+---------+----------------+ 
| car_id  | bigint(20) unsigned | NO | PRI | NULL | auto_increment | 
| manufacturer | varchar(100)  | YES |  | NULL |    | 
+--------------+---------------------+------+-----+---------+----------------+ 
2 rows in set (0.03 sec) 

mysql> desc bookings; 
+------------+---------------------+------+-----+---------+----------------+ 
| Field  | Type    | Null | Key | Default | Extra   | 
+------------+---------------------+------+-----+---------+----------------+ 
| booking_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | 
| fk_car_id | bigint(20) unsigned | NO | MUL | NULL |    | 
| from_date | date    | YES |  | NULL |    | 
| to_date | date    | YES |  | NULL |    | 
+------------+---------------------+------+-----+---------+----------------+ 
4 rows in set (0.00 sec) 

bookingsbooking_id是主鍵和fk_car_id爲外鍵引用cars表的主鍵(car_id)。


使用IN()子查詢中的相應的JPA標準查詢轉到如下所示。

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); 
Metamodel metamodel = entityManager.getMetamodel(); 
Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); 

Subquery<Long> subquery = criteriaQuery.subquery(Long.class); 
Root<Bookings> subRoot = subquery.from(metamodel.entity(Bookings.class)); 
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId)); 

List<Predicate> predicates = new ArrayList<Predicate>(); 

ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate1); 
ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate1); 
Predicate and1 = criteriaBuilder.and(exp1, exp2); 

ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate2); 
ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate2); 
Predicate and2 = criteriaBuilder.and(exp3, exp4); 

ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate3); 
ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate3); 
Predicate and3 = criteriaBuilder.and(exp5, exp6); 

Predicate or = criteriaBuilder.or(and1, and2, and3); 
predicates.add(or); 
subquery.where(predicates.toArray(new Predicate[0])); 

criteriaQuery.where(criteriaBuilder.in(root.get(Cars_.carId)).value(subquery).not()); 

List<Cars> list = entityManager.createQuery(criteriaQuery) 
     .setParameter(fromDate1, new Date("2015/05/04")) 
     .setParameter(toDate1, new Date("2015/05/04")) 
     .setParameter(fromDate2, new Date("2015/05/06")) 
     .setParameter(toDate2, new Date("2015/05/06")) 
     .setParameter(fromDate3, new Date("2015/05/04")) 
     .setParameter(toDate3, new Date("2015/05/06")) 
     .getResultList(); 

它產生的興趣的下面的SQL查詢(基於Hibernate 4.3.6最終測試,但不應該有在這一背景下,平均ORM框架的任何差異)。

SELECT 
    cars0_.car_id AS car_id1_7_, 
    cars0_.manufacturer AS manufact2_7_ 
FROM 
    project.cars cars0_ 
WHERE 
    cars0_.car_id NOT IN (
     SELECT 
      bookings1_.fk_car_id 
     FROM 
      project.bookings bookings1_ 
     WHERE 
      bookings1_.from_date<=? 
      AND bookings1_.to_date>=? 
      OR bookings1_.from_date<=? 
      AND bookings1_.to_date>=? 
      OR bookings1_.from_date>=? 
      AND bookings1_.to_date<=? 
    ) 

括號上面查詢的WHERE子句中的條件式在技術上完全多餘的,其只需要更好其中休眠無視可讀性 - 休眠不必考慮它們。


我個人然而,喜歡使用EXISTS操作。因此,查詢可以如下重建。

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); 
Metamodel metamodel = entityManager.getMetamodel(); 
Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); 

Subquery<Long> subquery = criteriaQuery.subquery(Long.class); 
Root<Bookings> subRoot = subquery.from(metamodel.entity(Bookings.class)); 
subquery.select(criteriaBuilder.literal(1L)); 

List<Predicate> predicates = new ArrayList<Predicate>(); 

ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate1); 
ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate1); 
Predicate and1 = criteriaBuilder.and(exp1, exp2); 

ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate2); 
ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate2); 
Predicate and2 = criteriaBuilder.and(exp3, exp4); 

ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate3); 
ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate3); 
Predicate and3 = criteriaBuilder.and(exp5, exp6); 

Predicate equal = criteriaBuilder.equal(root, subRoot.get(Bookings_.fkCarId)); 
Predicate or = criteriaBuilder.or(and1, and2, and3); 
predicates.add(criteriaBuilder.and(or, equal)); 
subquery.where(predicates.toArray(new Predicate[0])); 

criteriaQuery.where(criteriaBuilder.exists(subquery).not()); 

List<Cars> list = entityManager.createQuery(criteriaQuery) 
     .setParameter(fromDate1, new Date("2015/05/04")) 
     .setParameter(toDate1, new Date("2015/05/04")) 
     .setParameter(fromDate2, new Date("2015/05/06")) 
     .setParameter(toDate2, new Date("2015/05/06")) 
     .setParameter(fromDate3, new Date("2015/05/04")) 
     .setParameter(toDate3, new Date("2015/05/06")) 
     .getResultList(); 

它產生以下SQL查詢。

SELECT 
    cars0_.car_id AS car_id1_7_, 
    cars0_.manufacturer AS manufact2_7_ 
FROM 
    project.cars cars0_ 
WHERE 
    NOT (EXISTS (SELECT 
     1 
    FROM 
     project.bookings bookings1_ 
    WHERE 
     (bookings1_.from_date<=? 
     AND bookings1_.to_date>=? 
     OR bookings1_.from_date<=? 
     AND bookings1_.to_date>=? 
     OR bookings1_.from_date>=? 
     AND bookings1_.to_date<=?) 
     AND cars0_.car_id=bookings1_.fk_car_id)) 

它返回相同的結果列表。


附加:

這裏subquery.select(criteriaBuilder.literal(1L));,而使用像上的EclipseLink,EclipseLink的gets confused複雜的子查詢語句criteriaBuilder.literal(1L)表達式和導致異常。因此,在EclipseLink上編寫複雜的子查詢時可能需要考慮到這一點。在這種情況下,只需選擇id,如

subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId)); 

與第一種情況一樣。注意:如果您在EclipseLink上運行上述表達式,您將在SQL查詢生成中看到odd behaviour,儘管結果列表將相同。

您也可以使用連接,後者在後端數據庫系統上效率更高,在這種情況下,您需要使用DISTINCT篩選出可能的重複行,因爲您需要父表中的結果列表。如果詳細表中存在多個子行,則結果列表可能包含重複行 - bookings對應的父行cars我把它留給你。 :)這就是它在這裏。

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); 
Metamodel metamodel = entityManager.getMetamodel(); 
Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); 
criteriaQuery.select(root).distinct(true); 

ListJoin<Cars, Bookings> join = root.join(Cars_.bookingsList, JoinType.LEFT); 

ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.fromDate), fromDate1); 
ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); 
Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.toDate), toDate1); 
Predicate and1 = criteriaBuilder.and(exp1, exp2); 

ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.fromDate), fromDate2); 
ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); 
Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.toDate), toDate2); 
Predicate and2 = criteriaBuilder.and(exp3, exp4); 

ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.fromDate), fromDate3); 
ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); 
Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.toDate), toDate3); 
Predicate and3 = criteriaBuilder.and(exp5, exp6); 

Predicate or = criteriaBuilder.not(criteriaBuilder.or(and1, and2, and3)); 
Predicate isNull = criteriaBuilder.or(criteriaBuilder.isNull(join.get(Bookings_.fkCarId))); 
criteriaQuery.where(criteriaBuilder.or(or, isNull)); 

List<Cars> list = entityManager.createQuery(criteriaQuery) 
     .setParameter(fromDate1, new Date("2015/05/04")) 
     .setParameter(toDate1, new Date("2015/05/04")) 
     .setParameter(fromDate2, new Date("2015/05/06")) 
     .setParameter(toDate2, new Date("2015/05/06")) 
     .setParameter(fromDate3, new Date("2015/05/04")) 
     .setParameter(toDate3, new Date("2015/05/06")) 
     .getResultList(); 

它產生以下SQL查詢。

SELECT 
    DISTINCT cars0_.car_id AS car_id1_7_, 
    cars0_.manufacturer AS manufact2_7_ 
FROM 
    project.cars cars0_ 
LEFT OUTER JOIN 
    project.bookings bookingsli1_ 
     ON cars0_.car_id=bookingsli1_.fk_car_id 
WHERE 
    (
     bookingsli1_.from_date>? 
     OR bookingsli1_.to_date<? 
    ) 
    AND (
     bookingsli1_.from_date>? 
     OR bookingsli1_.to_date<? 
    ) 
    AND (
     bookingsli1_.from_date<? 
     OR bookingsli1_.to_date>? 
    ) 
    OR bookingsli1_.fk_car_id IS NULL 

如可以注意到休眠提供商逆響應WHERE NOT(...)WHERE子句中的條件語句。其他提供者也可能生成確切的WHERE NOT(...),但畢竟,這與問題中所寫的相同,併產生與以前情況相同的結果列表。

未指定右連接。因此,JPA提供者不必執行它們。他們大多不支持正確的連接。


所屬JPQL只是爲了完整性:)起見

IN()查詢:

SELECT c 
FROM cars AS c 
WHERE c.carid NOT IN (SELECT b.fkcarid.carid 
         FROM bookings AS b 
         WHERE b.fromdate <=? 
           AND b.todate >=? 
           OR b.fromdate <=? 
           AND b.todate >=? 
           OR b.fromdate >=? 
           AND b.todate <=?) 

EXISTS()查詢:

SELECT c 
FROM cars AS c 
WHERE NOT (EXISTS (SELECT 1 
        FROM bookings AS b 
        WHERE (b.fromdate <=? 
           AND b.todate >=? 
           OR b.fromdate <=? 
           AND b.todate >=? 
           OR b.fromdate >=? 
           AND b.todate <=?) 
          AND c.carid = b.fkcarid)) 

最後一個使用左連接(帶有命名參數):

SELECT DISTINCT c FROM Cars AS c 
LEFT JOIN c.bookingsList AS b 
WHERE NOT (b.fromDate <=:d1 AND b.toDate >=:d2 
      OR b.fromDate <=:d3 AND b.toDate >=:d4 
      OR b.fromDate >=:d5 AND b.toDate <=:d6) 
      OR b.fkCarId IS NULL 

以上所有JPQL語句都可以按照您已知的方法使用以下方法運行。

List<Cars> list=entityManager.createQuery("Put any of the above statements", Cars.class) 
       .setParameter("d1", new Date("2015/05/04")) 
       .setParameter("d2", new Date("2015/05/04")) 
       .setParameter("d3", new Date("2015/05/06")) 
       .setParameter("d4", new Date("2015/05/06")) 
       .setParameter("d5", new Date("2015/05/04")) 
       .setParameter("d6", new Date("2015/05/06")) 
       .getResultList(); 

當需要/需要時,用相應的索引/位置參數替換命名參數。

所有這些JPQL語句也生成與上述標準API生成的SQL語句相同的SQL語句。


  • 我總是避免在這種情況下IN()子查詢,尤其是在 使用MySQL。我會用IN()子查詢,當且僅當它們 絕對必要的情況下,比如當我們需要確定 結果集或刪除的,如

    SELECT * FROM table_name WHERE id IN (1, 2, 3, 4, 5);` 
    DELETE FROM table_name WHERE id IN(1, 2, 3, 4, 5); 
    
    靜態值列表上排列表

    等。

  • 在這種情況下,我總是希望使用EXISTS運算符進行查詢,因爲結果列表只涉及 基於另一個表中的條件的單個表。在這種情況下加入 將產生前面提到的重複行,需要使用DISTINCT過濾掉 ,如上面其中一個查詢所示。

  • 我最好使用連接,當要檢索的結果集是從多個數據庫表組合而成的 時 - 無論如何都必須使用。

一切都依賴於許多事情。這些都不是里程碑。

聲明:我對RDBMS知之甚少。


注:我已經使用了參數/超載棄用日期構造 - Date(String s)與SQL查詢中的所有情況下,一個純粹的測試目的相關的索引/位置參數才避免了你已經知道的噪音的整個一塌糊塗java.util.SimpleDateFormat。您也可以使用其他更好的API,如Joda Time(Hibernate支持它),java.sql.*(這些是java.util.Date的子類),Java 8中的Java Time(現在大部分不支持,除非定製),並且在需要/需要時。

希望有幫助。

+0

哇,這無疑是我在互聯網上得到的最好和最詳細的答案。非常感謝!幫助我真的很多。 –

+2

我也有興趣完成答案:)謝謝。 @StevenX – Tiny

2

如果你這樣做的格式將運行得更快:

SELECT c.* 
    FROM cars c 
    LEFT JOIN bookings b 
      ON b.fkCarId = c.id 
      AND (b.fromDate ...) 
    WHERE b.fkCarId IS NULL; 

這種形式仍然不會是非常有效的,因爲它會掃描所有cars,然後將手伸入bookings每一次。您需要索引fkCarId。 「fk」聞起來像是一個FOREIGN KEY,這意味着一個索引。請提供SHOW CREATE TABLE進行確認。

如果CriteriaBuilder無法構建,向他們抱怨,或讓你的方式。

翻轉它周圍威力運行速度更快:

SELECT c.* 
    FROM bookings b 
    JOIN cars c 
      ON b.fkCarId = c.id 
    WHERE NOT (b.fromDate ...); 

在這個公式中,我希望做一個表掃描上bookings,過濾掉保留字符,一個ð才把伸入cars爲所需的行。如果車輛很少,這可能會更快。