克隆JPA实体

kri*_*isy 39 java jpa clone

我有一个JPA实体已经存在于数据库中.
我希望有一个副本(具有不同的ID),并修改了一些字段.

最简单的方法是什么?喜欢:

  • 设置它的@Id字段null并保持它将工作?
  • 我是否必须为实体创建克隆方法(复制除了以外的所有字段@Id)?
  • 还有其他方法(比如使用克隆框架)吗?

SJu*_*n76 49

使用EntityManager.detach.它使bean不再链接到EntityManager.然后将Id设置为新Id(如果是自动则设置为null),更改所需的字段并保持不变.

  • 我们遇到的一个问题就是`detach`会忽略对托管实体的未刷新更改.例如,如果您想(1)修改管理实体,(2)分离实体,(3)保留副本,则必须在分离之前调用`flush`,否则您的修改将不会持久化.来自Javadoc的`flush`:"对实体进行的未刷新更改(包括删除实体)将不会同步到数据库." (10认同)
  • 如果实体有表关系和级联,比如`@OneToMany(cascade = CascadeType.ALL,mappedBy = "FIELD_NAME")`,你可能需要循环每个引用实体对象并在持久化之前重置Id,否则可能会抛出[PersistentObjectException: detached传递给持久化的实体](http://stackoverflow.com/questions/15198675/javax-persistence-persistenceexception-org-hibernate-persistentobjectexception)。 (2认同)
  • @javaMS 我想到了两种风险:当会话中其他实体的集合引用被分离的实体时会发生什么;使用延迟加载/代理时这是否可以正常工作。没有深入了解它。 (2认同)

sch*_*pel 14

使用EclipseLink时,您可以使用非常方便的CopyGroup-Feature:

http://wiki.eclipse.org/EclipseLink/Examples/JPA/AttributeGroup#CopyGroup

一个很大的优点是,没有太多的摆弄,它也恰当地克隆了私有的关系.

这是我的代码,使用私人拥有的@ OneToMany关系克隆播放列表只需几行:

public Playlist cloneEntity( EntityManager em ) {
    CopyGroup group = new CopyGroup();
    group.setShouldResetPrimaryKey( true );
    Playlist copy = (Playlist)em.unwrap( JpaEntityManager.class ).copy( this, group );
    return copy;
}
Run Code Online (Sandbox Code Playgroud)

确保使用persist()保存此新对象,merge()不起作用.


Vla*_*cea 6

最好使用复制构造函数并准确控制需要克隆的属性。

所以,如果你有一个Post这样的实体:

@Entity(name = "Post")
@Table(name = "post")
public class Post {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL, 
        orphanRemoval = true, 
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private Set<Tag> tags = new HashSet<>();
 
    //Getters and setters omitted for brevity
 
    public void addComment(
            PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }
 
    public void addDetails(
            PostDetails details) {
        this.details = details;
        details.setPost(this);
    }
 
    public void removeDetails() {
        this.details.setPost(null);
        this.details = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

comments在复制 aPost并将其用作新的模板时克隆是没有意义的:

Post post = entityManager.createQuery(
    "select p " +
    "from Post p " +
    "join fetch p.details " +
    "join fetch p.tags " +
    "where p.title = :title", Post.class)
.setParameter(
    "title", 
    "High-Performance Java Persistence, 1st edition"
)
.getSingleResult();
 
Post postClone = new Post(post);
postClone.setTitle(
    postClone.getTitle().replace("1st", "2nd")
);
entityManager.persist(postClone);
Run Code Online (Sandbox Code Playgroud)

您需要添加到Post实体的是copy constructor

/**
 * Needed by Hibernate when hydrating the entity 
 * from the JDBC ResultSet
 */
private Post() {}
 
public Post(Post post) {
    this.title = post.title;
 
    addDetails(
        new PostDetails(post.details)
    );
 
    tags.addAll(post.getTags());
}
Run Code Online (Sandbox Code Playgroud)

这是解决实体克隆/复制问题的最佳方法。任何试图使此过程完全自动化的其他方法都忽略了并非所有属性都值得复制这一点。