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?
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 expectation is that the Question
s 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
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?
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