使用 IN 运算符查询具有联接列的持久性实体

Query persistence Entity with join column using IN operator

提问人:Naman 提问时间:11/14/2023 更新时间:11/14/2023 访问量:31

问:

我有一个实体,例如 follow,它与另一个名为 story_id.Story

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "user_story_mapping")
public class UserStoryMapping {
 
     @ManyToOne
     @JoinColumn(name = "story_id")
     private Story story; // this attribute
 
     @Column(name = "user_id")
     private String userId;
 
     @Column(name = "seen_at")
     private Date seenAt;
  
     @Transient
     private String storyId;
}

我尝试执行的是查询此表,例如

select usm.story_id as storyId from user_story_mapping usm where usm.user_id='Naman' and usm.story_id IN ("8a9f90858871dc9f018abc3fc3dc4e86","ffhy8a9f9abc3fc3dc4e09") and usm.status = 1;

为了在 的帮助下实现这一点,我引用了以下查询常量:javax.persistence.EntityManager#createQuery

public static final String GET_ACTIVE_USER_STORY_MAPPINGS = """
             from UserStoryMapping usm \
             where ((usm.userId = :userId) and (usm.story IN :storyIds) and (usm.status = 1))
             """;

在执行时,这会导致以下异常

…nested exception is java.lang.IllegalArgumentException:
Parameter value element [8a9f90858871dc9f018abc3fc3dc4e86] did not match expected type [entity.core.Story (n/a)]"

如果我将查询更改为

public static final String GET_ACTIVE_USER_STORY_MAPPINGS = """
             select usm.story_id as storyId \
             from UserStoryMapping usm \
             where ((usm.userId = :userId) and (usm.story_id IN :storyIds) and (usm.status = 1))
             """;

然后我被卡住了

org.hibernate.QueryException: could not resolve property: story_id of: entity.core.UserStoryMapping [select usm.story_id as storyId\nfrom entity.core.UserStoryMapping usm where ((usm.userId = :userId) and (usm.story_id IN :storyIds) and (usm.status = 1))\n];
java sql hibernate jpa 联接

评论

0赞 Chris 11/14/2023
你的 UserStoryMapping.story 映射是 Story 的实例 - 为了让它工作,JPA/JPQL 要求你做更类似于 “(usm.story.id in :storyIds)” 的事情。提供程序可能会执行联接故事 - 如果要确保避免这种情况,则必须将“story_id”列映射为实体中的基本列,以便可以在查询中使用它,就像在第二个示例中尝试的那样:“(usm.story_id IN :storyIds)”。您可以将两个引用映射到同一列的基本映射,只需将一个标记为只读即可
0赞 Naman 11/14/2023
@Chris感谢您对 的引用,这确实有效,但它会导致提供程序执行 JOIN。您能否用一些代码详细说明一下引用映射和基本映射的方法以及它是如何工作的?usm.story.id
0赞 Chris 11/14/2023
还可以尝试使用“(usm.story IN :storyIds)”将 Story 的实例传递到查询。许多 JPA 提供程序将为您从实体实例中提取 ID。

答:

1赞 Chris 11/14/2023 #1

错误消息指出了问题:UserStoryMapping.story 映射是指向 Story 的实例,因此 JPA 要求您为使用此映射的查询操作传入 Story 实例,但您却直接传入 ID 值。如果要使用 ID,则需要改用 form 的查询表达式:,以便查询将 ID 值与 Story.Id 列匹配。不过,许多提供程序将执行对 Story 的联接,因为通过使用 usm.story,规范需要对结果进行内部联接功能。"(usm.story.id in :storyIds)"

某些提供程序可能允许将 Story 实例直接传递到查询。对于他们,您可以使用 .这取决于提供程序和映射类型;他们可能会通过从您传入的故事实例中提取 ID 来将其转换为,或者他们可能仅使用该值来引用 usm 中的 FK。"(usm.story IN :storyInstances)""(usm.story.id in :storyIds)"

如果要避免连接,并且不依赖于 JPA 提供程序行为或映射细节,那么还可以将外键作为基本映射直接映射到实体中。

@Entity
@Table(name = "user_story_mapping")
public class UserStoryMapping {
 
  @ManyToOne
  @JoinColumn(name = "story_id")
  private Story story; //this controls the FK, and must always be set

  @Column(name = "story_id", insertable=false, updatable=false)
  private String storyId;
  ..
}

然后,当从数据库中读取 UserStoryMapping 实例时,将在实体中设置 storyId,但该值不会用于控制“story_id”外键列 - 该列仍仅由故事 ManyToOne 属性控制。这将允许您在查询中使用 story 属性来访问联接,或者在想要使用不带联接的值时将 storyId 属性用作任何基本映射,从而允许以下形式的表达式:

(usm.storyId IN :storyIds)

评论

0赞 Naman 11/15/2023
感谢您发布此作为答案,我会尝试一下并在一段时间内接受作为答案。