【问题标题】:Hibernate LazyInitializationException while using collection in Spring JSP page在 Spring JSP 页面中使用集合时出现 Hibernate LazyInitializationException
【发布时间】:2013-10-03 13:52:46
【问题描述】:

我有这样的实体:

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

如果此集合已加载到事务中,这怎么可能?我只是想循环,此时休眠不应该加载任何东西,因为它已经加载了。为什么会发生这种奇怪的事情?

【问题讨论】:

  • 它正在初始化集合而不是该集合的内容(每行都有一堆代理)。此外(恕我直言)使您的网络方法具有事务性是一个坏主意,您的服务应该是事务性的。使用OpenSessionInViewFilter 或使用Hibernate.initialize 方法正确初始化集合。
  • OpenSessionInViewFilter 在我的情况下不起作用,因为我通过 JPA 使用 Hibernate,有帮助的是:OpenEntityManagerInViewFilter,现在它可以工作了,但这是个好方法吗?
  • 你没有提到你在哪里使用 JPA,所以我假设普通休眠。它是否是一个好的解决方案取决于您的应用程序。没有适用的一般规则(我猜只有意见:))。如果您不希望这样做,那么您需要正确初始化您的集合(通过 Hibernate.initialize 方法或创建自定义查询以加载所需的数据(强制急切获取依赖资源)。
  • 实际上 OpenEntityManagerInViewFilter 是第二个最糟糕的解决方案(比使用 fetch = FetchType.EAGER 更好,因为您可以将其限制为某个 url,但不好,因为您在用户请求期间保持会话打开)。使用 Hibernate.initialize 更好,因为您可以在特定的 @Transactional 服务方法中执行此操作。让 @Many/OnetoMany 集合的其余部分变得懒惰并使用自定义连接获取查询是最好的解决方案 => 对于 Hibernate.initialize,只有一个查询而不是两个或更多。

标签: java spring hibernate jsp spring-mvc


【解决方案1】:

在视图中,实体管理器已经关闭,因此您的集合中的元素无法检索那里的属性。您在控制器中编写的代码不会初始化集合中的元素(它是一个 LAZY 集合),而只会初始化集合(而不是其中的元素)。

通过在您的 Web 配置中配置 OpenEntityManagerInViewFilter 来强制实体管理器保持打开状态。

或者更改您的控制器代码以包含对 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
}

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

【讨论】:

  • 谢谢。现在我有一些选择。将进行一些调查,这是最佳做法。
  • 让集合变得懒惰并使用 fetch join(通过 Hibernate 或 JPA)是最好的解决方案,因为它只使用一个查询。
【解决方案2】:

改变

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

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 使用的。
  • 顺便说一句,Hibernate 也有一个 fetch join(在这里搜索“fetch join”docs.jboss.org/hibernate/orm/3.3/reference/en/html/…)。它比 Hibernate.initialize 更好,因为它只使用一个查询。
【解决方案3】:

从 EntityManager 加载 AssessmentPart 将加载评估 @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中通过Assessment获取AssessmentParts列表应该可以解决问题。

【讨论】:

  • 从评估中加载评估部分工作正常,我在同一个事务中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-11
  • 2012-12-28
  • 1970-01-01
  • 2019-05-05
  • 2021-04-28
  • 1970-01-01
相关资源
最近更新 更多