在 JSF 中使用 JPA 实体.哪个是防止 LazyInitializationException 的最佳策略? [英] Using JPA entities in JSF. Which is the best strategy to prevent LazyInitializationException?

查看:12
本文介绍了在 JSF 中使用 JPA 实体.哪个是防止 LazyInitializationException 的最佳策略?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

想听听专家关于从 JSF UI 编辑 JPA 实体的最佳实践的意见.

Would like to hear experts on best practice of editing JPA entities from JSF UI.

那么,就这个问题说两句.

So, a couple of words about the problem.

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

Imagine I have the persisted object MyEntity and I fetch it for editing. In DAO layer I use

return em.find(MyEntity.class, id);

返回带有父"实体代理的 MyEntity 实例 - 想象其中之一是 MyParent.MyParent 作为代理问候被获取到 @Access(AccessType.PROPERTY):

Which returns MyEntity instance with proxies on "parent" entities - imagine one of them is MyParent. MyParent is fetched as the proxy greeting to @Access(AccessType.PROPERTY):

@Entity
public class MyParent {

    @Id
    @Access(AccessType.PROPERTY)    
    private Long id;
    //...
}

并且 MyEntity 引用了它:

and MyEntity has the reference to it:

@ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
private MyParent myParent;

到目前为止一切顺利.在 UI 中,我只是直接使用获取的对象而不创建任何值对象,并使用选择列表中的父对象:

So far so good. In UI I simply use the fetched object directly without any value objects created and use the parent object in the select list:

<h:selectOneMenu value="#{myEntity.myParent.id}" id="office">
    <f:selectItems value="#{parents}"/>
</h:selectOneMenu>

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

Everything is rendered ok, no LazyInitializationException occurs. But when I save the object I recieve the

LazyInitializationException: could not initialize proxy - no Session

MyParent 代理 setId() 方法上.

on MyParent proxy setId() method.

如果我更改 MyParentEAGER

@ManyToOne(fetch = FetchType.EAGER)
private MyParent myParent;

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

or fetch the object using left join fetch p.myParent (actually that's how I do now). In this case the save operation works ok and the relation is changed to the new MyParent object transparently. No additional actions (manual copies, manual references settings) need to be done. Very simple and convenient.

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

BUT. If the object references 10 other object - the em.find() will result 10 additional joins, which isn't a good db operation, especially when I don't use references objects state at all. All I need - is links to objects, not their state.

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

This is a global issue, I would like to know, how JSF specialists deal with JPA entities in their applications, which is the best strategy to avoid both extra joins and LazyInitializationException.

扩展持久性上下文不适合我.

Extended persistence context isn't ok for me.

谢谢!

推荐答案

你应该提供视图期望的模型.

You should provide exactly the model the view expects.

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

If the JPA entity happens to match exactly the needed model, then just use it right away.

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

If the JPA entity happens to have too few or too much properties, then use a DTO (subclass) and/or a constructor expression with a more specific JPQL query, if necessary with an explicit FETCH JOIN. Or perhaps with Hibernate specific fetch profiles, or EclipseLink specific attribute groups. Otherwise, it may either cause lazy initializtion exceptions over all place, or consume more memory than necessary.

查看中的打开会话"模式是一个糟糕的设计.在整个 HTTP 请求-响应处理期间,您基本上保持打开单个数据库事务.您完全无法控制是否开始新的数据库事务.当业务逻辑需要时,您不能在同一个 HTTP 请求期间生成多个事务.请记住,当单个查询在事务期间失败时,整个 事务将回滚.另见 什么时候需要或方便使用 Spring 或 EJB3 或它们一起使用?

The "open session in view" pattern is a poor design. You're basically keeping a single DB transaction open during the entire HTTP request-response processing. Control over whether to start a new DB transaction or not is completely taken away from you. You cannot spawn multiple transactions during the same HTTP request when the business logic requires so. Keep in mind that when a single query fails during a transaction, then the entire transaction is rolled back. See also When is it necessary or convenient to use Spring or EJB3 or all of them together?

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

In JSF perspective, the "open session in view" pattern also implies that it's possible to perform business logic while rendering the response. This doesn't go very well together with among others exception handling whereby the intent is to show a custom error page to the enduser. If a business exception is thrown halfway rendering the response, whereby the enduser has thus already received the response headers and a part of the HTML, then the server cannot clear out the response anymore in order to show a nice error page. Also, performing business logic in getter methods is a frowned upon practice in JSF as per Why JSF calls getters multiple times.

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

Just prepare exactly the model the view needs via usual service method calls in managed bean action/listener methods, before render response phase starts. For example, a common situation is having an existing (unmanaged) parent entity at hands with a lazy loaded one-to-many children property, and you'd like to render it in the current view via an ajax action, then you should just let the ajax listener method fetch and initialize it in the service layer.

<f:ajax listener="#{bean.showLazyChildren(parent)}" render="children" />

public void showLazyChildren(Parent parent) {
    someParentService.fetchLazyChildren(parent);
}

public void fetchLazyChildren(Parent parent) {
    parent.setLazyChildren(em.merge(parent).getLazyChildren()); // Becomes managed.
    parent.getLazyChildren().size(); // Triggers lazy initialization.
}

特别是在 JSF UISelectMany 组件中,还有另一个完全出乎意料的可能导致 LazyInitializationException 的原因:在保存所选项目期间,JSF 需要在填充之前重新创建底层集合它与选定的项目,但是如果它恰好是持久层特定的延迟加载集合实现,那么也会抛出此异常.解决方案是将 UISelectMany 组件的 collectionType 属性显式设置为所需的普通"属性.类型.

Specifically in JSF UISelectMany components, there's another, completely unexpected, probable cause for a LazyInitializationException: during saving the selected items, JSF needs to recreate the underlying collection before filling it with the selected items, however if it happens to be a persistence layer specific lazy loaded collection implementation, then this exception will also be thrown. The solution is to explicitly set the collectionType attribute of the UISelectMany component to the desired "plain" type.

<h:selectManyCheckbox ... collectionType="java.util.ArrayList">

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

这篇关于在 JSF 中使用 JPA 实体.哪个是防止 LazyInitializationException 的最佳策略?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆