How to use Hibernate orphanRemoval with DtoMapping and #Audited - java

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.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}]
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 annotation 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.

Related

How to eagerly initialize JPA entity collections from native query

I have a case where I need to use a native query in JPA (I'm using Spring Data JPA) that involves joining with a collection of child entities (association is bi-directional #OneToMany) and I would like to initialize my parent entity with the child entities (i.e., eagerly fetch them).
Since I'm using a native query, join fetch with JPQL and entity load graphs are not an option.
My current approach is using a #SqlResultSetMapping to map the query result to the entities with #EntityResult (one for the parent and one for the child). I then have the Spring Data repository interface method returning a List<Tuple> and then have a default wrapper method that iterates through the list, "attaching" the child entities to the parent, and returning the parent entity.
Here is some example code.
Parent entity:
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#SqlResultSetMapping(
name = "parentMapping",
entities = {
#EntityResult(entityClass = Parent.class),
#EntityResult(entityClass = Child.class)
}
)
#NamedNativeQuery(
name = "Parent.findByParentId0",
query = "SELECT * FROM parent p JOIN child c ON c.parent_id = p.parent_id",
resultSetMapping = "parentMapping")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "PARENT_ID")
#EqualsAndHashCode.Include
private String id;
#Setter(AccessLevel.NONE)
#OneToMany(
mappedBy = "parent",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Set<Child> children;
public Set<Child> getChildren() {
return unmodifiableSet(children);
}
public void addChild(final Child child) {
requireNonNull(child);
children.add(child);
child.setParent(this);
}
public void removeChild(final Child child) {
children.remove(requireNonNull(child));
children.setParent(null);
}
}
Child entity:
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "CHILD_ID")
private Long id;
#ToString.Exclude
#EqualsAndHashCode.Include
#JoinColumn(name = "PARENT_ID", nullable = false)
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Parent parent;
}
Spring Data JPA repository interface:
#Named
interface ParentRepository extends JpaRepository<Parent, String> {
#SneakyThrows
default Optional<Parent> findByParentId(final String parentId) {
List<Tuple> results = findByParentId0(parentId);
Parent parent = null;
Set<Child> children = new HashSet<>();
for (Tuple row : results) {
parent = row.get(0, Parent.class);
children.add(row.get(1, Child.class));
}
if (parent == null)
return Optional.empty();
Field field = Parent.class.getDeclaredField("children");
field.setAccessible(true);
field.set(parent, children);
return Optional.of(parent);
}
#Query(nativeQuery = true)
List<Tuple> findByParentId0(String parentId);
}
This works but there are some very big drawbacks. If I use the parent entity setter (or reflection on the field which I'm doing here) to attach the collection to the parent and orphanRemoval is set to true on the association (which it is and needs to be for other use cases), I will get the error 'a collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance'.
If I add to the collection by accessing it directly (using either add or addAll), then JPA will submit another query to fetch the collection (since the FetchType is LAZY). This obviously defeats the purpose since I already have the child entities fetched from the native query.
Is there any way I can "attach" the child entities to the parent without the extra fetch and in a way so it is managed by JPA so orphanRemoval works?

How get Null value for child in OneToMany in Spring Boot JPA

I need to get simple List of Parent objects with null values for childs.
But when i use findAll() method and then try to get child object i get
LazyInitializationException: failed to lazily initialize a collection of role: ... could not initialize proxy - no Session
I see explanation of this problem (about Lazy/Eager, JOIN FETCH), but i need to get null for child objects without query for childs.
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parent")
Set<Child> childs;
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
Parent parent;
#Service
public class ParentService {
#Autowired
ParentRepository parentRepository;
public List<Course> getParentList() {
return parentRepository.findAll();
}
In hibernate i get correct query:
select parent0_.id as id1_0_ from parent parent0_
Before test i add few parent entities in DB, result not empty, and on this test i get error LazyInitializationException
#Test
public void checkParentListFormat() {
List<Parent> parentList = parentService.getParentList();
Assertions.assertThat(parentList.get(0).getChilds()).isNull();
}
I read about DTO, but is it possible to get simple Entity with null values? Thanks
I think you need to put your lazy initialization in the parent annotation.
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
Set<Child> childs;

Merging parent and lazy child collection list

1 parent entity may have 0 or multiple lazy child entities
For example, there is a function changing the status column in parent and child entities, while merge(parent), parent entity is updated but child entities are insert new instead of update.
Both the child entities id, data are exactly the same as in database while debugging.
The parent object is put in #SessionAttributes in spring controller, would it be the reason?
Even I merge only the child list, merge(childList), it create new records instead of update also.
#Entity
#Table(name = "member")
public class Member implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "memberParent", cascade = CascadeType.ALL)
public List<Child> ChildList
getter setter......
}
#Entity
#Table(name = "child")
public class Child implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="member_id")
private int mem_id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "member_id", referencedColumnName = "id", insertable = false, updatable = false)
})
public Member memberParent;
getter setter......
}
//Controller
#SessionAttributes({"member"})
public class Appcontroller {
#Transactional
#RequestMapping(value = {"/update-member/{id}"}, method = RequestMethod.GET)
public String viewEditRepresetative(ModelMap model, #PathVariable ind id) {
Member member = memberService.find(id);
model.addAttributes("member", member);
}
#Transactional
#RequestMapping(value = {"/update-member"}, method = RequestMethod.POST)
public String viewEditRepresetative(ModelMap model, HttpServletRequest reques, #Valid #ModelAttribute("member") Member member, BindingResult result,
RedirectAttributes redirectAttributes, SessionStatus status) {
if (!result.hasErrors()) {
memberService.merge(member);
}
}
I can't see any parent child relationship in your snapshot code.
Please amend the code for child class with below code to create the inheritance relationship.
public class Child extends Member implements Serializable{
Extending the Child class to the Parent(Member) will reflect the required changes related to lazy loading.

Why does setting ManyToOne parent early causes flush to fail?

I am creating a JPA entity with a ManyToOne relationship. Why does having child.setParent(parent); cause the following fail during flush():
org.apache.openjpa.persistence.ArgumentException: Missing field for property "parent_id" in type "class test.ChildPrimaryKey".
The test code that fails:
Parent parent = new Parent();
Child child = new Child();
ChildPrimaryKey childPrimaryKey = new ChildPrimaryKey();
childPrimaryKey.setLookupId(1);
child.setId(childPrimaryKey);
child.setParent(parent); // <-- FAIL because of this
// Begin transaction
entityManager.clear();
entityManager.getTransaction().begin();
LOGGER.info("Persisting parent without child.");
entityManager.persist(parent);
LOGGER.info("Updating parent with child.");
childPrimaryKey.setParentId(parent.getId());
parent.getChildren().add(child);
entityManager.merge(parent);
// Fail happens at flush
entityManager.flush();
Entities:
#Embeddable
public class ChildPrimaryKey {
#Column(name = "lookup_id")
private int lookupId;
#Column(name = "parent_id")
private long parentId;
}
#Entity
#Table(name = "child")
public class Child {
#EmbeddedId
private ChildPrimaryKey id;
#MapsId("parent_id")
#ManyToOne
private Parent parent;
}
#Entity
#Table(name = "parent")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Child> children;
}
If I remove child.setParent(parent); statement, then my code passes. Using OpenJPA 2.2.2
I beleive your MapsId should be #MapsId("parentId") based on the class structure you've presented. The value of MapsId is an attribute, not a column name.

Hibernate OneToMany generated foreign key

I have the following table structure:
parent(parentId)
child(childId, parentId fk)
Then, I have the following objects:
#Entity
#Table(name = "parent")
public class Parent {
#Id
#GeneratedValue(...)
private String id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "parentId")
Set<Child> children
}
#Entity
#Table(name = "child")
public class Child {
#Id
#GeneratedValue(...)
private String id;
#Column(...)
private String parentId;
}
Now, I create a transient parent and child, and I add the child to the parent, then save the parent:
Parent parent = new Parent();
parent.children.add(new Child());
parentDao.save(parent);
I get the exception:
org.hibernate.PropertyValueException: not-null property references a null or transient value
My question: How can I get the parentId in the child class to automatically be set to the value generated by the insertion of the parent?
I would re-arange your class structure as follows:
#Entity
#Table(name = "parent")
public class Parent {
#Id
#GeneratedValue(...)
private String id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "parentId")
Set<Child> children
}
#Entity
#Table(name = "child")
public class Child {
#Id
#GeneratedValue(...)
private String id;
#Column(...)
private Parent parent;
}
Then when hibernate fetches the parent class, and initializes the set of children, the child class will have a reference to the parent class. Then to get the parentId, you would call:
Child c = new Child()....
c.parent.id;
Your child shouldn't have a private String parentId, but a private Parent parent, and when you parent.children.add(child), you must also child.setParent(parent). See the prototypical parent-child relationship example in the Hibernate reference and the bidi one-to-many section of the annotation reference.

Categories