在Spring JSP页面中使用集合时出现Hibernate LazyInitializationException

4

我有这样的实例:

@Entity
@Table(name = "ASSESSMENT")
public class Assessment {

    //All other fields..

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "assessment")
    @OrderBy(value = "order ASC")
    private List<AssessmentPart> assessmentParts = new LinkedList<>();

    public List<AssessmentPart> getAssessmentParts() {
        return assessmentParts;
    }

    //All other getters/setters
}

另一个例子:
@Entity
@Table(name = "ASSESSMENT_PART")
public class AssessmentPart {

    //All other fields

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ASSESSMENT_ID", nullable = false)
    private Assessment assessment;

    public Assessment getAssessment() {
        return assessment;
    }

    public void setAssessment(Assessment assessment) {
        this.assessment = assessment;
    }

    //All other getters/setters
}

评估部分从评估实体进行延迟加载。我还有一个Spring控制器方法,它是Transactional的,并从数据库加载评估部分:

@Transactional
public void doSomething(String partId, Map<String, Object> model) {

    AssessmentPart assessmentPart = //laods a part with entity manager
    Assessment assessment = assessmentPart.getAssessment(); //Getting the assessments

    model.put("assessmentParts", assessment.getAssessmentParts()); //adding all assessments parts into spring model map
}

一旦我将评估部分添加到Spring模型映射中,它们就会在我的JSP页面中可用,并且我使用JSTL循环遍历它们:

<c:forEach var="assessmentPart" items="${assessmentParts}">
     //Not loading any lazy stuff, just getting an ID of assessment part
</c:forEach>

该异常是从此JSP页面抛出的:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: my.package.something.Assessment.assessmentParts, could not initialize proxy - no Session

如果这个集合已经在事务中被加载了,那么这怎么可能呢?我只是想循环遍历,此时Hibernate不应该再加载任何东西,因为它已经加载了。为什么会发生这种奇怪的事情呢?

它正在初始化集合,而不是该集合的内容(您会为每一行获取一组代理)。另外,我个人认为将您的 Web 方法设为事务性是一个不好的主意,应该将服务设置为事务性。可以使用OpenSessionInViewFilter 或者 使用 Hibernate.initialize 方法来正确初始化该集合。 - M. Deinum
OpenSessionInViewFilter在我的情况下无法工作,因为我是通过JPA使用Hibernate,帮助我的是:OpenEntityManagerInViewFilter,现在它可以工作了,但这是一个好方法吗? - user2219247
你没有提到你使用JPA,所以我假设你使用的是纯Hibernate。它是否是一个好的解决方案取决于你的应用程序。没有通用的规则可供应用(只有我的看法 :))。如果你不想这样,那么你需要正确初始化你的集合(可以通过Hibernate.initialize方法或创建自定义查询来加载所需的数据(强制获取依赖资源)。 - M. Deinum
实际上,OpenEntityManagerInViewFilter 是第二差的解决方案(比使用 fetch = FetchType.EAGER 更好,因为您可以将其限制为特定的 URL,但是不好,因为您在用户请求期间保持会话处于打开状态)。 使用 Hibernate.initialize 更好,因为您可以在特定的 @Transactional 服务方法中执行该操作。 将其余的@Many/OnetoMany 集合设置为 Lazy 并使用自定义 join fetch 查询是最佳解决方案=> 只需一个查询,而不是 Hibernate.initialize 的两个或更多查询。 - despot
3个回答

8
在视图中,EntityManager已经关闭,因此集合中的元素无法检索其属性。您在控制器中编写的代码未初始化集合中的元素(它是一个LAZY集合),而只有集合被初始化(而不是其中的元素)。
要么通过在web配置中配置OpenEntityManagerInViewFilter来强制EntityManager保持打开状态。
要么更改您的控制器代码以调用Hibernate.initialize来正确初始化您的集合。
@Transactional
public void doSomething(String partId, Map<String, Object> model) {

    AssessmentPart assessmentPart = //laods a part with entity manager
    Assessment assessment = assessmentPart.getAssessment(); //Getting the assessments
    Hibernate.initialize(assesment.getAssesmentParts()); // Init collection
    model.put("assessmentParts", assessment.getAssessmentParts()); //adding all assessments parts into spring model map
}

要么创建一个自定义查询,强制集合进行急切获取;要么这样做。

谢谢。现在我有一些选择。将进行一些调查,找出最佳实践。 - user2219247
将集合设为延迟加载,并使用Hibernate或JPA的fetch join是最佳解决方案,因为它只使用一个查询。 - despot

2

更改

model.put("assessmentParts", assessment.getAssessmentParts());

to

Hibernate.initialize(assessment.getAssessmentParts());

初始化您的集合。

assessment.getAssessmentParts() 方法调用并不会初始化您的集合。这个方法调用只会返回一个集合封装器,您需要将这个封装器传递给 Hibernate.initialize 方法进行手动初始化。

编辑:

使用 JPA,您可以通过 JPQL Fetch Joins 在需要的地方同时获取 AssessmentPartsAssessment,覆盖默认行为:

SELECT a FROM Assessment asmt LEFT JOIN FETCH asmt.assessmentParts WHERE asmt.id = :id

我并没有直接使用Hibernate,而是通过JPA来使用它。 - user2219247
顺便提一下,Hibernate也有一个fetch join(在这里搜索“fetch join”https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html)。它比Hibernate.initialize更好,因为它只使用一个查询。 - despot

0
从EntityManager加载AssessmentPart将会加载Assessment:@ManyToOne(fetch = FetchType.EAGER)
AssessmentPart assessmentPart = //laods a part with entity manager
Assessment assessment = assessmentPart.getAssessment();

尝试从评估中提取AssessmentParts是行不通的,因为它不是@OneToMany(fetch = FetchType.LAZY),而且会话已经关闭。

model.put("assessmentParts", assessment.getAssessmentParts());

从EntityManager中获取评估的AssessmentParts列表,使用新方法可以解决问题。

从评估作品中加载评估部分非常顺利,我在同一事务中。 - user2219247

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接