6

我在 MySQL 数据库中有一个表。不幸的是,GlassFish Server 中的 JAAS 身份验证/授权需要一个复合主键。

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)

该表包含以下格式的数据。

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>withrowKey工作正常,何时lazy设置为false.

<p:dataTable rowKey="#{row.groupTablePK.userGroupId} #{row.groupTablePK.groupId}">
    ...
</p:dataTable>

GroupTablePK是一个@Embeddable类(JPA)。我想不需要关于这个类的细节。


但是,当lazy在 a 上启用时<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>这些实现中选择一行时,方法sout内的语句将getRowData()显示以下内容。

Info:  rowKey : entity.GroupTablePK[ userGroupId=you123@gmail.com
Info:  rowKey : groupId=ROLE_USER ]

getRowKey()方法返回一个实例,GroupTablePK但该getRowData()方法只接受一个字符串类型的参数。它不是表示复合主键(特此GroupTablePK)的对象,因此可以将其类型转换为适当的对象类型(GroupTablePK),并基于该对象类型GroupTable可以从给定的对象中获取实例List<GroupTable>并获取getRowData()返回实例的方法GroupTable.

如何进一步进行?


该问题纯粹基于上一个问题:

java.lang.UnsupportedOperationException:不使用基本rowKey算法时必须实现getRowData(String rowKey)


编辑:

除了 in 之外,我还有hashcode()和实现。The method in returns but the method is invoked twice, when a row in a is selected. 它在两个后续调用中分两部分返回字符串表示形式。在第一次调用中,它返回,然后在第二次调用中,它返回。equals()toString()GroupTablePKtoString()GroupTablePKreturn "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";getRowData()<p:dataTable>GroupTablePKentity.GroupTablePK[ userGroupId=aaagroupId=ROLE_USER ]

相反,它应该entity.GroupTablePK[ userGroupId=aaa, groupId=ROLE_USER ]在一次调用中立即返回。

因此,这种比较groupTable.getGroupTablePK().toString().equals(rowKey)是不可能的,我在这篇文章之前就已经考虑过了。如,

@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());
    }
}

数据表:

<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()如前所述,它被调用两次并在两个后续调用中生成行键的拆分。


这是两个域类GroupTableGroupTablePK.

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 + " ]";
    }
}
4

2 回答 2

6

我运行了您的 MCVE(对此表示赞赏!)并复制了它。行键似乎在客户端被解释为逗号分隔的字符串,以涵盖需要多选的情况。如果单个行键的字符串表示形式包含逗号,这将失败,就像您的情况一样。你得到的 rowkey 参数getRowData()清楚地证明了这一点:它们是原始值以逗号分隔时的结果。

因此,要解决此问题,您需要确保getRowKey().toString()任何地方都不包含逗号。最好使用不同的分隔符。例如下划线。

@Override
public Object getRowKey(GroupTable groupTable) {
    GroupTablePK pk = groupTable != null ? groupTable.getGroupTablePK() : null;
    return pk != null ? pk.getUserGroupId() + "_" + pk.getGroupId() : null;
}
于 2015-04-30T08:05:22.420 回答
1

通过阅读您的问题,我猜测 getRowKey() 方法必须返回对单行唯一可识别的内容。可以理解的是,代表您的行的底层 JPA 实体有一个复合键对象,这很好。我认为问题在于 Java 对象在 Map 类型集合中使用任何东西作为键,键对象必须重载并为equalsandhashCode方法定义正确的实现。

我怀疑 Primefaces 可能正在使用某种类型的 Map 来根据键检索值。String 类型通常是对象唯一键的良好候选者,因为 String 是不可变的并且具有 equals 和 hashCode 的正确实现。它们是一个很好的候选者,因此如果您必须将 String 传递给,getRowData那么您始终可以在该对象上提供一个方法,该方法返回该对象的唯一字符串。例如,这可能是您为行数据对象提供的 hashCode 实现的 base 64 表示。

如果 String 不是必需参数,则只需为复合键对象实现 equals 和 hashCode 并将其直接用作您的键。

于 2015-04-26T16:40:53.097 回答