Clonage d'une entité JPA

J'ai une entité JPA déjà persisté dans la base de données.
Je voudrais en avoir une copie (avec un id différent), avec quelques champs modifiés.

Quelle est la façon la plus simple de le faire? Comme:

  • définir son champ @Id sur null et le persister fonctionnera?
  • devrai-je créer une méthode clone pour l'entité (copier tous les champs sauf le @Id)?
  • est-il une autre approche (comme l'utilisation d'un clonage cadre)?
32
demandé sur Jaumzera 2012-07-24 10:11:04

6 réponses

Utilisez EntityManager.detach. Cela rend le bean plus lié à EntityManager. Ensuite, définissez L'Id sur le nouvel Id (ou null si automatique), modifiez les champs dont vous avez besoin et persistez.

42
répondu SJuan76 2012-07-24 06:13:54

Lorsque vous utilisez EclipseLink, vous pouvez utiliser la fonction CopyGroup très pratique:

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

Un gros avantage est que sans trop bidouiller, il clone correctement les relations privées.

C'est mon code, cloner une Playlist avec sa relation privée @OneToMany est une question de quelques lignes:

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

Assurez-vous que vous utilisez persist() pour enregistrer ce nouvel objet, merge() ne travail.

13
répondu schieferstapel 2014-06-05 08:23:22

Vous pouvez utiliser des frameworks de mappage comme Orika. http://orika-mapper.github.io/orika-docs/ Orika est un framework de mappage Java Bean qui copie récursivement les données d'un objet à un autre. Il est facile à configurer et offre diverses flexibilités.

Voici comment je l'ai utilisé dans mon projet: ajout d'une dépendance:

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

, Puis l'utiliser dans le code comme suit:

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

Cela pourrait aider si vous avez beaucoup de cas d'utilisation où ce type de clonage est nécessaire.

3
répondu Amrita 2017-01-04 09:28:32

Je fais face au même problème aujourd'hui: j'ai une entité dans la base de données et je veux:

  • l'obtenir à partir de la base de données
  • change une de ses valeurs d'attributs
  • créer un clone de lui
  • Ne modifiez que quelques attributs du clone
  • conserver le clone dans la base de données

Je réussis à faire les étapes suivantes:

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

Remarque : la dernière étape (persistance du clone) ne fonctionne pas si j'utilise 'persist' au lieu de 'merge', même si je note dans mode de débogage qui clone id a été changé après 'persist' commande !

Le problème auquel je suis toujours confronté est que ma première entité n'a pas été modifiée avant de la détacher.

2
répondu Bi30 2016-02-09 10:58:59

Comme mentionné dans les commentaires de la réponse acceptée, detach ignorera les modifications non affichées de l'entité gérée. Si vous utilisez spring, vous avez une autre option qui consiste à utiliser org.springframework.beans.BeanUtils

Ici, vous avez BeanUtils.copyProperties(Object source, Object target). Cela vous permettra de faire une copie complète sans altérer entityManager.

2
répondu vertho 2017-04-19 07:25:19

Comme je l'ai expliqué dans Cet article , il vaut mieux utiliser un constructeur de copie et contrôler exactement quels attributs doivent être clonés.

Donc, si vous avez une entité Post comme celle-ci:

@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;
    }
}

Il n'est pas logique de cloner le comments lors de la duplication d'un Post et de l'utiliser comme modèle pour un nouveau:

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

Ce que vous devez ajouter à l'entité Post est un constructeur de copie:

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

C'est la meilleure façon de s'adresser à l'entité clone/problème de dédoublement. Toutes les autres méthodes, qui tentent de rendre ce processus complètement automatique, manquent le point que tous les attributs ne valent pas la peine d'être dupliqués.

0
répondu Vlad Mihalcea 2018-09-06 04:59:27