I have seen multiple articles related my issue , but nothing seems to work and I don't see much of detailed explanation anywhere .
I have 2 entities , Person and Address and I am trying to have bidirectional OneToMany/ManyToOne relationship .
I am using Spring Data JPA which is using internally Hibernate Core 6.6.4 , Java 17 and SpringBoot 3.4.1 and MySQL .
Tables Creation :-
create table if not exists `person` (
`person_id` int AUTO_INCREMENT primary key ,
`name` varchar(20) not null ,
`age` int not null ,
`contact_id` int not null ,
`created_at` TIMESTAMP not null,
`created_by` varchar(10) not null,
`updated_at` TIMESTAMP default null,
`updated_by` varchar(10) default null ,
foreign key (contact_id) references contact(contact_id)
) ;
create table if not exists `address` (
`addressId` int AUTO_INCREMENT primary key ,
`city` varchar(20) not null ,
`state` varchar(20) not null ,
`country` varchar(20) not null ,
`person_id` int not null ,
`created_at` TIMESTAMP not null,
`created_by` varchar(10) not null,
`updated_at` TIMESTAMP default null,
`updated_by` varchar(10) default null ,
foreign key (person_id) references person(person_id)
) ;
Model Classes :-
package com.anshu.example.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.HashSet;
import java.util.Set;
@EqualsAndHashCode(callSuper = true)
@Data
@Entity
@Table(name = "person")
public class Person extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "person_id")
private int personId ;
private String name ;
private int age ;
@OneToOne(fetch = FetchType.EAGER , cascade = CascadeType.ALL , targetEntity = Contact.class)
@JoinColumn(name = "contact_id" , referencedColumnName = "contact_id" , nullable = false)
private Contact contact ;
@OneToMany(mappedBy = "person" , cascade = CascadeType.ALL , fetch = FetchType.LAZY , targetEntity = Address.class)
private Set<Address> addresses = new HashSet<>();
}
package com.anshu.example.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Entity
@Table
@Data
public class Address extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int addressId ;
private String city ;
private String state ;
private String country ;
@ManyToOne(optional = false)
@JoinColumn(name = "person_id" , referencedColumnName = "person_id")
private Person person ;
}
package com.anshu.example.model;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@Column(name = "created_at" , updatable = false)
@CreatedDate
private LocalDateTime createdAt ;
@Column(name = "created_by" , updatable = false)
@CreatedBy
private String createdBy ;
@LastModifiedDate
@Column(name = "updated_at" )
private LocalDateTime updatedAt ;
@Column(name = "updated_by" )
@LastModifiedBy
private String updatedBy ;
}
Service class :-
package com.anshu.example.service;
import com.anshu.example.model.Person;
import com.anshu.example.repo.PersonRepo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class PersonService {
private final PersonRepo personRepo ;
public PersonService(PersonRepo personRepo) {
this.personRepo = personRepo ;
}
public Person save(Person person) {
log.info(person.toString());
return personRepo.save(person) ;
}
}
I am hitting the API through postman using Rest Controller .. following is my JSON :-
{
"name": "Anshu Anand",
"age": 25,
"contact": {
"name": "Anshu",
"email": "aa@gmail.com",
"message": "Home Contact",
"status": "OPEN"
},
"addresses":[
{
"city":"B",
"state":"A",
"country":"C"
},
{
"city":"V",
"state":"A",
"country":"C"
}
]}
Following is the error :-
EDIT :- As per @Turo's comment , I tried below changes in my service class , this resulted in StackOverflowError
@Slf4j
@Service
public class PersonService {
private final PersonRepo personRepo ;
public PersonService(PersonRepo personRepo) {
this.personRepo = personRepo ;
}
public Person save(Person person) {
log.info(person.toString());
for(Address address : person.getAddresses()) {
address.setPerson(person);
}
return personRepo.save(person) ;
}
}
Error :-
java.lang.StackOverflowError: null
at com.anshu.example.model.BaseEntity.hashCode(BaseEntity.java:15) ~[classes/:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
at com.anshu.example.model.Address.hashCode(Address.java:7) ~[classes/:na]
at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
at com.anshu.example.model.Person.hashCode(Person.java:10) ~[classes/:na]
....
After following suggestions by John , Although I am able to save the entities in table but I am facing following issue . Can someone please explain , what happens after saving the entities and how JPA forms the object which is returned in "save()" response ?
2025-01-09T23:16:42.178+05:30 WARN 5900 --- [example] [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Ignoring exception, response committed already: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Document nesting depth (1001) exceeds the maximum allowed (1000, from `StreamWriteConstraints.getMaxNestingDepth()`)
2025-01-09T23:16:42.178+05:30 WARN 5900 --- [example] [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Document nesting depth (1001) exceeds the maximum allowed (1000, from `StreamWriteConstraints.getMaxNestingDepth()`)]
How to avoid all these issue ? There must be some standard way of doing to avoid all these issue .
In a one-to-many relationship, the "many" side must be the owning side of the relationship. That means it is the entities on that side where the information associating entities is stored. This manifests in your SQL as which table carries the foreign key corresponding to the relationship, and it manifests in your mapping annotations as the mappedBy
attribute of @OneToMany
(designating the field of the other entity that actually maps the relationship).
At the Java level, because your Person
is not the owner of the relationship, adding Address
entities to Person.addresses
is not by itself effective for persisting the relationship, but that's all you can expect to achieve by deserializing the JSON representation provided. What you need to do is set the Person object in the Address.person
fields of the addresses. In your particular case, you should be able to do that fixup in PersonService.save()
, sometime before it saves the person.
In comments you asked whether that would produce a recursion problem. No, it will not. Even if you had defined cascading in both directions for the relationship, which you did not, JPA knows how to deal with that.
Additionally, you should use a container class, probably Integer
or Long
, for your @Id
fields. This is because those types are nullable on the Java side, and seeing their values as null is how the persistence provider knows that it needs to generate IDs for them. It's also a good way for your other code to distinguish between a bona fide ID and no ID assigned.