Я провел аудит классов родительских и дочерних объектов с однонаправленным отображением OneToMany. У меня есть классы DTO с одинаковыми полями для обоих (включая id
) и Mapper, который сопоставляет DTO с сущностями. Если в поле класса DTO установлено id
, метод Mapper.map(ParentDto parentDto, Parent.class)
не будет создавать новый экземпляр родительского объекта, а будет загружать объект из базы данных по его идентификатору, а затем сопоставлять поля в загруженном объекте. При отображении метод childs
Mapper.map(ChildDto parentDto, Child.class)
также пытается загрузить дочерний элемент из базы данных по его идентификатору.
Моя проблема в том, что перед отображением списка дочерних элементов преобразователь очищает список -> orphanRemoval = true
запускает и удаляет детей из базы данных. Затем они должны быть воссозданы Mapper и сохранены. Чтобы предотвратить это, я использую session.detach(parent)
на родительском объекте перед отображением, тем самым предотвращая любые изменения в базе данных, а затем снова присоединяю ее на session.saveOrUpdate(parent)
. Это приводит к NonUniqueObjectException
Mapper работает следующим образом:
@Stateless
public class Mapper{
@PersistenceContext(unitName = "core")
private EntityManager em;
public void map(ParentDto parentDto, Parent parent) {
Session session = em.unwrap(Session.class);
if em.contains(parent) {
session.detach(parent); //detach so that orphanRemoval will not delete childs from DB
}
...
parent.getChilds().clear;
for (ChildDto childDto : parentDto.getChilds()) {
parent.getChilds().add(mapToEntity(childDto));
}
session.saveOrUpdate(parent); //(re-)attach, so that changed fields or new childs get written to DB
}
public Parent mapToEntity(ParentDto parentDto) {
Parent parentEntity= null;
if (parentDto.id != null) {
parentEntity= loadParentEntityFromDb(parentDto.id);
}
if (parentEntity= null) {
parentEntity= new Parent();
}
map(parentDto, parentEntity);
return parentEntity;
}
public Child mapToEntity(ChildDto childDto) {
Child childEntity= null;
if (childDto.id != null) {
childEntity= loadChildEntityFromDb(childDto.id);
}
if (childEntity= null) {
childEntity= new Child();
}
map(childDto, childEntity);
return childEntity;
}
}
Родительский объект:
@Entity
@Audited
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Getter
private Long id;
...
@Getter
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinTable(name = "PARENT_TO_CHILD", joinColumns = @JoinColumn(name = "PARENT_ID", referencedColumnName = "ID"), inverseJoinColumns = @JoinColumn(name = "CHILD_ID", referencedColumnName = "ID"))
private final List<Child> childs = new ArrayList<>();
}
и дочерний объект
@Entity
@Audited
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Getter
private Long id;
...
}
Классы Dto:
public class ParentDto implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
@Setter
private Long id;
...
@Getter
private final List<ChildDto> childs = new ArrayList<>();
}
public class ChildDto implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
@Setter
private Long id;
...
}
При этой настройке во время отображения я получаю
12:35:17,422 WARN [com.arjuna.ats.arjuna] (default task-5) ARJUNA012125: TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple< 0:ffff0a00100f:4b2caaeb:5e4cf8b3:58b38, org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization@1149eaa7 >: org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [PARENT_TO_CHILDS_AUD#{REV=Revision [id=98781, timestamp=1582112117394, username=RUser], Parent_id=885752, childs_id=885754}]
Что работает , если я использую em.detach(parent)
а затем после сопоставления em.merge(parent)
. Но слияние возвращает копию родительского объекта, который является постоянным, parent
, данный мапперу, остается отсоединенным. Я не могу изменить сигнатуру метода сопоставления public void map(ParentDto parentDto, Parent parent)
, поэтому я попытался в первую очередь использовать session.saveOrUpdate(parent)
, так как он должен работать только с данным объектом и присоединить его.
Есть ли способ получить аннотацию @Audit
, работающую с session.saveOrUpdate()
, или запретить спящий режим удалять дочерние элементы (которые являются сиротами в течение доли секунды) во время сопоставления без изменения очистки списка перед сопоставлением?
Я использую Hibernate 5.3.6.Final.