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:
@Id
sur null
et le conserver, cela fonctionnera?@Id
)?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.
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.
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.
Je suis confronté au même problème aujourd'hui: j'ai une entité dans la base de données et je veux:
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.
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.
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.