web-dev-qa-db-fra.com

Clonage d'une entité JPA

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

Quelle est la manière la plus simple de faire ça? Comme:

  • définir son champ @Id sur null et le conserver, cela fonctionnera?
  • devrai-je créer une méthode de clonage pour l'entité (en copiant tous les champs sauf le @Id)?
  • existe-t-il une autre approche (comme l'utilisation d'un framework de clonage)?
33
krisy

Utilisez EntityManager.detach. Cela fait que le bean n'est plus lié à EntityManager. Puis définissez l'Id sur le nouvel ID (ou null si automatique), modifiez les champs dont vous avez besoin et persistez.

43
SJuan76

Lorsque vous utilisez EclipseLink, vous pouvez utiliser le CopyGroup-Feature TRÈS pratique:

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

Un gros plus, c'est que sans trop de jeu, il clone correctement des relations privées.

C'est mon code. Cloner une Playlist avec sa relation privée @ OneToMany est une affaire 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, la fusion () ne fonctionne pas.

14
schieferstapel

Vous pouvez utiliser des cadres de cartographie comme Orika. http://orika-mapper.github.io/orika-docs/ Orika est un framework de mappage de beans Java qui copie de manière récursive des 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: A ajouté une dépendance:

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

Puis utilisez-le 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 où un tel type de clonage est nécessaire.

3
Amrita

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

  • l'obtenir de la base de données
  • changer l'une de ses valeurs d'attributs
  • en créer un clone
  • ne modifiez que quelques attributs du clone
  • clone persistant 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 'fusionner', même si je remarque en mode débogage que l'identifiant du clone a été modifié après la commande 'persist'!

Le problème que je rencontre toujours est que ma première entité n’a pas été modifiée avant que je ne la détache.

3
Bi30

Comme indiqué dans les commentaires relatifs à la réponse acceptée, detatch ignorera les modifications non annulées apportées à l'entité gérée . Si vous utilisez Spring, vous avez une autre option, à savoir org.springframework.beans.BeanUtils.

Ici, vous avez BeanUtils.copyProperties(Object source, Object target). Cela vous permettra de faire une copie superficielle sans altérer EntityManager.

Edit: Citation de api doc: "cette méthode est destinée à effectuer une" copie superficielle "des propriétés et les propriétés complexes (par exemple, les propriétés imbriquées) ne seront pas copiées." 

Ce blog peut vous en dire plus sur la copie en profondeur d'objets Java.

2
vertho

Comme je l'ai expliqué dans cet article , il est préférable d'utiliser un constructeur de copie et de contrôler exactement les attributs à cloner.

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

Cela n'a pas de sens de cloner la comments lors de la duplication d'une Post et de l'utiliser comme modèle pour une nouvelle:

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 résoudre le problème de clonage/duplication d'entité. Toute autre méthode, qui tente de rendre ce processus complètement automatique, passe à côté du fait que tous les attributs ne méritent pas d'être dupliqués.

0
Vlad Mihalcea