I have a many-to-one bidirectionnal relationship between a parent and child entity. The problem is, when i'm persisting the child, the parent_id isn't persisted. The other fields are fine, but parent_id stay at NULL. I'm using Spring Data JPA with Hibernate and mapstruct to convert between entity and dto, if that can help.
The java files are the following :
#Entity
#Table(name = "Parent")
public class ParentEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany (fetch = FetchType.LAZY, mappedBy="parent_entity", cascade = { CascadeType.ALL })
private List<Child> children;
}
#Entity
#Table(name = "Child")
public class ChildEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(optional = true)
#JoinColumn(nullable = true, name = "parent_entity_id", insertable = false, updatable = false)
private Parent parent_entity;
}
I already tried a bunch of answers from SO, but to no avail for now.
Since you're using bidirectional relationship, you are now responsible to set a parent for your child. And remove insertable = false.
As far as I know. You have to add each other in both parent and child. Only adding children to the list does not seem to work.
Try it out, and see if that fixes the problem.
Related
I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
I´m creating my first Spring Boot application with the Java Persistence API to write to and read from a postgres database. I´ve looked through many tutorials and posts to figure out my exact problem and it seems like I currently have a bidirectional one-to-many relationship with two entities (Parent and Child), but the foreign-key of the child column is always null when I write to the database.
ParentEntity:
#Entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "tb_parent")
public class Parent {
#Schema(description = "id of the parent")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Schema(description = "child-list of the application")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval = true)
private Set<Child> children;
}
ChildEntity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "tb_child")
public class Child{
#Schema(description = "id of the child")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToOne(targetEntity = Parent.class, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = true, insertable = true)
private Parent parent;
}
ParentService:
...
#Override
public Parent create(Parent parent) {
log.info("save new parent: {}", parent);
return parentRepo.save(parent); // the repo is an interface that extends the JpaRepository
}
...
After invoking the create method, I have a parent row in the tb_parent with a generated id and one or more child rows in the tb_child with a generated id and a null value for the parent_id column.
Even though I´m able to find lots of posts describing a similar issue, I wasn´t yet able to find a solution that works for me.
Update #1:
A common suggestion is to manually set the parent object in all child elements. However, this results in a Stackoverflow Exception due to the circular structure.
public void setChildren(Set<Child> children) {
children.forEach(child -> child.setParent(this));
this.children = children;
}
Additionally, it kinda feels off because almost everything is managed automatically by the JPA Annotations and then you have to manually sync the data.
Thanks to Georgy Lvov I was able to find the most effective solution. I had to do the following steps:
remove the #Data annotation as suggested by Georgy Lvov (Thank you so much!) This basically avoids the generated getter and setter methods for all attributes and the methods equals(), hashCode(), and toString() by Lombok which caused the Stackoverflow Exception.
annotate the Set<Child> children; variable in the Parent class with the #JsonManagedReference annotation. see below:
#JsonManagedReference
#Schema(description = "child-list of the application")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval = true)
private Set<Child> children;
annotate the private Parent parent; in the Child class with the #JsonBackReference annotation. see below:
#JsonBackReference
#ManyToOne(targetEntity = Parent.class, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = true, insertable = true)
private Parent parent;
The #JsonBackReference also seems to avoid the circular structure when you create an openapi.json file.
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.