Home » Java » java – How to use Hibernate orphanRemoval with DtoMapping and @Audited-Exceptionshub

java – How to use Hibernate orphanRemoval with DtoMapping and @Audited-Exceptionshub

Posted by: admin February 23, 2020 Leave a comment

Questions:

I have audited parent and child entity classes with a one-directional OneToMany mapping. I have DTO classes with the same fields for both (including id) and a Mapper that maps from DTO to entities.
If in the DTO class id field is set, the Mapper.map(ParentDto parentDto, Parent.class) method will not instantiate a new instance of Parent entity, but load the entity from database by its id and then map fields on the loaded entity. When mapping the childs Mapper.map(ChildDto parentDto, Child.class) method also tries to load the child from database by its id.

My problem is, that before mapping the childs list, the mapper clears the list -> orphanRemoval = true triggers and deletes the children from database. They then have to be recreated by the Mapper and persisted. To prevent this I use session.detach(parent) on the parent entity before mapping, thus preventing any change to the database and later reattach it by session.saveOrUpdate(parent). This leads to a NonUniqueObjectException

The Mapper works like this:

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

Parent entity:

@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<>();
}

and Child entity

@Entity
@Audited
public class Child implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Getter
    private Long id;

    ...
}

the Dto Classes:

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;

    ...
}

With this setup during the mapping I then get

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.tr[email protected]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}]

What does work, is if I use em.detach(parent) and then after mapping em.merge(parent). But merge returns a copy of parent that is persistent, the parent given to the mapper stays detached. I can not change the signature of the mapping method public void map(ParentDto parentDto, Parent parent), that is why I tried using session.saveOrUpdate(parent) in the first place, as this is supposed to only work on the given object and reattach that.

So is there a way to either get the @Audit working with session.saveOrUpdate(), or to prevent hibernate to delete children (that are orphans for a fraction of a second) during the mapping without changing the clearing of the list before mapping?

I am using Hibernate 5.3.6.Final.

How to&Answers: