Search code examples
spring-boothibernatespring-data-jpahibernate-mappingone-to-one

Having difficulties mapping OneToOne relation of 1 parent class with 3 child classes


I have a parent class called FoodInfo, and 3 more child classes called Ingredient, Tag and MiscellaneousData. The relation between FoodInfo and each of those classes is OneToOne.

Currently, this is how I defined the classes:

FoodInfo:

@Entity
@Table(name="food")
public class FoodInfo {
    
    @Id
    @Column(name="code")
    private Long code;
    
    @OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
    private Tag tag;
    
    @OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
    private Ingredient ingredient;
    
    @OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
    private MiscellaneousData misc;

    //And the getters and setters for all attributes including:

    public Ingredient getIngredient() {
        return ingredient;
    }

    public MiscellaneousData getMisc() {
        return misc;
    }

    public String getProduct_name() {
        return product_name;
    }
    
    public void setTag(Tag tag) {
        this.tag = tag;
    }

    public void setIngredient(Ingredient ingredient) {
        this.ingredient = ingredient;
    }

    public void setMisc(MiscellaneousData misc) {
        this.misc = misc;
    }
}

In Ingredient's class:

@Entity
@Table(name="ingredient")
public class Ingredient {
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @JoinColumn(name = "code")
    private FoodInfo foodInfo;

    public FoodInfo getFoodInfo() {
        return foodInfo;
    }
    public void setFoodInfo(FoodInfo foodInfo) {
        this.foodInfo = foodInfo;
    }
}

The other two child classes are the same as Ingredient.

And finally, to Insert all the data I did like so:

FoodInfo food = new FoodInfo();
Ingredient ing = new Ingredient();
MiscellaneousData misc = new MiscellaneousData();
Tag tag = new Tag();

//And after setting all their attributes...

food.setTag(tag);
food.setMisc(misc);
food.setIngredient(ing);

tag.setFoodInfo(food);
misc.setFoodInfo(food);
ing.setFoodInfo(food);
        
foodRepository.save(food);

Now, when I try to run the program, an error says:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name  'entityManagerFactory' defined in class path resource  
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]:

Invocation of init  method failed; nested exception is org.hibernate.AnnotationException: Referenced property not a  (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of  com.guillermo.off.model.FoodInfo.ingredient 

............

Caused by: org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of com.guillermo.off.model.FoodInfo.ingredient

In previous attempts, using annotations in a different way I managed to insert the data in the database, but when I tried to fetch all this data, the program got into an endless loop.

Any help will be very appreciated! Thanks in advance!!

EDIT:

After doing what @Hülya suggested, the information in the database seems to be right: enter image description here

But when requesting the info, I ran into what looks an inifinite loop.

My code for requesting the data is:

@GetMapping("/food")
public List<FoodInfo> findFood(HttpServletResponse response) {
    List<FoodInfo> food = foodService.findAll();
    return food;
}

...and in the console, I can only see the folloging a thousand times:

at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.11.4.jar:2.11.4] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) ~[jackson-databind-2.11.4.jar:2.11.4]


Solution

  • @OneToOne annotation should be used on both parent and child side to create a bidirectional one-to-one mapping.

    As the error says: Referenced property not a (One|Many)ToOne There is no mapping on the Ingredient side.

    You should specify the entity association for foodInfo field with @OneToOne:

    @Entity
    @Table(name="ingredient")
    public class Ingredient {
        // ...
        @OneToOne
        @JoinColumn(name = "code")
        private FoodInfo foodInfo;
    }
    

    Update for com.fasterxml.jackson.databind exception:

    When serializing bidirectional relationships with jackson, cyclic dependency leads to an endless loop. To break the cycle you should add @JsonManagedReference and @JsonBackReference annotations:

    FoodInfo class:

    @Entity
    @Table(name="food")
    public class FoodInfo {
        // ...
        @OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
        @JsonManagedReference
        private Ingredient ingredient;
    }
    

    Ingredient class:

    @Entity
    @Table(name="ingredient")
    public class Ingredient {
        //...
        @OneToOne
        @JoinColumn(name = "code")
        @JsonBackReference
        private FoodInfo foodInfo;
    }