在JSF中使用JPA实体.哪个是防止LazyInitializationException的最佳策略?

bit*_*tec 10 jsf hibernate jpa lazy-initialization

希望听听有关从JSF UI编辑JPA实体的最佳实践的专家.

所以,关于这个问题的几句话.

想象一下,我有持久化对象MyEntity,我将其取出进行编辑.在DAO层我使用

return em.find(MyEntity.class, id);
Run Code Online (Sandbox Code Playgroud)

MyEntity在"父"实体上返回带有代理的实例 - 想象其中一个是MyParent.MyParent被提取为代理问候语@Access(AccessType.PROPERTY):

@Entity
public class MyParent {

    @Id
    @Access(AccessType.PROPERTY)    
    private Long id;
    //...
}
Run Code Online (Sandbox Code Playgroud)

和MyEntity有它的参考:

@ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
private MyParent myParent;
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.在UI中,我只是直接使用获取的对象而不创建任何值对象,并使用选择列表中的父对象:

<h:selectOneMenu value="#{myEntity.myParent.id}" id="office">
    <f:selectItems value="#{parents}"/>
</h:selectOneMenu>
Run Code Online (Sandbox Code Playgroud)

一切都好,没有LazyInitializationException发生.但是当我保存对象时,我收到了

LazyInitializationException: could not initialize proxy - no Session
Run Code Online (Sandbox Code Playgroud)

MyParent代理setId()方法.

如果我改变MyParent关系,我可以轻松解决问题EAGER

@ManyToOne(fetch = FetchType.EAGER)
private MyParent myParent;
Run Code Online (Sandbox Code Playgroud)

或使用获取对象left join fetch p.myParent(实际上我现在这样做).在这种情况下,保存操作正常,并且关系MyParent透明地更改为新对象.不需要执行其他操作(手动复制,手动参考设置).非常简单方便.

但是.如果对象引用了10个其他对象 - em.find()将导致10个额外的连接,这不是一个好的数据库操作,特别是当我根本不使用引用对象状态时.我只需要 - 是对象的链接,而不是它们的状态.

这是一个全球性的问题,我想知道,JSF专家如何在他们的应用程序中处理JPA实体,这是避免额外连接的最佳策略LazyInitializationException.

扩展的持久化上下文对我来说不合适.

谢谢!

Bal*_*usC 5

您应该准确地提供视图期望的模型。

如果 JPA 实体恰好与所需模型完全匹配,则立即使用它。

如果 JPA 实体碰巧具有太少或太多的属性,则使用 DTO(子类)和/或具有更具体 JPQL 查询的构造函数表达式,如有必要,使用显式FETCH JOIN. 或者可能使用 Hibernate 特定的获取配置文件,或 EclipseLink 特定的属性组。否则,它可能会导致所有地方的延迟初始化异常,或者消耗不必要的内存。

“在视图中打开会话”模式是一个糟糕的设计。您基本上是在整个HTTP 请求-响应处理期间保持单个 DB 事务处于打开状态。是否开始新的数据库事务的控制权完全从您手中夺走。当业务逻辑需要时,您不能在同一个 HTTP 请求期间生成多个事务。请记住,当单个查询在事务期间失败时,整个事务都会回滚。另请参阅何时需要或方便使用 Spring 或 EJB3 或同时使用它们?

从 JSF 的角度来看,“在视图中打开会话”模式还意味着可以在呈现响应的同时执行业务逻辑。这与其他异常处理并不能很好地配合,其目的是向最终用户显示自定义错误页面。如果在呈现响应的中途抛出业务异常,最终用户因此已经收到响应标头和部分 HTML,则服务器无法再清除响应以显示一个漂亮的错误页面。此外,根据 JSF多次调用 getter 的原因,在 getter 方法中执行业务逻辑在 JSF 中的做法令人不悦。

在渲染响应阶段开始之前,只需通过托管 bean 操作/侦听器方法中的常用服务方法调用准确地准备视图所需的模型。例如,一种常见的情况是手头有一个现有的(非托管的)父实体和一个延迟加载的一对多子属性,并且您希望通过 ajax 操作在当前视图中呈现它,那么您应该只是让ajax侦听器方法在服务层中获取并初始化它。

<f:ajax listener="#{bean.showLazyChildren(parent)}" render="children" />
Run Code Online (Sandbox Code Playgroud)
public void showLazyChildren(Parent parent) {
    someParentService.fetchLazyChildren(parent);
}
Run Code Online (Sandbox Code Playgroud)
public void fetchLazyChildren(Parent parent) {
    parent.setLazyChildren(em.merge(parent).getLazyChildren()); // Becomes managed.
    parent.getLazyChildren().size(); // Triggers lazy initialization.
}
Run Code Online (Sandbox Code Playgroud)

特别是在 JSFUISelectMany组件中,还有另一个完全出乎意料的可能原因LazyInitializationException:在保存所选项目期间,JSF 需要在使用所选项目填充它之前重新创建底层集合,但是如果它恰好是持久层特定的延迟加载集合执行,那么这个异常也会被抛出。解决方案是collectionTypeUISelectMany组件的属性显式设置为所需的“普通”类型。

<h:selectManyCheckbox ... collectionType="java.util.ArrayList">
Run Code Online (Sandbox Code Playgroud)

在 com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel 处的 org.hibernate.LazyInitializationException中详细询问和回答。

也可以看看:


Aug*_*sto 2

一种非常常见的方法是在视图过滤器中创建一个开放的实体管理器。Spring 提供了一个(请查看此处)。

我看不出您正在使用 Spring,但这并不是真正的问题,您可以根据您的需要调整该类中的代码。您还可以检查过滤器Open Session in View,它的作用相同,但它保持休眠会话打开而不是实体管理器。

这种方法可能不适合您的应用程序,SO中有一些关于这种模式或反模式的讨论。链接1 . 我认为对于大多数应用程序(小型应用程序,少于 20 个并发用户),此解决方案工作得很好。

编辑

这里有一个 Spring 类与 FSF 关系更好


归档时间:

查看次数:

4928 次

最近记录:

8 年,1 月 前