【问题标题】:Why is getAsObject() method of JSF converter called multiple times?为什么多次调用 JSF 转换器的 getAsObject() 方法?
【发布时间】:2013-12-18 16:42:24
【问题描述】:

我的数据库结构通过连接表将用户实体与角色耦合,用户类拥有关系并拥有一个引用角色实体集合的字段。

我想使用 SelectOneMenu 将用户链接到相关角色,但这当然会返回角色对象而不是集合。尽管 SelectManyMenu 会通过返回一个集合来绕过这个问题,但用户应该只分配一个角色。我正在使用 Omnifaces SelectItemsConverter 类来处理 ObjectString 转换,并认为我可以重写 getAsObject 以返回包含单个选定角色的集合。但是,这不起作用 - 调试表单处理显示在 JSF 验证过程中调用了 3 次 getAsObject 方法(其中三个关联的 SQL 调用查询 Roles 表)。第一次,选择的字符串值被传递,但接下来的两次调用包含一个空字符串,但尽管如此,角色设置器仅被调用一次(在更新模型阶段)并接收到正确的值。

我已经通过将角色实体添加到集合中的代码处理用户实体的持久性到数据库中来解决这个问题,但这似乎不是很优雅。如果可能的话,我宁愿将转换为用户实体所需的对象都放在一个地方。更重要的是,我不明白为什么我会看到这种行为,无论是正常的还是错误代码的结果,以及多个数据库查询在生产场景中可能产生的影响。不知道困扰着我(不是双关语!),因为这表明我无法理解 JSF 生命周期和 servlet 处理的来龙去脉。

包含 Role 实体的 SelectOneMenu 由此方法填充:

public static SelectItem[] getSelectItems(List<?> entities, boolean selectOne) {
    int size = selectOne ? entities.size() + 1 : entities.size();
    SelectItem[] items = new SelectItem[size];
    int i = 0;
    if (selectOne) {
        items[0] = new SelectItem("", "Please select one item");
        i++;
    }
    for (Object x : entities) {
        items[i++] = new SelectItem(x, x.toString());
    }
    return items;
}

RolesSelectItemConverter.getAsObjec() 方法与标准 Omnifaces 实现相同,只是调用 Logger 以在调用该方法时进行标记。

调试跟踪:

INFO:   START PHASE PROCESS_VALIDATIONS 3
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@368dde0b} and value {exhibitor}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.036--ServerSession(1830578621)--Connection(2011154278)--Thread(Thread[http-listener-1(1),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.039--ServerSession(1830578621)--Connection(1600432374)--Thread(Thread[http-listener-1(1),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@368dde0b} and value {}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.042--ServerSession(1830578621)--Connection(947366464)--Thread(Thread[http-listener-1(1),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.044--ServerSession(1830578621)--Connection(966514315)--Thread(Thread[http-listener-1(1),5,main])--SELECT t1.ID, t1.EMAIL, t1.FORENAME, t1.INACTIVE, t1.PASSWORD, t1.PHONE_NUMBER, t1.REGISTERED_DATETIME, t1.SURNAME, t1.ADDRESS_ID FROM USER_ROLE_MAP t0, USERS t1 WHERE ((t0.ROLE_ID = ?) AND (t1.ID = t0.USER_ID))
    bind => [2]
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.048--ServerSession(1830578621)--Connection(119432927)--Thread(Thread[http-listener-1(1),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@368dde0b} and value {}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 19:59:50.051--ServerSession(1830578621)--Connection(1342595455)--Thread(Thread[http-listener-1(1),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   END PHASE PROCESS_VALIDATIONS 3
INFO:   START PHASE UPDATE_MODEL_VALUES 4
INFO:   SelectedRole setter called. Value: exhibitor
INFO:   END PHASE UPDATE_MODEL_VALUES 4

我刚刚获得了更多调试信息,查看了 Role.equals() 被调用的次数以及比较对象是什么。它使阅读变得有趣!我可以通过可用的角色实体理解第一组迭代(当用户选择的角色被识别时迭代停止),但通过整个角色表的第二组迭代让我感到困惑。如果正在查看一张大表(例如国家或州),我的代码似乎效率很低!

INFO:   START PHASE PROCESS_VALIDATIONS 3
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@18a45031} and value {eventOrg}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 20:54:37.596--ServerSession(1165546789)--Connection(544502930)--Thread(Thread[http-listener-1(3),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 20:54:37.599--ServerSession(1165546789)--Connection(1032878258)--Thread(Thread[http-listener-1(3),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@18a45031} and value {}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 20:54:47.789--ServerSession(1165546789)--Connection(948440937)--Thread(Thread[http-listener-1(3),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   equals() called - comparison object {}
INFO:   equals() called - comparison object {delegate}
INFO:   equals() called - comparison object {exhibitor}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   equals() called - comparison object {eventOrg}
INFO:   [EL Fine]: sql: 2013-12-02 20:54:47.793--ServerSession(1165546789)--Connection(1537445637)--Thread(Thread[http-listener-1(3),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   RoleSelectItemsConverter.getAsObject called for UOComponent {javax.faces.component.html.HtmlSelectOneMenu@18a45031} and value {}
INFO:   Attempting find all: com.rms.myconferenceprofile.entity.Role
INFO:   [EL Fine]: sql: 2013-12-02 20:55:08.661--ServerSession(1165546789)--Connection(697639746)--Thread(Thread[http-listener-1(3),5,main])--SELECT ID, DESCRIPTION, ROLE_NAME FROM ROLES
INFO:   equals() called - comparison object {}
INFO:   equals() called - comparison object {delegate}
INFO:   equals() called - comparison object {exhibitor}
INFO:   equals() called - comparison object {eventOrg}
INFO:   equals() called - comparison object {venueOrg}
INFO:   equals() called - comparison object {ADMIN}
INFO:   END PHASE PROCESS_VALIDATIONS 3

Xtreme Biker 提供帮助后的其他信息:

我没有使用 JSF Managed Beans,因为我知道支持将在下一个版本后撤销。 getSelectItems 方法包含在未明确限定范围的实用程序类中 - 由于我使用的是 CDI,我猜唯一等同于 @ViewScope 的范围是 @ConversationScope?此方法是从 NetBeans 自动生成的代码中提取的,因此可能不是最佳的。

【问题讨论】:

  • 没有人吗?连BalusC都没有?这些多个 SQL 调用让我担心,因为这似乎是一个巨大的潜在服务器负载,我认为这是可以避免的......

标签: jpa jsf-2 entity converter


【解决方案1】:

您提供的代码显示您在不适当的地方执行持久层访问。假设您的托管 bean 是@ViewScoped,那么应该使用preRenderView 事件或@PostConstruct 来调用持久层并将所有加载的值保留在变量中。稍后,您将在 getter 方法中仅返回这些变量。在最终用户执行另一个请求之前,无需再次访问数据库。

因此,在 getter 方法中执行此类操作被认为是一种不好的做法。 JSF 将在每次需要访问属性时调用该方法,因为它不会缓存该值(您需要在自己的 bean 中执行此操作,并将它们存储在变量中)。

Converter 类中访问数据库也被认为是不好的,但应该使用 Omnifaces 转换器来避免。

您似乎使用getSelectItems 方法来填充您的可用值。但是,这不是您从视图中调用的内容,因为它有两个参数。你用什么方法调用它?您是否一次又一次地加载List&lt;?&gt; entities

与问题无关我建议您使用 Java 命名约定。以get 开头的方法是getters,它们不接收任何参数。 setter 只接收一个参数,对应于他们必须设置的参数。任何不同的方法都不应该以getset 开头(您可以使用loadprepare 前缀)。

另请参阅:

【讨论】:

  • 感谢您的帮助。我在上面的原始问题中添加了一些说明,这可能会(也可能不会)改变您的建议,但如果我理解正确,我可以将实体加载到支持 bean 中,或者 @CoversationScoped CDI bean 可以解决吗?当我使用 Omnifaces 时,我不认为数据库命中源自那里,尽管我没有调试跟踪来确认或反驳这一点。感谢您在命名约定方面的 cmets - 我从 Netbeans autogen 代码中提取了该方法,所以这只是我的懒惰!
猜你喜欢
  • 2013-05-02
  • 2014-09-01
  • 1970-01-01
  • 2011-01-20
  • 2010-11-11
相关资源
最近更新 更多