postgresqlhibernatejpaentitygraph

hibernate 6 entitygraph no longer automatically fetching nested eager fields


Class Diagram

TL;DR

Given the entity graph: Algo -> 2 (lazy) Question q1 and q2 -> (eager) abstract QuestionConfig -> child (depending on concrete type with more nested eager fields).

In spring boot 2.x with hibernate 5.x the following query would not only fetch Question eagerly, but also QuestoinConfig and all nested eager fields:

    @EntityGraph(attributePaths = {Algo_.Q1, Algo_.Q2})
    Algo findOneByIdWithQuestions(Long id);

in spring boot 3.1.2 with hibernate 6.2 it is no longer doing so, but I get

org.hibernate.LazyInitializationException: could not initialize proxy [com.foo.bar.QuestionConfig#1] - no Session

How can I make it work again?

Details

After upgrading spring boot 2.x to 3.x the following does no longer work, but it used to work in 2.x.

@Entity
public class Question {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // Question and QuestionConfig use the same id.
    // In general Question is the parent entity that is operated on, hence synchronization is done by question
    // .setConfig().
    // Technically, however, QuestionConfig owns the relationship, because Question does not know any unique index in
    // QuestionConfig.
    // Instead, QuestionConfig references/uses the id of Question and hence becomes the owning entity.
    // See https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
    // @Setter implemented
    @OneToOne(
            mappedBy = "question",
            cascade = CascadeType.ALL,
            orphanRemoval = true,
            fetch = FetchType.EAGER
    )
    @JoinColumn(name = "config_id", nullable = false)
    private QuestionConfig<?> config;
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class QuestionConfig<T extends QuestionConfig<T>> extends RootEntity<T, Long> {
    // id etc...


    @OneToOne(fetch = FetchType.LAZY) // managed by Question.setConfig()
    @MapsId
    @JoinColumn(name = "id") // otherwise it would be question_id
    // synchronized by question.setConfig()
    // see https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
    private Question question;

    @Transient
    public String getType() {
        return this.getClass().getAnnotation(DiscriminatorValue.class).value();
    }
}
@Entity
public class Algo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Setter
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "q1_id", nullable = false)
    private Question q1;

    @Setter
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "q2_id", nullable = false)
    private Question q2;

    // ...
}

Note that the Algo.q1 and Algo.g2 are defined fetch=LAZY while the Question.config is defined fetch=EAGER. In a query I want to fetch an Algo with both q1 and q2 eagerly using an @EntityGraph:

public interface AlgoRepository extends CrudRepository<Algo, Long> {
    @Query("SELECT a FROM Algo a WHERE a.id = :id")
    @EntityGraph(attributePaths = {Algo_.Q1, Algo_.Q2})
    Algo findOneByIdWithQuestions(Long id);

    //...
}

The problem

The expectation is that the Questions q1 and q2 are eagerly loaded together with q1.config and q2.config and all nested fields that are defined EAGER in the entity.

In 2.x I could do the following in a non-transactional context:

Algo algo = algoRepository.findOneByIdWithQuestions(id);
Question q1 = algo.getQ1();
System.out.println(q1.getConfig().getType()); // does not work in 3.x

But after the upgrade to 3.x I get this error on the last line

org.hibernate.LazyInitializationException: could not initialize proxy [com.foo.bar.QuestionConfig#1] - no Session

What I tried

My first idea was to just define the complete desired entity graph, in this example like that:

    @EntityGraph(attributePaths = {Algo_.Q1 + "." + Question_.CONFIG, Algo_.Q2 + "." + Question_.CONFIG})
    Algo findOneByIdWithQuestions(Long id);

That indeed solves the error in the example BUT in reality it does not work, because there are different nested sub-childs of QuestionConfig that also would needed to be defined, but since it is abstract the attributes depend on the actual type and hence cannot be defined in the entity graph. I think the solution from the link (using 2 queries of which the first populates the hibernate 1st level cache) might work, but since that is cumbersome and it was working in 2.x I wonder what else could be a simpler solution?


Solution

  • Older behavior was incorrect, treating the fetch graph the same as the loadGraph was defined in the JPA specification. To have your Entity fetch types respected and additionally load the listed attributes eagerly, use a load group. In Spring, this is just by defining the type:

    @EntityGraph(type=EntityGraph.EntityGraphType.LOAD)
    

    If unspecified, the default type is EntityGraph.EntityGraphType.FETCH