Java JPA SpringBoot - java

I have table A and table B, table B have a fk that rererences to table A.
class EntityA
#Entity
#Table(name = "tableA")
public class EntityA {
... ... ...
#OneToMany(mappedBy="entityA")
private Set<EntityB> entityBList;
}
class EntityB
#Entity
#Table(name = "tableB")
public class EntityB{
... ... ...
#ManyToOne
#JoinColumn(name="id_entityA", nullable=false)
private EntityA entityA;
}
But when i try to call findAll method from repository (from EntityA) i get:
Could not write JSON: Infinite recursion

This is circular dependency issue you are facing.
Use the #JsonManagedReference and #JsonBackReference annotations to fix this issue.
Refer https://stackoverflow.com/a/47715121/6572971 for how to use this.

I've solved the problem using unidirectional relationship. Used this on my EntityA and erase the property entityA on EntityB.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "id_nfs")
private Set<EntityB> entityBList
The #JsonManagedReference and #JsonBackReference annotations didnt fix my problem, probably i used wrong.

Related

Foreign-key in tb_child is null with bidirectional one-to-many relationship

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.

Lombok - Hibernate #OneToOne in same class - StackOverFlowError

I'm working with 2 tables: Person and City. I have a #ManyToOne relationship which worked fine. (many persons can belong to one city).
Then I needed to create a parent-child relationship. (one person can be parent of another person). The code:
#Entity
#Data
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
public Person() {
}
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_id", nullable = false)
private City city;
}
#Entity
#Data
#Table(name = "city")
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "city_id")
private Integer cityId;
[...]
}
This code compiles, I let hibernate to create the table and I can see the parent_id column.
However, after I inserted a few rows and ran myRepository.findAll(), I'm getting the following:
java.lang.StackOverflowError
at java.base/java.lang.Integer.toString(Integer.java:438)
at java.base/java.lang.Integer.toString(Integer.java:1165)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.City.toString(City.java:15)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
[...]
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
Even inspecting the result in debug, it was returning the StackOverFlow error, but the child-parent mappings were done correctly. Even though from parent I could inspect/expand the child, then expand the parent and so on...
The example with #OneToOne in the same class is taken from here.
Any ideas on how I can solve the issue?
Thanks #Daniel Wosch and #dan1st for suggestions.
Indeed, the generated toString from Lombok's #Data was the problem.
Solution was to use #Getter, #Setter, #EqualsAndHashCode from Lombok and my own toString which didnt reference both parent and child. Just one of them and it's ok.
Another option would be adding the following annotation right after the Data annotation: #EqualsAndHashCode(callSuper = false,exclude="your relational properties")
Just replace the value of exclude with your field.

JPA Bidirectional Onetomany json endless loop

the program under this particular environment:
EJB3.0 + JPA + jersey Web Service
First Entity :
#Entity
#Table(name = "student_by_test_yao")
public class StudentTest implements Serializable {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name = "class_id")
private ClassTest classes;
public StudentTest() {}
}
Second Entity:
#Entity
#Table(name = "class_by_test_yao")
public class ClassTest implements Serializable{
#Id
#GeneratedValue
private Integer id;
private String name;
#OneToMany(mappedBy = "classes",cascade = CascadeType.ALL, fetch=FetchType.EAGER)
private List<StudentTest> students;
public ClassTest() {}
}
When I get the ClassTest 's students list.
Exception is:
com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError)
If I change the fetch FetchType.LAZY the Exception is:
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
cn.gomro.mid.core.biz.goods.test.ClassTest.students,
could not initialize proxy - no Session
How to resolve my problems?
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userId")
private User user;
it really worked. I just tried that on Bi-Directional #ManyToOne mapping. It fixed
com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError)
Try to add #JsonIgnore annotation to one of fields to avoid looping
For bidirectional relationships you can use these annotations:
#JsonManagedReference for the parent and #JsonBackReference for the child.
Also, this link might help:
Jackson – Bidirectional Relationships
Snippet must help you.
#JsonIgnore
#ManyToOne
#JoinColumn(name = "columnName", referencedColumnName = "id")
private Class class;

Hibernate envers: RelationTargetAuditMode.NOT_AUDITED vs #NotAudited

I try to audit an entity but I don't want to audit its relationships. If I put #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) in #ManyToOne relations, this works and I don't have any exception, but when I try to use the same annotation in a #onetomany with the param mappedby defined, I have an exception that says me that I have to audit the other entity.
Example:
#Table(name = "OWNERS")
#Entity
#EntityListeners(AuditingEntityListener.class)
#Audited
public class Owner {
...
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(fetch=FetchType.LAZY)
private User user;
...
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "owner" )
private Set<Pet> pets = new HashSet<Pet>();
...
}
When you use #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) you are telling hibernate not to audit this entity but audit the relation so you hibernate will save the id of the referenced entity. Thats why Pet must be an #Audited entity.
If you do not want to store the relation at all you need to use #NotAudited
Check this Whats the difference between #NotAudited and RelationTargetAuditMode.NOT_AUDITED in Hibernate EnVers?
Well, I think you have two options here:
Actually audit the entity Pet, if applicable;
Use the annotation #NotAudited instead of #Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Think about it, The audit table for Owner doesn't have to persist the Pet's associated. If it does, use option 1.
Hope it helps!

Persistence: ManyToMany to same class

I am trying to set a ManyToMany annotation on my code:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "user")
public class User extends AbstractUser
{
#ManyToMany(mappedBy = "promotors", cascade = CascadeType.PERSIST)
#JoinTable(name = "user_student",
joinColumns=#JoinColumn(name="promotor_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="student_id", referencedColumnName="id")
)
private Collection<User> students;
#ManyToMany
private Collection<User> promotors;
}
However every time i try to run the application and the db gets generated, it creates 2 tables for the ManyToMany, 1 new table that is called user_student as i defined beneath, but it also creates a second table user_user which i didn't define but is generated from the promotors.
It's correct you cannot map many to many relationship on one table. As you have only one possible column to map it to. What enforces one to one relationship.
You always have to have mapping table. Its also most convenient way to map many to many relationships on different tables.
Apparently, i didn't define the second ManyToMany correctly, this is the correct code:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "user")
public class User extends AbstractUser
{
#ManyToMany
#JoinTable(name = "user_student",
joinColumns={#JoinColumn(name="promotor_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="student_id", referencedColumnName="id")}
)
private Collection<User> students;
#ManyToMany(mappedBy = "students", cascade = CascadeType.PERSIST)
private Collection<User> promotors;
}

Categories