Right now, if I want to delete a Parent entry from database, I would use cascade annotation in the Parent class, so that a deletion of Parent would also delete any children tied to it. Like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
The actual deletion would be like this:
this.parentRepository.delete(parentID);
However, if I want to explicitly choose whether to cascade delete or to simple delete, how would I do that?
I don't think I can choose to turn off the cascade annotation in code manually, so is there a way to cascade delete without using annotation?
You should not delete parent if child has relation on parent. It's not good way. In db child should not have notNull restriction on parent
But you really want control cascade deletion, I would recommend use #EntityListeners:
#EntityListeners({ParentJpaCallbacksListener.class})
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent")
private Set<Child> children;
}
where
#Component
public class ParentJpaCallbacksListener {
#Autoware ChildRepository childRepository;
#PreRemove
// or #PostRemove
void preRemove(Parent parent) {
// your cascade deletion logic
// for example use childRepository to delete some children
}
}
In this way you should not have cascade = CascadeType.REMOVE.
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;
Let's say I have the following "parent" pojo...
#Entity
#Table(name = "parent")
public class Parent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch=FetchType.LAZY, mappedBy = "parent", cascade = {CascadeType.ALL})
#JsonIgnoreProperties("parent")
List<Child> children;
}
and I have the following child POJO :
#Entity
#Table(name = "child")
public class Child{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name="parent_id")
private Parent parent;
}
The result of this will be that I have two tables, and my jpa repository will perform queries using the parent_id field within the child table.
However, What if I want it be like a lookup, where by there is a third table for the relationship, where I have the child id and the parent id as a row, and that would be the relationship? can I modify my spring - jpa / hibernate setup for that? If so, some help would be appreciated!
Maybe a very obvious answer exists but I cant see it.
I have a parent table Parent and a child Table Child.
#Entity
#Table(name="PARENT")
public class Parent implements Serializable {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="PARENT_ID")
private int parentId;
#JsonBackReference
#OneToMany(mappedBy="parent", cascade = CascadeType.ALL)
private List<Child> childs;
....
}
And the child entity.
#Entity
#Table(name="CHILD")
public class Child implements Serializable {
#EmbeddedId
private ChildPK id;
#ManyToOne
#JsonManagedReference
#JoinColumn(name="PARENT_ID",insertable = false, updatable = false, referencedColumnName = "PARENT_ID")
private Parent parent;
....
}
The composite primary key being:
#Embeddable
public class ChildPK implements Serializable {
#Column(name="CTGRY_ID", insertable=false, updatable=false)
private int ctgryId;
#Column(name="PARENT_ID", insertable=false, updatable=false)
private int parentId;
....
}
Now in my service method, i set the relation both ways
childItem.setParent(parent);
parent.setChilds(childItemList);
and call save on the entity:
parentDao.save(parent);
This creates a new entry for the PARENT table as expected with a pk generated by the table identity. Creates a new insert in the CHILD table as expected with a pk generated by the table identity.
However, the fk of the child entry should have been the parentId, but it comes as 0!
Am i missing something?
Although this problem is not unique, when I tried looking for similar issues online, few of the suggestions didn't appear to be relevant.
Please let me know in case you need more details.
Thanks!
You are making both id's entries not insertable (one must be insertable, or the JPA implementation wont know from where take the id):
#ManyToOne
#JsonManagedReference
#JoinColumn(name="PARENT_ID", insertable = false, updatable = false, referencedColumnName = "PARENT_ID")
private Parent parent;
And
#Column(name="PARENT_ID", insertable=false, updatable=false)
private int parentId;
You should remove the insertable = false from parent object (first example).
Other option is removing the insertable false from the second one and making it "by hand" in the #prepersist:
#PrePersist
protected setIdsBeforeSave() {
this.id = new ChildPK(parent.Id, categoryId);
}
Edit
Option 1
#Entity
#Table(name="CHILD")
public class Child implements Serializable {
#EmbeddedId
private ChildPK id;
#ManyToOne
#JsonManagedReference
#JoinColumn(name="PARENT_ID", referencedColumnName = "PARENT_ID")
private Parent parent;
....
}
And the other classes remain the same
Option 2
#Entity
#Table(name="CHILD")
public class Child implements Serializable {
#EmbeddedId
private ChildPK id;
....
#PrePersist
protected void setIdsBeforeSave() {
//You set the parent ID, so wont be null, idk what category means, that why i just put the 0
this.id = new ChildPK(parent.Id, 0);
}
}
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.
I have Hibernate Entities that look something like this (getters and setters left out):
#Entity
public class EntityA {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private EntityB parent;
}
#Entity
public class EntityB extends SuperEntity {
#OneToMany(mappedBy = "parent")
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
#MappedSuperclass
public class SuperEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long itemId;
}
When I query for EntityA it loads fine, with the parent association being replaced by a Hibernate proxy (as it is Lazy). If I want access to the parent's id I perform the following call:
EntityA entityA = queryForEntityA();
long parentId = entityA.getParent().getItemId();
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value. However, in my case this generates a SQL statement which fetches EntityB and only then returns the Id.
How can I investigate the problem? What are some likely causes of this incorrect behaviour?
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value.
Use property access type. The behavior you're experiencing is a "limitation" of field access type. Here is how Emmanuel Bernard explained it:
That is unfortunate but expected. That's one of the limitations of field level access.
Basically we have no way to know that getId() indeed only go and access the id field. So we need to load the entire object to be safe.
So change your code into:
#Entity
public class EntityA {
private EntityB parent;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
public EntityB getParent() {
return parent;
}
...
}
#MappedSuperclass
public class SuperEntity {
private long itemId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public long getItemId() {
return itemId;
}
...
}
Related question
Hibernate Annotations - Which is better, field or property access?
References
Proxy loaded on getId-call when using annotations on fields
proxy getId => why sql is generated !
HHH-3718 (if this issue can ever be solved)
What you say makes sense - that it would not make a DB hit since EntityA contains the parent ID. I am just not sure if the getParent() call actually loads the EntityB object regardless of whether all you're interested in is the ID. You might try marking the children collection (and any other fields) as Lazy if you want to save the DB hit.
#Entity
public class EntityB : SuperEntity {
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
As for Hibernate:
This behavior has been changed since Hibernate 5.2.12.