Search code examples
javaspringhibernatelazy-loadingentitygraph

Lazily fetching entity with multiple relations: Could not write JSON: failed to lazily initialize a collection of role



Message Could not write JSON: failed to lazily initialize a collection of role: core.domain.Cat.catFoods, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: core.domain.Cat.catFoods, could not initialize proxy - no Session (through reference chain: web.dto.ToysDTO["toys"]->java.util.HashSet[0]->web.dto.ToyDTO["cat"]->core.domain.Cat["catFoods"])

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

I have the following entities: Toy, Cat, CatFood and Food. Basically Cat with Toy are in 1:1 relation and Cat and Food are in m:n relation using CatFood.

@NamedEntityGraphs({
        @NamedEntityGraph(name = "toyWithCat",
                attributeNodes = @NamedAttributeNode(value = "cat"))
})
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode(callSuper = true)
@Entity
public class Toy extends BaseEntity<Long> {
    String name;
    int size;

    public Toy(Long id, String name, int size) {
        this.setId(id);
        this.name = name;
        this.size = size;
    }

    @JsonBackReference(value = "cat-reference")
    @OneToOne(mappedBy = "favoriteToy")
    private Cat cat;

}
@NamedEntityGraphs({
        @NamedEntityGraph(name = "catWithToy",
                attributeNodes = @NamedAttributeNode(value = "favoriteToy")),
        @NamedEntityGraph(name = "catWithCatFoodAndFood",
                attributeNodes = @NamedAttributeNode(value = "catFoods",
                        subgraph = "catFoodWithFood"),
                subgraphs = @NamedSubgraph(name = "catFoodWithFood",
                        attributeNodes = @NamedAttributeNode(value = "food")))
})
@Entity
public class Cat extends BaseEntity<Long> {
    String name, breed;
    Integer catYears;
    @JsonManagedReference(value = "cat-reference")
    @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval = true)
    @JoinColumn(name = "toy_id", referencedColumnName = "id")
    private Toy favoriteToy;

    @JsonManagedReference(value = "cat-reference")
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "cat", cascade = {CascadeType.REMOVE}, orphanRemoval = true)
    Set<CatFood> catFoods;

And I am trying to call this function

public interface ToyRepository extends Repository<Toy, Long> {
    @Query("select distinct t from Toy t")
    @EntityGraph(value = "toyWithCat", type = EntityGraph.EntityGraphType.LOAD)
    List<Toy> getToysWithCat();
}

I am using the same idea on fetching cats with toys as the toy entity does not have another relation and they are loaded without a problem


Solution

  • In your Toy class you are using @EqualsAndHashCode which will be resolved in an hashCode() implementation calculating the hashCode based on all properties of that class.

    That means that the hashCode method in your Toy class invokes the hasCode method on Cat. In cat there is a Set of CatFoods which is mapped by Cat which means that to calculate the hashCode of catFoods the Cat property is involved. To calculate the hashCode of Cat it begins again calculating the hashCode of the Set of CatFoods

    (Sounds confusing but currently I am unable to describe it better)

    As there is no session active the Lazy collection can not be fetched which is required to calculate the hashCod. Thats why you get this exception.

    Takeaway: explicitly exclude LAZY fetched properties from @EqualsAndHashCode calculation. You can annotate these properties using @EqualsAndHashCode.Exclude to exclude them from the hashCode calculation.

    To see implementation of the hashCode calculation you can use the DeLombok functionality of IntelliJ.