使用 JPA 条件 API 将条件表达式与 "AND" 和 "OR" 谓词相结合
Combining conditional expressions with "AND" and "OR" predicates using the JPA criteria API
我需要修改以下代码示例。
我有一个 MySQL 查询,它看起来像这样(2015-05-04 和 2015-05-06 是动态的,代表一个时间范围)
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
table 和一个 cars
table。我想知道在某个时间范围内有哪辆车可用。 SQL 查询非常有效。
我想 "convert" 这个变成 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
没有定义为外键,它只是一个整数。有什么方法可以这样修复它吗?
如果您采用这种格式,它会 运行 更快:
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
进行 table 扫描,过滤掉保留的字符,然后才进入 cars
以获得所需的行。如果可用汽车很少,这可能会特别快。
我在 MySQL 数据库中创建了以下两个 table,只有必要的字段。
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)
bookings
中的booking_id
table是主键,fk_car_id
是引用主键(car_id
)的外键cars
table.
使用 IN()
sub-query 的相应 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 final 上测试,但在这种情况下,普通 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
子句中条件表达式周围的括号在技术上完全是多余的,只是为了更好的可读性才需要,而 Hibernate 忽略了这一点——Hibernate 不必接受它们考虑在内。
不过,我个人更喜欢使用 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))
其中returns相同的结果列表。
补充:
此处 subquery.select(criteriaBuilder.literal(1L));
,在 EclipseLink 上的复杂 sub-query 语句中使用 criteriaBuilder.literal(1L)
等表达式时,EclipseLink gets confused 并导致异常。因此,在 EclipseLink 上写复杂的 sub-queries 时可能需要考虑到。在那种情况下只是 select 一个 id
例如
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId));
和第一种情况一样。注意:如果您在 EclipseLink 上 运行 上面的表达式,您将在 SQL 查询生成中看到一个 odd behaviour,尽管结果列表将是相同的。
您也可以使用在 back-end 数据库系统上更有效的连接,在这种情况下,您需要使用 DISTINCT
来过滤掉可能的重复行,因为您需要一个结果来自 parent table 的列表。如果对应的 parent 行 cars
在详细 table - bookings
中存在多个 child 行,则结果列表可能包含重复行。 交给你了。 :)这里是这样的。
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
可以注意到,Hibernate 提供程序反转 WHERE
子句中的条件语句以响应 WHERE NOT(...)
。其他提供者也可能生成确切的 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();
在 needed/required.
时用相应的 indexed/positional 参数替换命名参数
所有这些 JPQL 语句还生成与上述条件 API 生成的语句相同的 SQL 语句。
在这种情况下,我总是会避免 IN()
sub-queries,尤其是在
使用 MySQL。我会使用 IN()
sub-queries 当且仅当它们是
绝对需要的情况,例如当我们需要确定
结果集或删除基于静态值列表的行列表,例如
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
运算符的查询,因为结果列表涉及
只有一个 table 基于另一个 table(s) 中的条件。加入
在这种情况下,将产生前面提到的需要过滤掉的重复行
如上面的查询之一所示,使用 DISTINCT
。
- 当要检索的结果集是
从多个数据库组合 tables - 无论如何都必须使用,因为很明显。
毕竟一切都取决于很多事情。这些根本不是里程碑。
免责声明:我对 RDBMS 的了解很少
注意: 我使用了 parameterized/overloaded 弃用 日期构造函数 - Date(String s)
对于与 SQL 查询关联的 indexed/positional 参数,在所有情况下都是出于纯粹的测试目的,只是为了避免您已经知道的 java.util.SimpleDateFormat
噪声的整个混乱。您还可以使用其他更好的 APIs,例如 Joda Time(Hibernate 支持它)、java.sql.*
(它们是 java.util.Date
的 sub-classes)、Java Time在 Java 8 中(除非自定义,否则目前大部分不支持)当 required/needed.
时
希望对您有所帮助。
我需要修改以下代码示例。
我有一个 MySQL 查询,它看起来像这样(2015-05-04 和 2015-05-06 是动态的,代表一个时间范围)
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
table 和一个 cars
table。我想知道在某个时间范围内有哪辆车可用。 SQL 查询非常有效。
我想 "convert" 这个变成 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
没有定义为外键,它只是一个整数。有什么方法可以这样修复它吗?
如果您采用这种格式,它会 运行 更快:
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
进行 table 扫描,过滤掉保留的字符,然后才进入 cars
以获得所需的行。如果可用汽车很少,这可能会特别快。
我在 MySQL 数据库中创建了以下两个 table,只有必要的字段。
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)
bookings
中的booking_id
table是主键,fk_car_id
是引用主键(car_id
)的外键cars
table.
使用 IN()
sub-query 的相应 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 final 上测试,但在这种情况下,普通 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
子句中条件表达式周围的括号在技术上完全是多余的,只是为了更好的可读性才需要,而 Hibernate 忽略了这一点——Hibernate 不必接受它们考虑在内。
不过,我个人更喜欢使用 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))
其中returns相同的结果列表。
补充:
此处 subquery.select(criteriaBuilder.literal(1L));
,在 EclipseLink 上的复杂 sub-query 语句中使用 criteriaBuilder.literal(1L)
等表达式时,EclipseLink gets confused 并导致异常。因此,在 EclipseLink 上写复杂的 sub-queries 时可能需要考虑到。在那种情况下只是 select 一个 id
例如
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId));
和第一种情况一样。注意:如果您在 EclipseLink 上 运行 上面的表达式,您将在 SQL 查询生成中看到一个 odd behaviour,尽管结果列表将是相同的。
您也可以使用在 back-end 数据库系统上更有效的连接,在这种情况下,您需要使用 DISTINCT
来过滤掉可能的重复行,因为您需要一个结果来自 parent table 的列表。如果对应的 parent 行 cars
在详细 table - bookings
中存在多个 child 行,则结果列表可能包含重复行。 交给你了。 :)这里是这样的。
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
可以注意到,Hibernate 提供程序反转 WHERE
子句中的条件语句以响应 WHERE NOT(...)
。其他提供者也可能生成确切的 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();
在 needed/required.
时用相应的 indexed/positional 参数替换命名参数所有这些 JPQL 语句还生成与上述条件 API 生成的语句相同的 SQL 语句。
在这种情况下,我总是会避免
IN()
sub-queries,尤其是在 使用 MySQL。我会使用IN()
sub-queries 当且仅当它们是 绝对需要的情况,例如当我们需要确定 结果集或删除基于静态值列表的行列表,例如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
运算符的查询,因为结果列表涉及 只有一个 table 基于另一个 table(s) 中的条件。加入 在这种情况下,将产生前面提到的需要过滤掉的重复行 如上面的查询之一所示,使用DISTINCT
。- 当要检索的结果集是 从多个数据库组合 tables - 无论如何都必须使用,因为很明显。
毕竟一切都取决于很多事情。这些根本不是里程碑。
免责声明:我对 RDBMS 的了解很少
注意: 我使用了 parameterized/overloaded 弃用 日期构造函数 - Date(String s)
对于与 SQL 查询关联的 indexed/positional 参数,在所有情况下都是出于纯粹的测试目的,只是为了避免您已经知道的 java.util.SimpleDateFormat
噪声的整个混乱。您还可以使用其他更好的 APIs,例如 Joda Time(Hibernate 支持它)、java.sql.*
(它们是 java.util.Date
的 sub-classes)、Java Time在 Java 8 中(除非自定义,否则目前大部分不支持)当 required/needed.
希望对您有所帮助。