【问题标题】:JPA (Hibernate) / QueryDSL left join with condition doesn't workJPA (Hibernate) / QueryDSL left join with condition 不起作用
【发布时间】:2018-12-04 11:51:33
【问题描述】:

我有两张桌子 - 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方法-on(booking.price.gt(2000))中指定了条件子句。

经过一番研究,我发现这个条件应该在where方法中指定,应该是这样的:

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

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

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

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


使用的版本:
  • Spring Boot 2.0.2
  • Spring Data JPA 2.0.7
  • 休眠 5.2.16.Final
  • 查询DSL 4.1.4

【问题讨论】:

  • 您对此有什么解决方案吗?我有同样的要求 - 需要将用户加载到结果列表中,即使他的所有预订都不符合条件。在那里,我更喜欢带有空预订列表的用户对象。 @leopal 的回答没有这样做。
  • 解决了。请看下面我的回答。 stackoverflow.com/a/69421978/5035525

标签: java sql hibernate jpa querydsl


【解决方案1】:

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

您的查询应该是这样的:

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

Hibernate 产生的查询:

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>?

【讨论】:

  • 嗨,@leopal。感谢您的回复,但它仍然无法正常工作,因为 left join 应该可以工作。让我们再考虑一下我的例子。假设我想获取价格大于 5000 的所有用户及其预订(不存在符合此条件的预订)。我希望检索所有用户 - Alice 和 Bob,但您的代码只返回 Alice,因为她根本没有任何预订。我创建了这个 sql fiddle 来展示我想要检索的内容 - sqlfiddle.com/#!9/d0a182/2。主要思想是我总是想从左表中检索所有行 - 用户。
  • 你试过了吗? jpaQuery.from(user).leftJoin(booking) .on(user.id.eq(booking.user.id).and(booking.price.gt(5000))).fetchJoin().fetch(); 让我知道它是否有更新我的答案所需的结果。
  • 不幸的是不起作用。此查询的结果是所有预订的所有用户(忽略价格条件)。我看到休眠生成了 3 个不同的查询,第一个查询Hibernate: /* select user from User user left join fetch Booking booking with user.id = booking.user.id and booking.price &gt; ?1 */ select user0_.id as id1_11_, user0_.name as name2_11_ from user user0_ left outer join booking booking1_ on (user0_.id=booking1_.user_id and booking1_.price&gt;?)
  • 第二次查询Hibernate: select bookings0_.user_id as user_id4_1_0_, bookings0_.id as id1_1_0_, bookings0_.id as id1_1_1_, bookings0_.name as name2_1_1_, bookings0_.price as price3_1_1_, bookings0_.user_id as user_id4_1_1_ from booking bookings0_ where bookings0_.user_id=?第三次查询Hibernate: select bookings0_.user_id as user_id4_1_0_, bookings0_.id as id1_1_0_, bookings0_.id as id1_1_1_, bookings0_.name as name2_1_1_, bookings0_.price as price3_1_1_, bookings0_.user_id as user_id4_1_1_ from booking bookings0_ where bookings0_.user_id=?
  • 我猜第一个查询看起来不错,但我不明白为什么它会生成另外两个查询,也许这就是问题
【解决方案2】:

似乎没有 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");

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2016-08-13
    • 2020-03-19
    • 1970-01-01
    • 2021-12-19
    相关资源
    最近更新 更多