Переопределение / реализация методов getRowKey() и getRowData(), когда есть составной первичный ключ, который объединяет несколько столбцов в качестве ключа строки
У меня есть таблица в базе данных MySQL. К сожалению, есть составной первичный ключ, который необходим для аутентификации / авторизации JAAS в 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)
Таблица содержит данные в следующем формате.
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)
<p:dataTable>
с rowKey
отлично работает, когда lazy
установлен в false
,
<p:dataTable rowKey="#{row.groupTablePK.userGroupId} #{row.groupTablePK.groupId}">
...
</p:dataTable>
GroupTablePK
является @Embeddable
класс (JPA). Подробности об этом классе не нужны, я полагаю.
когда 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>
с этими реализациями, sout
заявление внутри getRowData()
Метод отображает следующее.
Info: rowKey : entity.GroupTablePK[ userGroupId=you123@gmail.com
Info: rowKey : groupId=ROLE_USER ]
getRowKey()
метод возвращает экземпляр GroupTablePK
но getRowData()
Метод принимает только параметр типа String. Это не объект, представляющий составной первичный ключ (настоящим GroupTablePK
) так что он может быть приведен к соответствующему типу объекта (GroupTablePK
) и на основании чего экземпляр GroupTable
может быть получено из данного List<GroupTable>
и получить getRowData()
метод, чтобы вернуть этот экземпляр GroupTable
,
Как продолжить дальше?
Вопрос основан исключительно на предыдущем вопросе:
РЕДАКТИРОВАТЬ:
я имею hashcode()
а также equals()
реализации в дополнение к toString()
в GroupTablePK
, toString()
метод в GroupTablePK
возвращается return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
но getRowData()
метод вызывается дважды, когда строка в <p:dataTable>
выбран. Возвращает строковое представление GroupTablePK
в двух частях в двух последующих звонках. При первом вызове возвращается entity.GroupTablePK[ userGroupId=aaa
а затем во втором вызове, он возвращает groupId=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.
Управляемый боб:
@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()
вызывается дважды и производит разделение ключа строки в двух последующих вызовах, как было сказано ранее.
Это два класса домена 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 + " ]";
}
}
2 ответа
Я запустил ваш 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;
}
Прочитав ваш вопрос, я предполагаю, что метод getRowKey() должен возвращать что-то уникально идентифицируемое в одну строку. Понятно, что базовая сущность JPA, представляющая вашу строку, имеет составной ключевой объект, что нормально. Я думаю, что проблема заключается в том, что объект Java должен использовать что-либо в качестве ключа в коллекции типов Map, объект ключа должен быть перегружен и определить правильную реализацию для equals
а также hashCode
методы.
Я подозреваю, что Primefaces, вероятно, использует какую-то карту для получения значений на основе ключа. Тип String обычно является хорошим кандидатом на уникальный ключ объекта, поскольку строки являются неизменяемыми и имеют правильные реализации equals и hashCode. Они являются хорошим кандидатом для этого, так что если вы должны передать строку getRowData
тогда вы всегда можете предоставить метод для этого объекта, который возвращает уникальную строку для этого объекта. Это может быть, например, представление base 64 реализации hashCode, которую вы предоставляете для своего объекта данных строки.
Если String не является обязательным параметром, просто реализуйте equals и hashCode для объекта составного ключа и используйте его непосредственно в качестве ключа.