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?
Related
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;
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.
I am new to JPA. I have parent and several children. I am able to successfully save parent and associated child with Column Mapping using below code.
#Data
#Entity
#Table(name = "Parent")
public class Parent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ParentKey", nullable = false, length = 20)
private Long parentKey;
#Column(name = "ParentID", nullable = true, length = 60)
private String parentID;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "ParentKey", nullable = false)
private Set<Child> childSet = new HashSet<>(0);
}
#Data
#Entity
#Table(name = "Child")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childKey;
private String ChildID;
}
I have a scenario where I receive only child records with parent ID. I lookup parent and want to use same child object and repository for saving additional child records. Since there is no reference for parent key, how can I achieve without duplicating child object.
Child Object does not have ParentKey. I do not want to make any changes to Parent when I am loading child records only.
I am using mySQL.
Thanks
First, you need a way to query DB that will get you Parent entity by parentID. If you are using spring data jpa module, you can add the following method to a ParentRepository
Optional<Parent> findByParentID(String parentID);
Assuming your service method looks like this, you first retrieve Parent from DB using parentID and then you can add children to that parent.
public void saveChildren(String parentID, Set<Child> children) {
Parent parent = parentRepository.findByParentID(parentID)
.orElseThrow(() -> <Your excp that says parent not found by parentID>);
// assuming you have filtered out duplicates, now you can save remaining as
parent.getChildSet().addAll(children);
parentRepository.save(parent);
}
I have the following 2 classes with hibernate and hibernate search annotations:
Parent Class:
#Indexed
#Entity
public class Parent {
#Id
#Column(name="parent_id")
private Long id;
#Field(store = Store.YES)
private String name;
#IndexedEmbedded
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", targetEntity = Child.class)
private List<Child> childList;
//getters and setters
}
Child Class:
#Entity
public class Child {
#Id
private Long id;
#ManyToOne(targetEntity = Parent.class)
#ContainedIn
#JoinColumn(name = "parent_id")
private Parent parent;
#Field(store = Store.YES)
private String name;
}
I created an index for the above scenario. Now I am trying to get all the child names by searching a given parent name.
I used projection on the field as follows:
Query searchQuery = queryBuilder.keyword().onField("name").matching("test").createQuery();
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(searchQuery, Parent.class);
fullTextQuery.setProjection("childList.name");
Now when I try to run the query to retrieve the search results, I am getting the name of only first child of the parent model.
When I see the indexes using Luke, I am able to see all the values in the document.
How to get the list of all the child names stored in index?
In this example you have a model which required "turning around".
Your goal is to query a list child names so you need to search for the Child entity.
You can still include the "parent's name" in each child, and restrict your query on that.
#Entity #Indexed
public class Child {
...
Query searchQuery = queryBuilder.keyword().onField("parent.name").matching("test").createQuery();
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(searchQuery, Child.class);
fullTextQuery.setProjection("name");
Say I have a unidirectional #ManyToOne relationship like the following:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
If I have a parent P and children C1...Cn referencing back to P, is there a clean and pretty way in JPA to automatically remove the children C1...Cn when P is removed (i.e. entityManager.remove(P))?
What I'm looking for is a functionality similar to ON DELETE CASCADE in SQL.
If you are using hibernate as your JPA provider you can use the annotation #OnDelete. This annotation will add to the relation the trigger ON DELETE CASCADE, which delegates the deletion of the children to the database.
Example:
public class Parent {
#Id
private long id;
}
public class Child {
#Id
private long id;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
With this solution a unidirectional relationship from the child to the parent is enough to automatically remove all children. This solution does not need any listeners etc. Also a JPQL query like DELETE FROM Parent WHERE id = 1 will remove the children.
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
You'll therefore need to do this:
Either, change the unidirectional #ManyToOne relationship to a bi-directional #ManyToOne, or a unidirectional #OneToMany. You can then cascade REMOVE operations so that EntityManager.remove will remove the parent and the children. You can also specify orphanRemoval as true, to delete any orphaned children when the child entity in the parent collection is set to null, i.e. remove the child when it is not present in any parent's collection.
Or, specify the foreign key constraint in the child table as ON DELETE CASCADE. You'll need to invoke EntityManager.clear() after calling EntityManager.remove(parent) as the persistence context needs to be refreshed - the child entities are not supposed to exist in the persistence context after they've been deleted in the database.
Create a bi-directional relationship, like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
I have seen in unidirectional #ManytoOne, delete don't work as expected.
When parent is deleted, ideally child should also be deleted, but only parent is deleted and child is NOT deleted and is left as orphan
Technology used are Spring Boot/Spring Data JPA/Hibernate
Sprint Boot : 2.1.2.RELEASE
Spring Data JPA/Hibernate is used to delete row .eg
parentRepository.delete(parent)
ParentRepository extends standard CRUD repository as shown below
ParentRepository extends CrudRepository<T, ID>
Following are my entity class
#Entity(name = “child”)
public class Child {
#Id
#GeneratedValue
private long id;
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = “parent_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
#Entity(name = “parent”)
public class Parent {
#Id
#GeneratedValue
private long id;
#Column(nullable = false, length = 50)
private String firstName;
}
Use this way to delete only one side
#ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
// #JoinColumn(name = "qid")
#JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = #ForeignKey(name = "qid"), nullable = false)
// #JsonIgnore
#JsonBackReference
private QueueGroup queueGroup;
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
Given annotation worked for me. Can have a try
For Example :-
public class Parent{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="cct_id")
private Integer cct_id;
#OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private List<Child> childs;
}
public class Child{
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="cct_id")
private Parent parent;
}
You don't need to use bi-directional association instead of your code, you have just to add CascaType.Remove as a property to ManyToOne annotation, then use #OnDelete(action = OnDeleteAction.CASCADE), it's works fine for me.