我正在通过JPA使用休眠模式(测试的后端是h2,但是在其他引擎上也会发生同样的问题),并且在连接可选列并对它们进行过滤时遇到了问题。

我有以下数据模型:

@Entity
public class Ticket {
    @Id
    private long id;

    @ManyToOne(optional = true)
    @Nullable
    private Assignee assignee;
}

@Entity
public class Assignee {
    @Id
    private long id;

    private String name;
}


和三个实体:


Assignee{id = 1, name = kitty}
Ticket{id = 1, assignee = null}
Ticket{id = 2, assignee = 1}


现在,我正在使用jpql查询票证:


select t from Ticket t产生两个票证,如预期的那样。
select t from Ticket t where t.assignee is null仅产生票证1,如预期的那样。
如预期,带有select t from Ticket t where t.assignee.name = :namename=kitty仅产生票证2。


但是,在OR子句中将两个过滤器链接在一起不会像预期的那样:select t from Ticket t where (t.assignee is null or t.assignee.name = :name)name=kitty仅产生票证2,而查询也应与票证1匹配(因为受让人可能为空)。检查休眠调试日志时,将生成以下SQL查询:

SELECT
  ticket0_.id          AS id1_1_,
  ticket0_.assignee_id AS assignee2_1_
FROM Ticket ticket0_ CROSS JOIN Assignee assignee1_
WHERE ticket0_.assignee_id = assignee1_.id AND (ticket0_.assignee_id IS NULL OR assignee1_.name = ?)


由于票证1没有受让人,因此显然无法满足条件ticket0_.assignee_id = assignee1_.id,因此休眠状态错误地翻译了此查询。

有什么办法可以解决这个问题吗?

最佳答案

SELECT语句上,您指定了此表达式t.assignee.name。尽管您没有在JOIN语句中显式使用SELECT操作,但是从Ticket实体遍历到Assignee实体以获得name属性将需要两个实体之间的NATURAL JOIN。因此,您将在输出SQL中看到ticket0_.assignee_id = assignee1_.id

您可以重写查询:

SELECT t from Ticket t WHERE (t.assignee IS NULL) OR (t.assignee IS NOT NULL AND t.assignee.name = :name)


或者尝试改用OUTER JOIN:

SELECT t FROM Ticket t LEFT JOIN Assignee a WHERE a.name = :name

10-04 13:33