我正在通过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 = :name
的name=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