Ste*_*n X 20 java mysql jpa criteria
我需要调整以下代码示例.
我有一个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'))
Run Code Online (Sandbox Code Playgroud)
我有一张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();
Run Code Online (Sandbox Code Playgroud)
另一个问题:fkCarId未定义为外键,它只是一个整数.有什么方法可以解决这个问题吗?
Tin*_*iny 35
我在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)
Run Code Online (Sandbox Code Playgroud)
booking_id在bookings表是一个主键,fk_car_id是一个外键引用主键(car_id中的)cars表.
使用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();
Run Code Online (Sandbox Code Playgroud)
它会产生您感兴趣的以下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<=?
)
Run Code Online (Sandbox Code Playgroud)
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();
Run Code Online (Sandbox Code Playgroud)
它生成以下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))
Run Code Online (Sandbox Code Playgroud)
返回相同的结果列表.
额外:
这里subquery.select(criteriaBuilder.literal(1L));,criteriaBuilder.literal(1L)在EclipseLink上使用复杂的子查询语句中的表达式时,EclipseLink 会混淆并导致异常.因此,在EclipseLink上编写复杂的子查询时可能需要考虑它.只需选择一个id例如
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId));
Run Code Online (Sandbox Code Playgroud)
和第一种情况一样.注意:如果在EclipseLink上运行如上所述的表达式,您将在SQL查询生成中看到奇怪的行为,尽管结果列表将是相同的.
您还可以使用在后端数据库系统上更高效的连接,在这种情况下,您需要使用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();
Run Code Online (Sandbox Code Playgroud)
它生成以下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
Run Code Online (Sandbox Code Playgroud)
可以注意到,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 <=? )
Run Code Online (Sandbox Code Playgroud)
该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) )
Run Code Online (Sandbox Code Playgroud)
最后一个使用左连接(带有命名参数):
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
Run Code Online (Sandbox Code Playgroud)
如您所知,所有上述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();
Run Code Online (Sandbox Code Playgroud)
在需要/需要时,使用相应的索引/位置参数替换命名参数.
所有这些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);
Run Code Online (Sandbox Code Playgroud)
和我一样.
EXISTS在这种情况下,我总是更喜欢使用运算符的查询,因为结果列表仅涉及基于另一个表中的条件的单个表.在这种情况下,联接将产生前面提到的重复行,需要使用DISTINCT上面的一个查询中所示过滤掉.
毕竟,一切都取决于许多事情.这些都不是里程碑.
免责声明:我对RDBMS知之甚少.
注意:我已经使用了参数化/重载的弃用日期构造函数 - Date(String s)在所有情况下都与SQL查询相关联的索引/位置参数仅用于纯测试目的,以避免java.util.SimpleDateFormat您已经知道的整个噪声.你也可以使用其他更好的API,比如Joda Time(Hibernate支持它),java.sql.*(那些是子类java.util.Date),Java 8中的Java Time(现在大多不支持,除非自定义),当需要/需要时.
希望有所帮助.