繁体   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