Overriding/implementing getRowKey() 和 getRowData() 方法,当有一个复合主键将多个列组合为一个行键时
Overriding/implementing getRowKey() and getRowData() methods, when there is a composite primary key which combines multiple columns as a row key
我在 MySQL 数据库中有一个 table。不幸的是,GlassFish Server 中的 JAAS authentication/authorization 需要一个复合主键。
mysql> desc group_table;
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| user_group_id | varchar(176) | NO | PRI | NULL | |
| group_id | varchar(15) | NO | PRI | NULL | |
+---------------+--------------+------+-----+---------+-------+
2 rows in set (0.05 sec)
table 包含以下格式的数据。
mysql> select * from group_table;
+-------------------------+------------+
| user_group_id | group_id |
+-------------------------+------------+
| you123@gmail.com | ROLE_ADMIN |
| you123@gmail.com | ROLE_USER |
| you123@ymail.com | ROLE_USER |
| you123@hotmail.com | ROLE_USER |
| you123@yahoo.com | ROLE_USER |
+-------------------------+------------+
5 rows in set (0.00 sec)
A <p:dataTable>
with rowKey
工作正常,当 lazy
设置为 false
.
<p:dataTable rowKey="#{row.groupTablePK.userGroupId} #{row.groupTablePK.groupId}">
...
</p:dataTable>
GroupTablePK
是一个 @Embeddable
class (JPA)。我想不需要有关此 class 的详细信息。
然而,当 lazy
在 <p:dataTable>
上启用时,需要实施 getRowKey()
和 getRowData()
方法。
当复合主键需要列的组合作为行键 - 唯一的行标识符时,如何做到这一点?
@Named
@ViewScoped
public class UserAuthorityManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
}
@Override
public List<GroupTable> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
//... setRowCount(rowCount);
//... Return a List<GroupTable> from a business Service.
}
}
以上实现尚未完成。
当使用这些实现在 <p:dataTable>
中选择一行时,getRowData()
方法中的 sout
语句显示以下内容。
Info: rowKey : entity.GroupTablePK[ userGroupId=you123@gmail.com
Info: rowKey : groupId=ROLE_USER ]
getRowKey()
方法 return 是 GroupTablePK
的实例,但 getRowData()
方法只接受 String 类型参数。它不是表示复合主键的对象(此处为 GroupTablePK
),因此可以将其类型转换为适当的对象类型(GroupTablePK
),并基于此创建 GroupTable
的实例可以从给定的 List<GroupTable>
获得 getRowData()
方法 return GroupTable
.
的那个实例
如何进行下一步?
该问题完全基于前一个问题:
编辑:
除了 GroupTablePK
中的 toString()
之外,我还有 hashcode()
和 equals()
实现。 GroupTablePK
returns return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
中的 toString()
方法,但当 <p:dataTable>
中的一行被选中时, getRowData()
方法被调用了两次。它 return 是 GroupTablePK
的字符串表示形式,在两个后续调用中分为两部分。在第一次调用中,它 returns entity.GroupTablePK[ userGroupId=aaa
然后在第二次调用中,它 returns groupId=ROLE_USER ]
.
它应该在一次调用中立即 return entity.GroupTablePK[ userGroupId=aaa, groupId=ROLE_USER ]
。
因此,这种比较 groupTable.getGroupTablePK().toString().equals(rowKey)
是不可能的,我在此之前一直在考虑 post。比如,
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
编辑 2:
以下是消除 JPA 噪声以重现问题的最短示例。
交替尝试,
- PrimeFaces 3.5
- PrimeFaces 4.0
- PrimeFaces 5.0
- PrimeFaces 5.1
- PrimeFaces 5.2
所有这些版本的 PrimeFaces 上的行为都保持不变。
托管 bean:
@Named
@ViewScoped
public class CompositeRowKeyManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private List<GroupTable> selectedValues; // Getter & setter.
private static final long serialVersionUID = 1L;
public CompositeRowKeyManagedBean() {}
private List<GroupTable> init() {
List<GroupTable> list = new ArrayList<GroupTable>();
GroupTablePK groupTablePK = new GroupTablePK("aaa", "ROLE_ADMIN");
GroupTable groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("bbb", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ccc", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ddd", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("eee", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
return list;
}
@Override
public List<GroupTable> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<GroupTable> list = init();
setRowCount(list.size());
return list;
}
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
public void onRowEdit(RowEditEvent event) {
GroupTablePK groupTablePK = ((GroupTable) event.getObject()).getGroupTablePK();
System.out.println("grouoId : " + groupTablePK.getGroupId() + " : userGroupId : " + groupTablePK.getUserGroupId());
}
}
数据table:
<p:dataTable var="row"
value="#{compositeRowKeyManagedBean}"
lazy="true"
editable="true"
selection="#{compositeRowKeyManagedBean.selectedValues}"
rows="50">
<p:column selectionMode="multiple"></p:column>
<p:ajax event="rowEdit" listener="#{compositeRowKeyManagedBean.onRowEdit}"/>
<p:column headerText="GroupId">
<h:outputText value="#{row.groupTablePK.userGroupId}"/>
</p:column>
<p:column headerText="UserGroupId">
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
<f:facet name="input">
<p:inputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
</p:cellEditor>
</p:column>
<p:column headerText="Edit">
<p:rowEditor/>
</p:column>
</p:dataTable>
当试图编辑一行时,将调用 onRowEdit()
方法。如前所述,getRowData()
被调用两次并在两个后续调用中生成行键的拆分。
这是两个域 classes GroupTable
和 GroupTablePK
.
public class GroupTable implements Serializable {
private static final long serialVersionUID = 1L;
protected GroupTablePK groupTablePK;
public GroupTable() {}
public GroupTable(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
public GroupTable(String userGroupId, String groupId) {
this.groupTablePK = new GroupTablePK(userGroupId, groupId);
}
public GroupTablePK getGroupTablePK() {
return groupTablePK;
}
public void setGroupTablePK(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
@Override
public int hashCode() {
int hash = 0;
hash += (groupTablePK != null ? groupTablePK.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTable)) {
return false;
}
GroupTable other = (GroupTable) object;
if ((this.groupTablePK == null && other.groupTablePK != null) || (this.groupTablePK != null && !this.groupTablePK.equals(other.groupTablePK))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTable[ groupTablePK=" + groupTablePK + " ]";
}
}
public class GroupTablePK implements Serializable {
private String userGroupId;
private String groupId;
public GroupTablePK() {}
public GroupTablePK(String userGroupId, String groupId) {
this.userGroupId = userGroupId;
this.groupId = groupId;
}
public String getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(String userGroupId) {
this.userGroupId = userGroupId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
@Override
public int hashCode() {
int hash = 0;
hash += (userGroupId != null ? userGroupId.hashCode() : 0);
hash += (groupId != null ? groupId.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTablePK)) {
return false;
}
GroupTablePK other = (GroupTablePK) object;
if ((this.userGroupId == null && other.userGroupId != null) || (this.userGroupId != null && !this.userGroupId.equals(other.userGroupId))) {
return false;
}
if ((this.groupId == null && other.groupId != null) || (this.groupId != null && !this.groupId.equals(other.groupId))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
}
}
通过阅读您的问题,我猜测 getRowKey() 方法必须 return 对单行唯一可识别的东西。可以理解的是,代表您的行的底层 JPA 实体有一个复合键对象,这很好。我认为问题是 Java 对象使用任何东西作为 Map 类型集合中的键,键对象必须重载并为 equals
和 hashCode
方法定义正确的实现.
我怀疑 Primefaces 可能正在使用某种 Map 来检索基于键的值。 String 类型通常是对象唯一键的良好候选者,因为 String 是不可变的并且具有 equals 和 hashCode 的正确实现。他们是一个很好的候选者,所以如果你必须将一个字符串传递给 getRowData
那么你总是可以在该对象上提供一个方法,该方法 return 是该对象的唯一字符串。例如,这可能是您为行数据对象提供的 hashCode 实现的 base 64 表示。
如果 String 不是必需参数,则只需为复合键对象实现 equals 和 hashCode,然后直接将其用作您的键。
我 运行 您的 MCVE(对此表示敬意!)并复制了它。行键在客户端似乎被解释为逗号分隔的字符串,以涵盖需要多项选择的情况。如果单个 rowkey 的字符串表示形式包含逗号(如您的情况),这将失败。您在 getRowData()
中获得的 rowkey 参数是它的明确证据:它们是原始值以逗号分隔时的结果。
因此,要解决此问题,您需要确保 getRowKey().toString()
不包含逗号。最好使用不同的分隔符。例如。一个下划线。
@Override
public Object getRowKey(GroupTable groupTable) {
GroupTablePK pk = groupTable != null ? groupTable.getGroupTablePK() : null;
return pk != null ? pk.getUserGroupId() + "_" + pk.getGroupId() : null;
}
我在 MySQL 数据库中有一个 table。不幸的是,GlassFish Server 中的 JAAS authentication/authorization 需要一个复合主键。
mysql> desc group_table;
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| user_group_id | varchar(176) | NO | PRI | NULL | |
| group_id | varchar(15) | NO | PRI | NULL | |
+---------------+--------------+------+-----+---------+-------+
2 rows in set (0.05 sec)
table 包含以下格式的数据。
mysql> select * from group_table;
+-------------------------+------------+
| user_group_id | group_id |
+-------------------------+------------+
| you123@gmail.com | ROLE_ADMIN |
| you123@gmail.com | ROLE_USER |
| you123@ymail.com | ROLE_USER |
| you123@hotmail.com | ROLE_USER |
| you123@yahoo.com | ROLE_USER |
+-------------------------+------------+
5 rows in set (0.00 sec)
A <p:dataTable>
with rowKey
工作正常,当 lazy
设置为 false
.
<p:dataTable rowKey="#{row.groupTablePK.userGroupId} #{row.groupTablePK.groupId}">
...
</p:dataTable>
GroupTablePK
是一个 @Embeddable
class (JPA)。我想不需要有关此 class 的详细信息。
然而,当 lazy
在 <p:dataTable>
上启用时,需要实施 getRowKey()
和 getRowData()
方法。
当复合主键需要列的组合作为行键 - 唯一的行标识符时,如何做到这一点?
@Named
@ViewScoped
public class UserAuthorityManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
}
@Override
public List<GroupTable> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
//... setRowCount(rowCount);
//... Return a List<GroupTable> from a business Service.
}
}
以上实现尚未完成。
当使用这些实现在 <p:dataTable>
中选择一行时,getRowData()
方法中的 sout
语句显示以下内容。
Info: rowKey : entity.GroupTablePK[ userGroupId=you123@gmail.com
Info: rowKey : groupId=ROLE_USER ]
getRowKey()
方法 return 是 GroupTablePK
的实例,但 getRowData()
方法只接受 String 类型参数。它不是表示复合主键的对象(此处为 GroupTablePK
),因此可以将其类型转换为适当的对象类型(GroupTablePK
),并基于此创建 GroupTable
的实例可以从给定的 List<GroupTable>
获得 getRowData()
方法 return GroupTable
.
如何进行下一步?
该问题完全基于前一个问题:
编辑:
除了 GroupTablePK
中的 toString()
之外,我还有 hashcode()
和 equals()
实现。 GroupTablePK
returns return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
中的 toString()
方法,但当 <p:dataTable>
中的一行被选中时, getRowData()
方法被调用了两次。它 return 是 GroupTablePK
的字符串表示形式,在两个后续调用中分为两部分。在第一次调用中,它 returns entity.GroupTablePK[ userGroupId=aaa
然后在第二次调用中,它 returns groupId=ROLE_USER ]
.
它应该在一次调用中立即 return entity.GroupTablePK[ userGroupId=aaa, groupId=ROLE_USER ]
。
因此,这种比较 groupTable.getGroupTablePK().toString().equals(rowKey)
是不可能的,我在此之前一直在考虑 post。比如,
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
编辑 2:
以下是消除 JPA 噪声以重现问题的最短示例。
交替尝试,
- PrimeFaces 3.5
- PrimeFaces 4.0
- PrimeFaces 5.0
- PrimeFaces 5.1
- PrimeFaces 5.2
所有这些版本的 PrimeFaces 上的行为都保持不变。
托管 bean:
@Named
@ViewScoped
public class CompositeRowKeyManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private List<GroupTable> selectedValues; // Getter & setter.
private static final long serialVersionUID = 1L;
public CompositeRowKeyManagedBean() {}
private List<GroupTable> init() {
List<GroupTable> list = new ArrayList<GroupTable>();
GroupTablePK groupTablePK = new GroupTablePK("aaa", "ROLE_ADMIN");
GroupTable groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("bbb", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ccc", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ddd", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("eee", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
return list;
}
@Override
public List<GroupTable> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<GroupTable> list = init();
setRowCount(list.size());
return list;
}
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
public void onRowEdit(RowEditEvent event) {
GroupTablePK groupTablePK = ((GroupTable) event.getObject()).getGroupTablePK();
System.out.println("grouoId : " + groupTablePK.getGroupId() + " : userGroupId : " + groupTablePK.getUserGroupId());
}
}
数据table:
<p:dataTable var="row"
value="#{compositeRowKeyManagedBean}"
lazy="true"
editable="true"
selection="#{compositeRowKeyManagedBean.selectedValues}"
rows="50">
<p:column selectionMode="multiple"></p:column>
<p:ajax event="rowEdit" listener="#{compositeRowKeyManagedBean.onRowEdit}"/>
<p:column headerText="GroupId">
<h:outputText value="#{row.groupTablePK.userGroupId}"/>
</p:column>
<p:column headerText="UserGroupId">
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
<f:facet name="input">
<p:inputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
</p:cellEditor>
</p:column>
<p:column headerText="Edit">
<p:rowEditor/>
</p:column>
</p:dataTable>
当试图编辑一行时,将调用 onRowEdit()
方法。如前所述,getRowData()
被调用两次并在两个后续调用中生成行键的拆分。
这是两个域 classes GroupTable
和 GroupTablePK
.
public class GroupTable implements Serializable {
private static final long serialVersionUID = 1L;
protected GroupTablePK groupTablePK;
public GroupTable() {}
public GroupTable(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
public GroupTable(String userGroupId, String groupId) {
this.groupTablePK = new GroupTablePK(userGroupId, groupId);
}
public GroupTablePK getGroupTablePK() {
return groupTablePK;
}
public void setGroupTablePK(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
@Override
public int hashCode() {
int hash = 0;
hash += (groupTablePK != null ? groupTablePK.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTable)) {
return false;
}
GroupTable other = (GroupTable) object;
if ((this.groupTablePK == null && other.groupTablePK != null) || (this.groupTablePK != null && !this.groupTablePK.equals(other.groupTablePK))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTable[ groupTablePK=" + groupTablePK + " ]";
}
}
public class GroupTablePK implements Serializable {
private String userGroupId;
private String groupId;
public GroupTablePK() {}
public GroupTablePK(String userGroupId, String groupId) {
this.userGroupId = userGroupId;
this.groupId = groupId;
}
public String getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(String userGroupId) {
this.userGroupId = userGroupId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
@Override
public int hashCode() {
int hash = 0;
hash += (userGroupId != null ? userGroupId.hashCode() : 0);
hash += (groupId != null ? groupId.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTablePK)) {
return false;
}
GroupTablePK other = (GroupTablePK) object;
if ((this.userGroupId == null && other.userGroupId != null) || (this.userGroupId != null && !this.userGroupId.equals(other.userGroupId))) {
return false;
}
if ((this.groupId == null && other.groupId != null) || (this.groupId != null && !this.groupId.equals(other.groupId))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
}
}
通过阅读您的问题,我猜测 getRowKey() 方法必须 return 对单行唯一可识别的东西。可以理解的是,代表您的行的底层 JPA 实体有一个复合键对象,这很好。我认为问题是 Java 对象使用任何东西作为 Map 类型集合中的键,键对象必须重载并为 equals
和 hashCode
方法定义正确的实现.
我怀疑 Primefaces 可能正在使用某种 Map 来检索基于键的值。 String 类型通常是对象唯一键的良好候选者,因为 String 是不可变的并且具有 equals 和 hashCode 的正确实现。他们是一个很好的候选者,所以如果你必须将一个字符串传递给 getRowData
那么你总是可以在该对象上提供一个方法,该方法 return 是该对象的唯一字符串。例如,这可能是您为行数据对象提供的 hashCode 实现的 base 64 表示。
如果 String 不是必需参数,则只需为复合键对象实现 equals 和 hashCode,然后直接将其用作您的键。
我 运行 您的 MCVE(对此表示敬意!)并复制了它。行键在客户端似乎被解释为逗号分隔的字符串,以涵盖需要多项选择的情况。如果单个 rowkey 的字符串表示形式包含逗号(如您的情况),这将失败。您在 getRowData()
中获得的 rowkey 参数是它的明确证据:它们是原始值以逗号分隔时的结果。
因此,要解决此问题,您需要确保 getRowKey().toString()
不包含逗号。最好使用不同的分隔符。例如。一个下划线。
@Override
public Object getRowKey(GroupTable groupTable) {
GroupTablePK pk = groupTable != null ? groupTable.getGroupTablePK() : null;
return pk != null ? pk.getUserGroupId() + "_" + pk.getGroupId() : null;
}