Ответ 1
Используйте EntityManager.detach
. Это делает bean больше не привязанным к EntityManager. Затем установите Id в новый Id (или null, если автоматический), измените нужные вам поля и сохраните.
У меня уже есть сущность JPA в базе данных.
Я хотел бы иметь его копию (с другим идентификатором) с некоторыми измененными полями.
Какой самый простой способ сделать это? Подобно:
@Id
значение null
и сохранить его будет работать?@Id
)?Используйте EntityManager.detach
. Это делает bean больше не привязанным к EntityManager. Затем установите Id в новый Id (или null, если автоматический), измените нужные вам поля и сохраните.
При использовании EclipseLink вы можете использовать ОЧЕНЬ удобную функцию CopyGroup:
http://wiki.eclipse.org/EclipseLink/Examples/JPA/AttributeGroup#CopyGroup
Большим плюсом является то, что без особых усилий он также клонирует частные отношения-отношения.
Это мой код, клонирование плейлиста с его частной связью @OneToMany - это вопрос нескольких строк:
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() не работает.
Сегодня я сталкиваюсь с той же проблемой: у меня есть сущность в базе данных, и я хочу:
Мне удастся выполнить следующие шаги:
@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);
}
Примечание: последний шаг (clone persistence) не работает, если я использую 'persist' вместо 'merge', даже если я отмечаю в режиме отладки, что идентификатор клонирования был изменен после 'persist' команда!
Проблема, с которой я все еще сталкиваюсь, заключается в том, что моя первая сущность не была изменена, прежде чем я ее отключу.
Вы можете использовать картографические структуры, такие как Orika. http://orika-mapper.github.io/orika-docs/ Orika - это структура отображения Java bean, которая рекурсивно копирует данные из одного объекта в другой. Он легко настраивается и предоставляет различные гибкости.
Вот как я использовал его в своем проекте:
добавил 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);
Это может помочь, если у вас есть много случаев, когда требуется такое клонирование.
Как упоминалось в комментариях к принятому ответу, detatch будет игнорировать невыпущенные изменения в управляемом объекте. Если вы используете org.springframework.beans.BeanUtils
вас есть другой вариант, который заключается в использовании org.springframework.beans.BeanUtils
Здесь у вас есть BeanUtils.copyProperties(Object source, Object target)
. Это позволит вам сделать поверхностную копию без вмешательства в entityManager.
Редактировать: цитата из api doc: "этот метод предназначен для выполнения" поверхностной копии "свойств, поэтому сложные свойства (например, вложенные) не будут скопированы".
Этот блог может рассказать вам больше о глубоком копировании объектов Java.
Как я объяснил в этой статье, вам лучше использовать конструктор копирования и точно контролировать, какие атрибуты необходимо клонировать.
Итак, если у вас есть объект 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());
}
Это лучший способ решения проблемы клонирования/дублирования объектов. Любые другие методы, которые пытаются сделать этот процесс полностью автоматическим, упускают из виду тот факт, что не все атрибуты заслуживают дублирования.
Я просто попытался установить идентификатор на ноль, и это сработало
address.setId(null);
address = addrRepo.save(address);
установка идентификатора на ноль сделала его таким образом, чтобы он сохранялся в новую запись с новым идентификатором, так как он у меня автоматически генерируется.