Как использовать Hibernate orphanRemoval с DtoMapping и @Audited - PullRequest
1 голос
/ 19 февраля 2020

Я провел аудит классов родительских и дочерних объектов с однонаправленным отображением 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.

...