簡體   English   中英

如何在 JPA-Hibernate 中使用嵌套的 collections 避免 LazyInitializationException?

[英]How to avoid LazyInitializationException with nested collections in JPA-Hibernate?

強制性背景信息:

作為學習 Spring 的學習的一部分,我構建了我常用的應用程序 - 一個保存問題並隨后使用它們創建隨機測驗的小工具。

每個主題可以有任意數量的主題,這些主題又可以有任意數量的問題,而這些問題又可以有任意數量的答案。

現在,問題正確:

我不斷收到 LazyInitializationExceptions。

我最后嘗試的:

我將幾乎所有用於 Set 的集合類型都更改了。 也很想將 enable_lazy_load_no_trans 屬性設置為 true,但我一直認為這是要避免的反模式。

實體:(僅顯示字段以避免代碼引起的疲勞牆)

主題:

@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;
}

話題:

@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;
}

問題:

@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;
}

回答:

@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();
}

現在,一大塊文本 Spring 引導向我投擲 - 堆棧跟蹤:

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();

反過來,它在服務 class 上調用此方法:

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

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

在此上下文中調用 activeSubjectHasTopics 方法:

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;
}

如果您將結構更改為設置,則無濟於事。 如果您需要獲取實體,您需要在 hql 查詢中顯式包含 FETCH 子句。 您將通過查看 Hibernate 文檔來按自己的方式工作: https://docs.jboss.org/hibernate/orm/3.3/performanceing.html/

由於 Christian Beikov 的評論,我能夠找到問題的原因 - 引用:

你從哪里得到這個 activeSubject object? 如果您不將其作為 activeSubjectHasTopics 中事務的一部分加載,那么這將不起作用,因為此時 object 已經分離,因為它是通過不同的事務加載的。

activeSubject object 被定義為包含activeSubjectHasTopics方法的服務 class 的一部分,並如他所指出的那樣由不同的事務初始化。

我能夠通過將服務 class 注釋為@Transactional並存儲我需要的對象的 ID 而不是對象本身來解決問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM