一对多关系不起作用。

21

我的表格:

商品: id, 名称

优惠: id, 价值, 商品id

实体:

@Entity
@Table(name="product")
public class Product implements Serializable {
    @OneToMany(mappedBy="product")
    private Set<Offer> offers;
    ...
}

@Entity
@Table(name="offer")
public class Offer implements Serializable {
    @ManyToOne
    @JoinColumn(name="PRODUCT_ID")
    private Product product;
    ...
}

当我尝试从表Product获取一些数据时,我会得到一个java.lang.NullPointerException错误,并且这段代码:product.getOffers()返回:

{IndirectSet: not instantiated}

如何解决这个问题?


你正在使用哪个ORM框架? - kiki
我正在使用JPA作为ORM框架。 - Emanuel
Emanuel:我认为@kiki是在问你使用的哪个JPA实现。 - Jeremy
5个回答

28

这不是错误信息。print指令会在底层的IndirectSet上调用toString()方法。

当从数据库中读取包含域对象时,TopLink将在实例变量中放置IndirectSet。当第一条消息发送到IndirectSet时,则从数据库中获取内容并恢复正常的Set行为。

IndirectCollection类型特别实现为不在toString()上进行实例化:

出于调试目的,#toString()不会触发数据库读取操作。

然而,对间接集合的任何其他调用(例如size()或isEmpty())都将实例化该对象。

最终,在“委托”方法中的任何一个方法首次调用getDelegate()时,将触发数据库读取操作,进而调用buildDelegate(),该方法会向值持有者发送getValue()消息。值持有者执行数据库读取。当第一条消息发送到IndirectSet时,则从数据库中获取内容并恢复正常的Set行为。

另请参见:IndirectList: not instantiated


3
我+1这个回答,非常信息量丰富。我曾经在Java Visual VM中回顾、比较和报告性能时,想知道为什么eclipselink indirectlist isEmpty()会出现。通过用检查list! = null和!list.isEmpty()代替JPA查询indirectlist来解决问题,我的性能问题得到了彻底解决。最初,我访问了超过600次数据库,但是最新实现只需要访问5次数据库! - Howard
1
+1。虽然这个答案是针对TopLink的,但它同样适用于EclipseLink。 - gammay
4
EclipseLink基于TopLink开发(供参考)。 - Patrick Bergner

14

如果在访问product.getOffers()时得到了{IndirectSet: not instantiated},那么很可能是在事务之外执行了这段代码。

默认情况下,@OneToMany@ManyToMany关系是延迟加载的,这意味着为了提高性能,只有当您首次访问数据时才会获取数据。这必须在一个活动的事务中进行。
如果您不在此范围内访问此数据,则将无法再访问此数据。您应该将调用代码放入活动事务中,或者将集合从lazy改为eager

@OneToMany(mappedBy="product", fetch=FetchType.EAGER)

我实际上使用JUnit测试尝试过它,并且确实使用以下代码包装了最终的EntityTransaction txn = em.getTransaction(); txn.begin(); 和txn.commit(),但我仍然得到相同的结果。 - Archimedes Trajano
所以,您确实在EntityManager的事务中调用了您的逻辑(并且您加载了一个对象并在不离开tx的情况下访问它),但仍然遇到相同的异常? - Piotr Nowicki
我没有收到“异常”而是收到了 {间接集:未实例化}。 - Archimedes Trajano
谢谢,这解决了我的问题。但是,我不得不移除一些我类中的"反向"关系,因为急切获取会引发大量查询。除了使用急切获取方案之外,是否有其他方法可以使 JPA 仅在访问时加载引用的对象? - Mr. Lance E Sloan
1
不这么想... 你可以在事务中需要时加载数据(LAZY的背后原理),或者你可以急切地加载它,这样你就不需要tx来访问它(有效的EAGER)。你可以使用FetchType.EAGER急切地加载对象,使用JPQL JOIN FETCH或在tx中迭代集合元素。然而,所有这些选项都会有效地急切地加载整个集合。 - Piotr Nowicki
显示剩余2条评论

7
这是我所做的:

// force load of the set.
entity.getSecrets().isEmpty();
System.out.println(entity.getSecrets());

你为什么要调用 entity.getSecrets().isEmpty()?据我所知,它并没有加载你的集合,因为你仍然需要访问对象本身;你发布的 sysout 应该就可以解决问题(尽管这不是调用对象的 toString 方法来进行延迟加载的好方法..)。 - Piotr Nowicki
2
你可能会这样想,但在EclipseLink中似乎并不是这样。无论如何,我发现.isEmpty()强制集合加载,这可能是一种“特性”。 - Archimedes Trajano

0

这解决了我的问题

@OneToMany(mappedBy = "columnName", cascade = { CascadeType.ALL}, fetch=FetchType.EAGER)


0

我正在使用Eclipselink 2.5.2版本作为我的ORM供应商,在一对多的JPA映射中,当延迟加载包含实体时,我遇到了这个问题。对我有效的解决方法是:

final List<ContainingEntity> list = Collections.unmodifiableList(new ArrayList<> 
                                    (containerEntity.getContainingEntityList());

容器实体方面的映射如下:

@OneToMany(mappedBy = containerEntity, cascade = CascadeType.ALL, fetchType = FetchType.LAZY)
private List<ContainingEntity> containingEntityList;

包含实体端的映射是:

@ManyToOne
@JoinColumn(name = "container_entity_id")
private ContainerEntity containerEntity;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接