Overriding / injecting the getRowKey () and getRowData () methods when there is a composite primary key that concatenates multiple columns as a row of a row

I have a table in a MySQL database. Unfortunately, there is a composite primary key that is required for JAAS authentication / authorization on the GlassFish server.

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)

      

The table contains data in the following format.

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>

s rowKey

works fine when lazy

set to false

.

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

      

GroupTablePK

- class @Embeddable

(JPA). Details about this class are unnecessary, I suppose.


If lazy

, however, is included in <p:dataTable>

, the methods getRowKey()

and getRowData()

should be implemented.

How do I do this when there is a composite primary key that requires a combination of columns as the row key - a unique row ID?

@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.
    }
}

      

The above implementations remain unfinished.

When a line is selected <p:dataTable>

with these implementations, the statement sout

within the method getRowData()

displays the following.

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

      

The method getRowKey()

returns an instance GroupTablePK

, but the method getRowData()

only accepts a String parameter. It is not an object representing a composite primary key (thus GroupTablePK

) so that it can be type-bound to the corresponding object type ( GroupTablePK

) and based on which an instance GroupTable

can be derived from a given one List<GroupTable>

and get a method getRowData()

to return that instance GroupTable

.

How do I proceed?


The question is based solely on the previous previous question:

java.lang.UnsupportedOperationException: getRowData (String rowKey) must be implemented when the underlying rowKey algorithm is not used


EDIT:

I have hashcode()

and equals()

implementation in addition toString()

to GroupTablePK

. Method toString()

in GroupTablePK

returns return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";

, but method getRowData()

is called twice when row in <p:dataTable>

. It returns the string representation GroupTablePK

in two parts on two subsequent calls. In the first call it returns entity.GroupTablePK[ userGroupId=aaa

and then in the second call it returns groupId=ROLE_USER ]

.

It should return at entity.GroupTablePK[ userGroupId=aaa, groupId=ROLE_USER ]

once in one call.

Such a comparison groupTable.getGroupTablePK().toString().equals(rowKey)

is therefore not possible, as I was thinking before this post. For example,

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

      


EDIT 2:

Following is the shortest possible JPA noise removal example to reproduce the problem.

An attempt is alternatively included,

  • PrimeFaces 3.5
  • PrimeFaces 4.0
  • PrimeFaces 5.0
  • PrimeFaces 5.1
  • PrimeFaces 5.2

The behavior remains stationary in all of these PrimeFaces versions.

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

      

Data 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>

      

When trying to edit a line, the method is called onRowEdit()

. getRowData()

is called twice and splits the string in the next two calls, as indicated earlier.


These are two domain classes GroupTable

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

      

+3


source to share


2 answers


I fired up your MCVE (it was prestige!) And reproduced it. The row row seems to be interpreted as a commasedparated row on the client side to cover the case where multiple selections are required. It will fail if the one-line string representation contains a comma, as in your case. The rowkey argument you received in getRowData()

is clear evidence of it: these are the results when the original value is divided by a comma.

So, in order to fix this problem, you need to make sure that it getRowKey().toString()

does not contain a comma anywhere. Better to use a different separator character. For example. underline.



@Override
public Object getRowKey(GroupTable groupTable) {
    GroupTablePK pk = groupTable != null ? groupTable.getGroupTablePK() : null;
    return pk != null ? pk.getUserGroupId() + "_" + pk.getGroupId() : null;
}

      

+3


source


From reading your question, I am guessing that the getRowKey () method should return what is uniquely identified for a single row. It is clear that the main JPA entity that represents your string has a composite key entity, which is fine. The problem is, I think for a Java object to use anything as a key in a collection of a map type, the key object should overload and define the correct implementation for the equals

and methods hashCode

.

I suspect Primefaces is probably using some kind of map to fetch values ​​based on a key. The String type is usually a good candidate for a unique object key because strings are immutable and have the correct equals and hashCode implementation. They make a good candidate for this, so if you must pass a String to getRowData

, then you can always provide a method on that object that returns a unique string for that object. This could be, for example, a base 64 representation of the hashCode implementation that you provide for your string data object.



If String is optional, then just implement equals and hashCode on the composite key object and use that directly as your key.

+1


source







All Articles