You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决JPA-Hibernate中嵌套集合的LazyInitializationException问题?

Spring中LazyInitializationException异常排查与解决

必要背景信息

作为Spring学习项目,我开发了一个用于存储题目、生成随机测验的小工具。数据结构层级为:科目包含多个主题,主题包含多个题目,题目包含多个答案。

问题说明

反复遇到LazyInitializationException异常。

已尝试方案

  • 将几乎所有集合类型改为Set
  • 曾考虑开启enable_lazy_load_no_trans属性,但知道这是需要避免的反模式,所以未采用

实体类定义(仅展示字段)

Subject:

@Entity
@Table(name = Resources.TABLE_SUBJECTS)
public class Subject implements DomainObject
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = Resources.ID_SUBJECT)
    private int subjectId;

    @Column(name="subject_name", nullable = false)
    private String name;

    @OneToMany(
            mappedBy = Resources.ENTITY_SUBJECT,
            fetch = FetchType.EAGER
    )
    private Set<Topic> topics;
}

Topic:

@Entity
@Table(name = Resources.TABLE_TOPICS)
public class Topic implements DomainObject
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "topic_id")
    private int topicId;

    @Column(name = "name")
    private String name;

    @OneToMany(
            mappedBy = Resources.ENTITY_TOPIC,
            orphanRemoval = true,
            cascade = CascadeType.MERGE,
            fetch = FetchType.EAGER
    )
    private Set<Question> questions;

    @ManyToOne(
            fetch = FetchType.LAZY
    )
    private Subject subject;
}

Question:

@Entity
@Table(name = Resources.TABLE_QUESTIONS)
public class Question implements DomainObject
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = Resources.ID_QUESTION)
    private int questionId;

    @Column(name = "statement")
    private String statement;

    @OneToMany(
            mappedBy = Resources.ENTITY_QUESTION,
            orphanRemoval = true,
            cascade = CascadeType.MERGE
    )
    private Set<Answer> answers;

    @ManyToOne(
            fetch = FetchType.LAZY
    )
    private Topic topic;
}

Answer:

@Entity
@Table(name = Resources.TABLE_ANSWERS)
public class Answer implements DomainObject
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = Resources.ID_ANSWER)
    private int answerId;

    @Column(name = "answer_text", nullable = false)
    private String text;

    @Column(name = "is_correct", nullable = false)
    private Boolean isCorrect;

    @ManyToOne(
            fetch = FetchType.LAZY
    )
    private Question question;
}

数据访问层实现

我通过继承JpaRepository实现CRUD操作,尝试了以下自定义查询但无效:

public interface SubjectRepository extends JpaRepository<Subject, Integer>
{
    @Query
    Optional<Subject> findByName(String name);

    @Query(value = "SELECT DISTINCT s FROM Subject s " +
            "LEFT JOIN FETCH s.topics AS t " +
            "JOIN FETCH t.questions AS q " +
            "JOIN FETCH q.answers as a")
    List<Subject> getSubjects();
}

异常栈信息

Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [org.callisto.quizmaker.domain.Subject#1] - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
    at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
    at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
    at org.callisto.quizmaker.domain.Subject$HibernateProxy$B8rwBfBD.getTopics(Unknown Source) ~[main/:na]
    at org.callisto.quizmaker.service.QuizMakerService.activeSubjectHasTopics(QuizMakerService.java:122) ~[main/:na]
    at org.callisto.quizmaker.QuizMaker.checkIfActiveSubjectHasTopics(QuizMaker.java:307) ~[main/:na]
    at org.callisto.quizmaker.QuizMaker.createNewQuestion(QuizMaker.java:117) ~[main/:na]
    at org.callisto.quizmaker.QuizMaker.prepareMainMenu(QuizMaker.java:88) ~[main/:na]
    at org.callisto.quizmaker.QuizMaker.run(QuizMaker.java:65) ~[main/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]

触发场景

调用以下代码时触发异常:

boolean output = service.activeSubjectHasTopics();

对应的Service方法:

public boolean activeSubjectHasTopics()
{
    if (activeSubject == null)
    {
        throw new NullPointerException(Resources.EXCEPTION_SUBJECT_NULL);
    }

    return !activeSubject.getTopics().isEmpty();
}

调用上下文代码:

private void createNewQuestion(View view, QuizMakerService service)
{
    int subjectId = chooseOrAddSubject(view, service);

    service.setActiveSubject(subjectId);

    if (checkIfActiveSubjectHasTopics(view, service))
    {
        chooseOrAddTopic(view, service, subjectId);
    }

    do
    {
        createQuestion(view, service);

        createAnswers(view, service);
    }
    while(view.askToCreateAnotherQuestion());

    service.saveDataToFile();

    prepareMainMenu(view, service);
}


private boolean checkIfActiveSubjectHasTopics(View view, QuizMakerService service)
{
    boolean output = service.activeSubjectHasTopics();

    if (!output)
    {
        view.printNoTopicsWarning(service.getActiveSubjectName());

        String topicName = readTopicName(view);

        createNewTopic(service, topicName);
    }

    return output;
}

解决方案

1. 修复实体加载逻辑

异常核心原因是activeSubject是Hibernate代理对象,获取它时的Session已关闭,后续调用getTopics()无法初始化代理。

可以在Repository中新增带关联加载的查询方法:

@Query("SELECT s FROM Subject s LEFT JOIN FETCH s.topics WHERE s.subjectId = :id")
Optional<Subject> findByIdWithTopics(@Param("id") Integer id);

然后在setActiveSubject方法中调用该方法获取Subject,确保topics集合已被主动加载,避免延迟加载触发异常。

2. 调整事务边界

将需要访问实体关联集合的业务流程包裹在事务中,比如在setActiveSubject或activeSubjectHasTopics方法上添加@Transactional注解,确保从获取实体到访问关联数据的整个过程处于同一个Session内。

如果是多步骤的业务逻辑(比如createNewQuestion整个流程),也可以在对应的Service方法上添加@Transactional,覆盖完整的业务操作周期。

3. 避免长期持有代理对象

不要将Hibernate代理对象长期保存在Service的成员变量中(如activeSubject),Session关闭后代理将无法初始化。建议每次需要访问时从Repository重新获取,或者确保获取时已加载所有需要的关联数据。


内容的提问来源于stack exchange,提问作者Emiliano De Santis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 21:05:21