I am trying my hands at Hibernate Relation Mapping(OneToOne, etc) exercises using Spring Boot. Before you ask, I have already consulted this link : [https://stackoverflow.com/questions/11104897/hibernate-attempted-to-assign-id-from-null-one-to-one-property-employee]. I understand that the weak entity needs to have a ref to the parent entity, but I am not able to figure out, why I need to do that explicitly in Person class constructor? The Codes are as follows.
SpringApplication:
package com.OneToOne.OneToOneMappingPractice;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class App
{
public static void main( String[] args )
{
ApplicationContext applContext = SpringApplication.run(App.class, args);
String[] beanNames = applContext.getBeanDefinitionNames();
Arrays.sort(beanNames);
for(String beanName : beanNames)
System.out.println(beanName);
}
}
CRUDController.java:
package com.OneToOne.OneToOneMappingPractice;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
public class CRUDController
{
private static int randomNumber=(int) Math.random();
@Autowired
private CRUDControllerRepository repository;
@GetMapping(value="/add")
public void add()
{
Person person = new Person("Person"+randomNumber, "Street"+randomNumber, randomNumber);
repository.save(person);
randomNumber+=1;
}
@GetMapping(value="/getAll")
public List<Person> getAll()
{
return repository.findAll();
}
@DeleteMapping(value="/deleteAll")
public void deleteAll()
{
repository.deleteAll();
}
}
Person.java:
package com.OneToOne.OneToOneMappingPractice;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
@Entity
public class Person
{
private String name;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private int Id;
@OneToOne(mappedBy="person", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Address address;
public Person() {}
public Person(String name, String streetName, int house_number)
{
super();
this.name = name;
this.address=new Address();
this.address.setStreetName(streetName);
this.address.setHouse_number(house_number);
//this.address.setPerson(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address.java:
package com.OneToOne.OneToOneMappingPractice;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
@Entity
public class Address {
@Id
@Column(name="user_id")
private int Id;
private int house_number;
private String streetName;
@OneToOne
@MapsId
@JoinColumn(name = "user_id")
private Person person;
public Address(){}
public int getHouse_number() {
return house_number;
}
public void setHouse_number(int house_number) {
this.house_number = house_number;
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String streetName) {
this.streetName = streetName;
}
// public Person getPerson() {
// return person;
// }
public void setPerson(Person person) {
this.person = person;
}
}
CRUDControllerRepository.java:
package com.OneToOne.OneToOneMappingPractice;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public interface CRUDControllerRepository extends JpaRepository<Person, Integer>
{
Person save(Person person);
void deleteAll();
List<Person> findAll();
}
Following are my questions :
- As you can see, in the Person class parameterized constructor, I have commented out the line : this.address.setPerson(this);. If I keep this line commented out, I get the exception : "attempted to assign id from null one-to-one property [com.OneToOne.OneToOneMappingPractice.Address.person]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.OneToOne.OneToOneMappingPractice.Address.person]". If I remove the comment syntax, it works perfectly. Why do I need to explicitly do this? Isn't the @OneToOne annotation supposed to take care of these references by itself?
2.If I enable the Person getPerson() method in the Address class, it recursively goes on, until the stack explodes: "Cannot render error page for request [/getAll] and exception [Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException". Why cant Hibernate itself determine that it needs to stop at that boundary itself, instead of fetching the Parent Object again?
Am I missing something here about these mapping annotations, or anything else?