55

我有一个 JPA 实体已经保存在数据库中。
我想要一份它的副本(具有不同的 id),并修改了一些字段。

最简单的方法是什么?像:

  • 将它的@Id字段设置为null并坚持它会起作用吗?
  • 我是否必须为实体创建一个克隆方法(复制除 之外的所有字段@Id)?
  • 有没有其他方法(比如使用克隆框架)?
4

8 回答 8

62

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

于 2012-07-24T06:13:54.407 回答
14

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

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

一个很大的优点是,它也无需太多的摆弄就可以正确地克隆私有关系。

这是我的代码,用它私有的@OneToMany-relationship 克隆一个播放列表只需几行代码:

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

确保使用persist() 保存这个新对象,merge() 不起作用。

于 2013-07-19T12:44:05.000 回答
8

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

因此,如果您有这样的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复制 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);

您需要添加到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());
}

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

于 2018-09-06T04:59:27.893 回答
4

我今天面临同样的问题:我在数据库中有一个实体,我想:

  • 从数据库中获取
  • 更改其属性值之一
  • 创建它的克隆
  • 只修改克隆的一些属性
  • 在数据库中持久化克隆

我成功地执行了以下步骤:

@PersistenceContext(unitName = "...")
private EntityManager entityManager;

public void findUpdateCloneAndModify(int myEntityId) {
  // retrieve entity from database
  MyEntity myEntity = entityManager.find(MyEntity.class, myEntityId);
  // modify the entity
  myEntity.setAnAttribute(newValue);
  // update modification in database
  myEntity = entityManager.merge(myEntity);
  // detach entity to use it as a new entity (clone)
  entityManager.detach(myEntity);
  myEntity.setId(0);
  // modify detached entity
  myEntity.setAnotherAttribute(otherValue);
  // persist modified clone in database
  myEntity = entityManager.merge(myEntity);
}

备注:如果我使用'persist'而不是'merge',最后一步(克隆持久性)不起作用,即使我在调试模式下注意到克隆ID在'persist'命令后已更改!

我仍然面临的问题是我的第一个实体在分离之前没有被修改。

于 2016-02-09T09:26:33.027 回答
3

您可以使用像 Orika 这样的映射框架。http://orika-mapper.github.io/orika-docs/ Orika 是一个 Java bean 映射框架,它递归地将数据从一个对象复制到另一个对象。它易于配置并提供各种灵活性。

这是我在项目中使用它的方式:添加了一个依赖:

 <dependency>
      <groupId>ma.glasnost.orika</groupId>
      <artifactId>orika-core</artifactId>
      <version>1.4.6</version>
</dependency>

然后在代码中使用如下:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper=mapperFactory.getMapperFacade();
User mappedUser = mapper.map(oldUser, User.class);

如果您有许多需要此类克隆的用例,这可能会有所帮助。

于 2017-01-04T09:28:32.697 回答
3

如已接受答案的评论中所述,detatch 将忽略对托管实体的未刷新更改。如果您使用的是弹簧,您还有另一个选择是使用org.springframework.beans.BeanUtils

在这里你有BeanUtils.copyProperties(Object source, Object target)。这将允许您在不篡改 entityManager 的情况下进行浅拷贝。

编辑: 来自 api doc 的引用:“此方法旨在执行属性的“浅复制”,因此不会复制复杂的属性(例如,嵌套的属性)。”

这篇博文可能会告诉您有关深度复制 Java 对象的更多信息。

于 2017-04-19T07:25:19.227 回答
0

我只是尝试将 id 设置为 null 并且它有效

address.setId(null);
address = addrRepo.save(address);

将 id 设置为 null 使它保存到具有新 id 的新记录中,因为我自动生成了它。

于 2019-07-19T08:12:31.377 回答
0

ModelMapper 库可用于此目的。

public MyEntity clone(MyEntity myInstance) {
    MyEntity newInstance = new MyEntity();
    new ModelMapper().map(myInstance, newInstance);
    return newInstance;
}

只需添加 Maven 依赖项

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.3.2</version>
</dependency>
于 2019-10-30T20:32:38.123 回答