1

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 :

  1. 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?

Amit Dev
  • 59
  • 8

1 Answers1

0

1- 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

Hibernate will not set it explicitly because it does not know to which person this address belongs to you need to specify that explicitly.

The purpose of @OneToOne is to tell hibernate where to get the rest of the data when it is already mapped.

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?

The exception is caused by Jackson serializer and not from hibernate. you can look at the examples here to see how it is fixed https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.

dark.semiQ
  • 111
  • 1
  • 7