JPA (休眠) / 查询 DSL 左连接与条件不起作用



我有两个表 -userbooking.每个用户可能有多个预订(一对多关系(。

user:                          booking:
id | name    |               id | country    | user_id | price   |
-------------|               ------------------------------------|
1 | Alice   |                1 | Italy      | 1       | 2000    |
2 | Bob     |                2 | France     | 1       | 2500    |
3 | Spain      | 1       | 3000    |

我想使用Query DSL选择预订价格大于 2000 的所有用户和所有预订。如果用户没有任何预订或预订不符合条件,我仍想选择此用户。

首先,让我们看一下使用简单的 SQL 左连接查询会是什么样子的:

SELECT u.*, b.* FROM user u LEFT JOIN booking b ON u.id = b.user_id AND b.price > 2000

上述查询应提供以下结果:

id | name    | id    | country    | user_id | price   |
-------------|----------------------------------------|
1 | Alice   | 2     | France     | 1       | 2500    |
1 | Alice   | 3     | Spain      | 1       | 3000    |
2 | Bob     | null  | null       | null    | null    |

现在我想使用JPAQuery DSL

JPA相关的东西:

@Entity
public class User {
@Id
private Long id;
private String name;
@OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
private List<Booking> bookings;
// getters and setters
}
@Entity
public class Booking {
@Id
private Long id;
private String name;
private Integer price;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "user_id")
private User user;
// getters and setters
}

查询 DSL:

public List<User> getUsersAndBookings() {
QUser user = QUser.user;
QBooking booking = QBooking.booking;
JPAQuery<User> jpaQuery = new JPAQuery(entityManager);
List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).on(booking.price.gt(2000)).fetchJoin().fetch();
return result;
}

实际上,此代码不起作用,并且我得到以下异常:

org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause not allowed on fetched associations; use filters [select user from com.example.demo.entity.User user left join fetch user.bookings as booking with booking.price > ?1]

问题是条件子句是在方法 -on(booking.price.gt(2000))on中指定的。

经过一些研究,我发现这个条件应该where方法中指定,并且应该看起来像这样:

List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).where(booking.price.gt(2000)).fetchJoin().fetch();

这有效,但不是我期望它的工作方式,因为它不返回所有用户,它只返回一个用户(Alice(,该用户有一些预订,与条件子句匹配。基本上,它只是过滤合并的表(左连接操作后的结果表(,这不是我要找的。

我想检索所有用户,如果特定用户没有任何预订,那么只需null而不是该用户的预订列表。

请帮忙,已经挣扎了几个小时没有任何成功。


使用的版本:
  • 春季启动 2.0.2
  • 春季数据 JPA 2.0.7
  • 休眠 5.2.16.最终版
  • 查询DSL 4.1.4

可以在 where 子句中使用isNull表达式来获取具有空值的行。

您的查询应如下所示:

jpaQuery.from(user)
.leftJoin(user.bookings, booking)
.fetchJoin()
.where(booking.price.gt(2000).or(booking.id.isNull())).fetch();

休眠产生的查询:

select
user0_.id as id1_1_0_,
bookings1_.id as id1_0_1_,
user0_.name as name2_1_0_,
bookings1_.country as country2_0_1_,
bookings1_.price as price3_0_1_,
bookings1_.user_id as user_id4_0_1_,
bookings1_.user_id as user_id4_0_0__,
bookings1_.id as id1_0_0__
from
user user0_
left outer join
booking bookings1_
on user0_.id=bookings1_.user_id
where
bookings1_.id is null
or bookings1_.price>?

似乎没有 JPA 方法。但是我使用过滤器org.hibernate.annotations.Filter以休眠方式修复了它。

@Entity
@FilterDef(name = "anyName", parameters = {
@ParamDef(name = "price", type = "integer")
})
public class User {    
@Id
private Long id;
private String name;
@OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
@Filter(name = "anyName", condition = "price > :inputPrice")
private List<Booking> bookings;
}

在查询数据库之前,必须启用此筛选器。

Session session = enityManager.unwrap(Session.class);
session.enableFilter("anyName").setParameter("inputPrice", 2000);
// fetch using hql or criteria; but don't use booking.price.gt(2000) or similar condition there
session.disableFilter("anyName");

现在,即使他所有的预订价格都低于2000,结果也会Userbookings并且列表将如预期的那样为空。

注意condition中的单词price应与 db 列名完全相同;而不是与模型属性名称完全相同。

最新更新