Hibernate使用新生成的ID将对象值复制到新对象中

时间:2012-03-30 14:25:33

标签: java hibernate jpa

我正在使用带有几个嵌套表的单列pk的关系数据库。我需要在项目中添加简单的归档。归档仅在应用程序到达特定状态时发生,因此我希望将现有的hibernate对象复制到新实例中,新实例将使用新ID保存,同时保留现有对象。我似乎无法弄清楚如何将现有对象复制到新实例而无需手动设置每个新的实例字段。有人知道这样做的简单方法吗?

9 个答案:

答案 0 :(得分:36)

只需检索对象,将其分离,将id设置为null并将其保留。

MyEntity clone = entityManager.find(MyEntity.class, ID);
entityManager.detach(clone);
clone.setId(null);
entityManager.persist(clone);

如果您的对象具有oneToMany关系,则必须重复所有子项的操作,但设置父对象ID(在persist调用之后生成)而不是null。

当然,您必须删除OneToMany关系中的任何CASCADE persist,否则您的持久性将在DB或fk约束失败中创建所有子项的重复项。

答案 1 :(得分:6)

我也在使用Hibernate,我得到了同样的要求。我遵循的是实施Cloneable。下面是如何执行此操作的代码示例。

class Person implements Cloneable {

        private String firstName;
        private String lastName;

        public Object clone() {

            Person obj = new Person();
            obj.setFirstName(this.firstName);
            obj.setLastName(this.lastName);

            return obj;
        }

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    }

或者您可以使用基于反射的解决方案,但我不建议这样做。有关详细信息,请查看此website

答案 2 :(得分:2)

您可以克隆该对象并擦除该ID并将其保存回来,以便分配新的ID。

所以

Person person = EmployeeDAO.get(empId);
Person newPersonCopy = person.createCopyWithNoId()
EmployeeDAO.add(newPersonCopy);

在这种情况下, createCopyWithNoId()将创建对象的克隆并将Id字段设置为null。当你现在添加新的克隆时,hibernate引擎会将其视为一个新对象,并且数据库将分配一个新的主键。

请注意我避免调用方法克隆,因为我们操作Id(将其设置为null)时出来的并不是一个精确的克隆;

另一种方法是添加一个构造函数,该构造函数接收了person类型的对象并创建了一个新对象,但只是没有设置Id字段,使其保留默认值null。再次坚持使用DAO。

答案 3 :(得分:2)

查看以下链接。一种最强大的克隆机制,可以通过hibernate以最有效的方式使用

http://thoughtfulsoftware.blogspot.in/2013/05/using-variable-depth-copy-to-prevent.html

答案 4 :(得分:1)

正如我在this article中所解释的那样,使用detach或其他人建议的深度克隆并不是克隆实体的最佳方法。如果您尝试使此过程完全自动化,则会错失并非所有属性都值得复制的观点。

因此,最好使用复制构造函数并精确控制需要克隆哪些属性。

因此,如果您有一个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;
    }
}

在复制comments并将其用作新模板的模板时,克隆Post毫无意义:

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);

您需要添加到Post实体中的是复制构造函数:

/**
 * 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());
}

因此,复制构造函数是解决实体克隆/复制问题的最佳方法。

答案 5 :(得分:0)

您可以克隆该对象是否可克隆,或者您可以为要复制的对象定义一个方法/构造函数,并将其自身的参数复制到一个新实例并将其返回给您。

答案 6 :(得分:0)

1-将commons-lang依赖关系添加到pom.xml文件

<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

2-导入org.apache.commons.lang.SerializationUtils

3-使用SerializationUtils.clone(oldObj)

例如Class1 newObj = (Class1) SerializationUtils.clone(oldObj);

另请参阅java-deep-copy

答案 7 :(得分:0)

您可以使用mapstruct并在对象之间创建方法副本,而忽略 id和您不想复制的每个字段。

如果您具有一对多,多对多等的关联,则您的映射器中还必须具有复制方法。

对于将孩子与父母捆绑在一起, 您可以使用afterMapping注释并添加Vlad Mihalcea之类的添加方法,然后在其中执行您的工作,或者只是在服务中完成

答案 8 :(得分:0)

我们有类似的要求,即我们需要在交易的特定点复制实体并将其用于审计。解决方案非常简单。我们可以使用Json Object Mapper。

public class Employee {
   public int id;
   public String name;
}

Employee employee = entityManager.find(Employee.class, ID); 

ObjectMapper mapper = new ObjectMapper();
Employee deepCopiedEmployee = mapper.readValue( mapper.writeValueAsString( employee ), Employee.class );
deepCopiedEmployee.setId(null);

另一种选择是使用内部Spring Utils。

org.springframework.beans.BeanUtils.copyProperties(<source>, <target>, ...ignoredPropertied)

Employee copiedEmployee = new Employee();
BeanUtils.copyProperties(employee, copiedEmployee, id)

注意:使用BeanUtils时,将在事务中跟踪Bean。也就是说,如果在交易过程中对源Bean所做的任何更改都将反映在目标复制的Bean中。