【问题标题】:Inserting the value in the "other side" of a 1-to-n relationship in JPA (using EclipseLink)在 JPA 中的 1 对 n 关系的“另一边”中插入值(使用 EclipseLink)
【发布时间】:2011-03-23 18:59:47
【问题描述】:

我有一个使用 JSF 2、JPA 2 和 EJB 3.1 在 GlassFish v3 上运行的应用程序(因此它使用 EclipseLink 2)。此应用程序有两个 JPA 实体,PersonMessage,每个 Message 都有两个 Persons 的引用,即消息发送者和消息接收者。 Person 类具有提供对发送或接收的Messages 的访问的属性。 Message 类是这样的:

@Entity
public class Message {
    @ManyToOne
    @JoinColumn(name="sender")
    private Person sender;

    @ManyToOne
    @JoinColumn(name="receiver")
    private Person receiver;
    // ... some stuff ...
}

Person 类如下:

@Entity
public class Person {
    @OneToMany(fetch=FetchType.EAGER, mappedBy="sender")
    private List<Message> sentMessages;

    @OneToMany(fetch=FetchType.EAGER, mappedBy="receiver")
    private List<Message> receivedMessages;
    // ... more stuff ...
}

Message 实例是通过 DAO 类中的方法创建的:

@Stateless
public class MessageDAOImpl implements MessageDAO {
    public Message crete(Person sender, Person receiver) {
        Message message = new Message();
        message.setSender(sender);
        message.setReceiver(receiver);
        entityManager.persist(message);
        // Puting in other objects, hoping it will work
        sender.getSentMessages().add(message);
        receiver.getReceivedMessages().add(message);

        // Saving sender and receiver: here comes the interesting part
        entityManager.merge(sender);    // [1]
        entityManager.merge(receiver); // [2]
        return message;
    }
}

然而,令人惊奇的事情发生了。当我注释掉标记为[1][2] 的行并调用该方法时,当我在其他地方使用这些实体时,该消息不会出现在sender 的已发送消息和receiver 的已接收消息列表中,即使它们是从实体管理器中检索的(例如通过EntityManager.find())。如果我取消注释,则消息会按预期出现在列表中。

我觉得这很有趣,因为我为注释掉行的情况设想了两种情况:

  • senderreceiver 实体直接从数据库中检索,消息应该出现在它们的列表中,因为我保存了消息,因此关系保留在数据库中;或
  • senderreceiver 实体是从某个缓存中检索到的,因此它们已经将消息添加到列表中。

显然,这种方式是行不通的。

我发现这个awesome article 关于 Hibernate 中的缓存。基于此,我认为问题在于,由于缓存不是对象而是值,因此需要 EntityManager.merge() 方法来“刷新”具有缓存值的实体。

是这样吗?如果不是,这种行为的原因是什么?拨打EntityManager.merge() 是最好的解决方案吗?

提前谢谢大家!

【问题讨论】:

    标签: java jpa jpa-2.0 eclipselink glassfish-3


    【解决方案1】:

    问题是您违反了对象身份。您的人员对象来自不同的事务,因此不同的持久性单元并且是分离的。通过从您的新消息中引用它们,您已经破坏了您的持久性单元,因为您现在如何从托管对象中引用分离的对象。即,如果您为任何一个人执行 find(),您将得到另一个人。

    您应该首先 find() 当前事务/持久性上下文中的每个人,然后用他们创建消息,或者在新消息上使用 merge() 而不是 persist() 作为与解析分离对象的合并。

    创建后消息对象不在人员消息中的原因是您正在使用共享缓存(默认使用 EclipseLink),并且在没有合并的情况下,您从未将消息添加到托管人员对象(只有分离的那些)。由于 ManyToOne 定义了数据库中的关系,您可以禁用缓存,或在人员上调用 refresh 并获取正确的消息。但是,正确的解决方案是不要破坏您的对象模型。

    看, http://en.wikibooks.org/wiki/Java_Persistence/Caching#Object_Identity

    【讨论】:

      【解决方案2】:

      我的意思是,当您持久保存消息时,接收者和发送者实体不会自动保存到数据库中。

      我认为您应该正确设置级联选项。也许这篇文章可能会有所帮助。

      http://www.mkyong.com/hibernate/cascade-jpa-hibernate-annotation-common-mistake/

      【讨论】:

      • 我认为没有必要保存发送者和接收者,因为关系是在Message 端注册的。此外,我看不出有任何级联的原因(实际上可能是删除级联),因为PERSIST 级联不起作用(Persons 已经持久化)并且MERGE 级联不会跟随。但是我可能会遗漏一些东西......(OTOH,非常感谢这篇文章,它真的很棒)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-23
      • 1970-01-01
      • 2012-09-27
      相关资源
      最近更新 更多