【问题标题】:Deep Copy in JPAJPA 中的深度复制
【发布时间】:2009-07-09 21:49:40
【问题描述】:

我想在 JPA 中制作实体的深层副本。我在这里发现了一个有趣的讨论: http://forums.java.net/jive/thread.jspa?messageID=253092&tstart=0

听起来提议的解决方案是将所有@Id 设置为零。这是我的基本代码:


//Start a JPA session.
EntityManager em= emf.createEntityManager();
em.getTransaction().begin();

//Get the object I want to copy.
MyClass myObject=em.find(MyClass.class,id);

//Use reflection to find @Id's and set them to zero for all @OneToMany and @OneToOne relations.
//TODO:  write the ugly recursive code to do this.

//Hoping this will create a deep copy.
em.merge(myObject);

//Close the session.
em.getTransaction().commit();
em.close();

这是一个好的策略吗?是否有人已经编写了可以共享的 TODO 代码???

谢谢!

【问题讨论】:

  • 链接已损坏。你能更新一下吗?
  • 您确定要进行深层复制吗?这可能导致整个数据库重复。我宁愿坚持实施复制 - 乏味,但可以让我头疼或更糟,服务器在生产中崩溃。

标签: java jpa


【解决方案1】:

我解决了这个问题。

我创建了一个组件,它根据包的注释 (javax.persistence) 为您完成整个过程。

组件已经将实体的 id 设置为 null。他根据每个属性关系@OneToMany@OneToOne@ManyToMany的类型对要应用的算法进行了所有分析。

示例

Person person = personDAO.find(1);
PersistenceCloner cloner = new PersistenceCloner(person); 
Person personCopy = cloner.generateCopyToPersist();

下载 JAR 和来源:jpa-entitycloner

【讨论】:

    【解决方案2】:

    我不确定清零已管理对象的 ID 是否是个好主意,尤其是。当您的实体没有将 equals() 定义为 ID 相等时。 JPA 实现可能已经将托管对象放在某个缓存中,并且在使用那里的对象 ID 时会发疯。

    我相信遵循 R.K. 的回答并真正复制对象会更安全。

    【讨论】:

    • 同意。我参与了几个项目,我们试图提出一种通用的方法来制作对象图的深层副本。随着项目的发展,您总是遇到的问题是您最终需要为不同的用例和/或对象内的不同属性集复制对象图的不同部分。最终,自己编写逻辑比尝试通过自动深度克隆变得聪明更容易。此外,擦除 ID 几乎肯定会破坏某些 JPA 实现。
    【解决方案3】:

    如果你的对象实现了 Serializable,你可以使用 writeObject() 和 readObject() 来进行深拷贝。我们有一个数据传输对象层次结构,并通过抽象超类 (DTO) 中的这种方法支持深拷贝:

    /**
     * Reply a deep copy of this DTO.  This generic method works for any DTO subclass:
     * 
     *      Person person = new Person();
     *      Person copy = person.deepCopy();
     * 
     * Note: Using Java serialization is easy, but can be expensive.  Use with care.
     * 
     * @return A deep copy of this DTO.
     */
    @SuppressWarnings("unchecked")
    public <T extends DTO> T deepCopy()
    {
        try
        {
            ObjectOutputStream oos = null;
            ObjectInputStream ois = null;
            try
            {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(bos);
                oos.writeObject(this);
                oos.flush();
                ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
                return (T) ois.readObject();
            }
            finally
            {
                oos.close();
                ois.close();
            }
        }
        catch ( ClassNotFoundException cnfe )
        {
            // Impossible, since both sides deal in the same loaded classes.
            return null;
        }
        catch ( IOException ioe )
        {
            // This has to be "impossible", given that oos and ois wrap a *byte array*.
            return null;
        }
    }
    

    (我确信有人会找到这些异常可能发生的原因。)

    其他序列化库(例如,XStream)可以以相同的方式使用。

    【讨论】:

    • 这不会复制标有transient关键字的字段。
    • @Sean:谢谢,说得好。 (在我们的例子中,这些是普通的 JavaBeans。)
    • @Sean:由于瞬态字段也不会最终出现在数据库中,这似乎是正确的行为。
    【解决方案4】:

    我能够按照问题中的描述获得深层副本。有必要急切地加载整个图形并将@Id 重置为空或零。我发现 Hibernate SessionFactory 实际上有一些方法可以帮助完成这个过程。

    上面关于深拷贝的其他建议似乎不起作用。当然,问题可能出在键盘和椅子之间。但它现在可以工作了。

    谢谢大家!

    【讨论】:

    • 基本上你说我们需要急切地加载整个图,但我们的图通常不会延迟初始化集合。您还评论说 SessionFactory 具有帮助重置@id 的过程的方法。你能提供一些代码示例吗,因为我面临同样的问题,这让我发疯:)
    • 但是你没有提供一段代码。哪个 Hibernate SessionFactory 方法可以做到
    【解决方案5】:

    您为什么要这样做?这听起来有点像黑客攻击。

    也就是说 Apache Commons BeanUtils 包含用于制作(浅)对象副本的 cloneBean()copyProperties() 方法。要进行深层复制,您可以按照here 的建议编写一个方法。

    【讨论】:

    • 我想制作存储在数据库中的数据的深层副本,这些副本将完全独立于从中复制它的对象。例如: -Object1 是 Object2 的深层副本。 -Object1 有一个发生变化的孩子(来自@OneToMany)。 -Object2 的孩子不应该改变。
    • 对于 deepCopy,您可以使用 apache commons 中的 SerializationUtils.clone()
    猜你喜欢
    • 2015-02-28
    • 2011-02-28
    • 1970-01-01
    • 2020-11-08
    • 2016-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多