我的数据库结构通过连接表将用户实体与角色耦合,用户类拥有关系并拥有引用角色实体集合的字段。
我想使用 SelectOneMenu 将用户链接到相关角色,但这当然会返回角色对象而不是集合。尽管 SelectManyMenu 可以通过返回 Collection 来绕过此问题,但用户应该只分配一个角色。我使用 Omnifaces SelectItemsConverter 类来处理 Object<>String 转换,并认为我可以重写 getAsObject 以返回包含单个选定角色的集合。然而,这不起作用 - 调试表单的处理表明,在 JSF 验证过程中,getAsObject 方法被调用了 3 次(其中三个关联的 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 自动生成的代码中提取的,因此可能不是最佳方法。
您提供的代码显示您正在不适当的位置执行持久层访问。假设您的托管 bean 是
@ViewScoped
,那么应该使用 preRenderView
事件或 @PostConstruct
来调用持久层并 将所有加载的值保留在变量中。稍后,您将在 getter 方法中返回该变量。在最终用户执行另一个请求之前,无需再次访问数据库。
因此,在 getter 方法中执行此类操作被认为是一种不好的做法。 JSF 每次需要访问属性时都会调用该方法,因为它不会缓存该值(您需要在自己的 bean 中执行此操作,并将它们存储在变量中)。
在
Converter
类中访问数据库也被认为是不好的,但应该使用 Omnifaces 转换器来避免。
您似乎使用
getSelectItems
方法来填充可用值。但是,这不是您从视图中调用的内容,因为它有两个参数。你从什么方法调用它?您是否一次又一次地加载List<?> entities
?
与问题无关,我建议您使用 Java 名称约定。以
get
开头的方法是 getters
,它们不接收任何参数。 setter
只接收一个参数,与它们必须设置的内容相对应。任何不同的方法不应以 get
或 set
开头(您可以使用 load
或 prepare
前缀)。
另见: