web-dev-qa-db-ja.com

JPAエンティティのクローニング

データベースに既に永続化されているJPAエンティティがあります。
一部のフィールドを変更して、コピー(異なるID)を取得したいと思います。

これを行う最も簡単な方法は何ですか?お気に入り:

  • @Idフィールドをnullに設定し、永続化すると動作しますか?
  • エンティティのクローンメソッドを作成する必要がありますか(@Idを除くすべてのフィールドをコピーします)?
  • 他のアプローチ(クローニングフレームワークの使用など)はありますか?
39
krisy

使用する EntityManager.detach。 BeanがEntityManagerにリンクされなくなります。次に、Idを新しいID(または自動の場合はnull)に設定し、必要なフィールドを変更して永続化します。

49
SJuan76

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()は機能しません。

14
schieferstapel

私は今日同じ問題に直面しています:私はデータベースにエンティティがあり、私はしたいです:

  • データベースから取得する
  • その属性値の1つを変更する
  • それのクローンを作成する
  • クローンのいくつかの属性を変更する
  • クローンをデータベースに永続化する

私は次の手順を実行することに成功します:

@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」コマンドの後にクローンIDが変更されていることに注意しても、「merge」ではなく「persist」を使用すると、最後のステップ(クローン永続性)は機能しません!

私がまだ直面している問題は、デタッチする前に最初のエンティティが変更されていないことです。

4
Bi30

Orikaのようなマッピングフレームワークを使用できます。 http://orika-mapper.github.io/orika-docs/ Orikaは、あるオブジェクトから別のオブジェクトにデータを再帰的にコピーするBeanマッピングフレームワークJava構成が簡単で、さまざまな柔軟性も提供します。

私のプロジェクトでこれをどのように使用したかを以下に示します。dependecyを追加しました。

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

これは、このような種類のクローニングが必要なユースケースが多数ある場合に役立ちます。

3
Amrita

受け入れられた回答へのコメントで述べたように、デタッチは管理対象エンティティへのフラッシュされていない変更を無視します。春を使用している場合は、_org.springframework.beans.BeanUtils_を使用する別のオプションがあります

ここにBeanUtils.copyProperties(Object source, Object target)があります。これにより、entityManagerを改ざんすることなく、浅いコピーを作成できます。

Edit:APIドキュメントからの引用:「このメソッドは、プロパティの複雑なプロパティ(ネストされたプロパティなど)の「浅いコピー」を実行するためのものです。 )はコピーされません。」

これ ブログの投稿では、ディープコピーの詳細についてJavaオブジェクト。

2
vertho

この記事 で説明したように、コピーコンストラクターを使用し、どの属性を複製する必要があるかを正確に制御することをお勧めします。

したがって、次のような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());
}

これは、エンティティのクローン/複製の問題に対処する最良の方法です。このプロセスを完全に自動化しようとする他の方法では、すべての属性を複製する価値があるというわけではありません。

0
Vlad Mihalcea

私はIDをnullに設定しようとしましたが、うまくいきました

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

idをnullに設定すると、自動的に生成されるため、新しいIDで新しいレコードに保存されました。

0
JanBrus