JPA 左连接查询不会在 Spring Boot 中实例化子集合?

Posted

技术标签:

【中文标题】JPA 左连接查询不会在 Spring Boot 中实例化子集合?【英文标题】:JPA left join queries wont instantiate child collections in Spring Boot? 【发布时间】:2017-09-11 02:04:59 【问题描述】:

只是一个简单的问题,我正在将 JPA 与 Spring Boot 一起使用。我有一个类成员与类 Artist 具有 @ManyToMany(FetchType.LAZY) 关系。成员不必有艺术家,但艺术家需要有成员。

在我的 JpaRepository 扩展中,我有一个方法来获取成员和它可能拥有的任何可能的 Artist 孩子。看起来像这样:

@Query("select m from Member m left join fetch m.artists where m.id=:id")
Member getMemberWithArtists(@Param("id") long memberId);

在 A 没有 B 子节点的情况下,B 集合返回为 null。 我之前在 JavaEE 项目中使用过这种方法,然后这种查询总是会给我一个空集合,而不仅仅是 NULL。 spring boot 处理这种情况的方式不同吗?

我的问题是,我能做些什么让它返回一个空集合而不是 null 吗?

这里是实体 A

@Entity
@Table(name="MEMBER")
public class Member extends LocationHolder implements Messenger 

    @ManyToMany(cascade=CascadeType.MERGE,CascadeType.REFRESH, fetch=FetchType.LAZY)
    @JoinTable(name="MEMBER_ARTIST_REL", 
       joinColumns=@JoinColumn(name="MEMBER_ID", referencedColumnName="ENTITY_ID"), 
       inverseJoinColumns=@JoinColumn(name="ARTIST_ID", referencedColumnName="ENTITY_ID"))
    private List<Artist> artists;

这是B

@Entity
@Table(name="ARTIST")
public class Artist extends LocationHolder 

   @ManyToMany(mappedBy="artists", targetEntity=Member.class, fetch=FetchType.LAZY, cascade=MERGE, REFRESH)
   private List<Member> members;

@Id 字段在超类中,类型为 Long

【问题讨论】:

好像很奇怪,能不能也把AB这两个类的代码贴出来。 刚刚用完整的类名编辑了问题 刚刚尝试了精确的类,并返回空集而不是 null。但是,有一个问题,您是否在创建 Member 的同一 JPA 会话中执行查询(例如,在集成测试中)?如果是,您能否发布包含您的测试的代码? 【参考方案1】:

默认情况下,JPA 确实应该在您描述的场景中返回一个空集。但是,查看您的实体,我怀疑存在可能导致 null 结果的情况:

    在创建成员时,您的实体似乎没有将 artists 列表初始化为空集合。 创建并保存Member 的实例 如果在相同的 JPA 会话中,您将尝试使用 getMemberWithArtists 存储库方法加载 member,那么实际上,member.artists 集合将是 null。这是因为 member 实例是从 JPA 会话缓存返回的(在步骤 (2) 中创建的实例,其中 member.artists 等于 null 由于缺少集合的默认初始化)李>

我试图在下面的代码 sn-p 中说明相同的内容:



    @Test
    public void testNullCollection() 
            Member member = new Member();
            memberRepository.save(member);
            member = memberRepository.getMemberWithArtists(member.getId());
            // Here the collection is going to be null since the instance is returned from the session cache
            assertNull(member.getArtists());
            entityManager.flush();
            entityManager.clear();
            // Here the collection is going to be empty, since in the previous step the session was cleared hence forcing JPA to initialize the entity from scratch and inject lazy collection
            member = memberRepository.getMemberWithArtists(member.getId());
            assertNotNull(member.getArtists());
            assertTrue(member.getArtists().isEmpty());
        

这个集成测试实际上通过了我。 (如果测试 JPA 会话跨越整个测试。在实际的 Web 应用程序中,您应该将 JPA 会话配置为仅持续一个请求)。

我相信,只需在 Member 实体的构造函数中将 member.artists 初始化为空集合即可解决新创建和持久化实体的问题(在相同的 JPA 会话中加载时)。



    public Member() 
        this.artists = new ArrayList();
    

希望这会有所帮助。

【讨论】:

感谢您的帮助 Ruben,初始化集合解决了这个问题。然而,这是一件好事吗?我很早就决定不初始化集合,认为这会导致大量浪费的分配内存。仅在需要时初始化它们不是更好吗?还是我在这里过于雄心勃勃?我对 JPA 了解不多,所以请告诉我。 不客气 :-)。好吧,老实说,除非您对它有严格的要求,否则我不会为额外的内存费心太多。但是,您还可以将集合的初始化推迟到实际访问(延迟初始化),例如,在getArtists() 方法中添加null 检查和默认初始化例程。只要集合的所有访问都通过getArtists(),您就可以享受这两个好处:应用程序的其余部分不知道集合可能是null;在访问集合之前,您不会浪费内存。 好吧,很高兴知道,我会采用这种方法。感谢您抽出宝贵时间提供帮助!

以上是关于JPA 左连接查询不会在 Spring Boot 中实例化子集合?的主要内容,如果未能解决你的问题,请参考以下文章

解决spring boot jpa查询,语句正确,返回为空问题

Spring Boot JPA Repository 未释放 Hikari DB Connection

Spring Data JPA 多个实体类表联合视图查询

Spring Boot JPA 连接数据库

使用spring-boot在Jpa查询中出错

Spring Boot with JOOQ 和 Spring Data JPA 之间的技术差异