【发布时间】:2014-06-28 04:10:20
【问题描述】:
我在使用版本转换实体时遇到问题。我做了一个简单的例子来解释我的问题,因为“真正的”应用程序很大并且包含许多不必要的东西。
情况:我有一个带有 primefaces 和 openjpa 的 Web 应用程序。我有 20 个需要转换器的组件(自动完成 + 选定菜单),它们使用持久性实体。
信息:我只想使用 jsf,primefaces ! (没有什么比全脸或其他什么特别的了。)问题 在底部。这只是测试代码。它不完整,并且有一些奇怪的事情。但这充其量解释了我的问题。
示例实体:(仅字段和哈希码 + 等于)
@Entity
public class Person {
@Id
private Long id;
private String name;
@Version
private Long version;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
第一个解决方案:我的第一个解决方案是为每个组件制作一个自己的转换器。我在那里注入我的托管 bean 并使用组件“值”中的 getter。
豆子
@ManagedBean(name = "personBean")
@ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
/** unnecessary things **/
xhtml:
<p:selectOneMenu >
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
转换器:
@ManagedBean
@RequestScoped
public class PersonConverter implements Converter{
@ManagedProperty(value = "personBean")
private PersonBean personBean;
@Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
//null & empty checks
Long id = Long.valueOf(value);
for(Person person : personBean.getPersons()){
if(person.getId().equals(id)){
return person;
}
}
throw new ConverterException("some text");
}
@Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
//null & Instanceof checks
return String.valueOf(((Person)value).getId());
}
}
总结:这个解决方案效果很好。但我发现必须有一个更好的解决方案作为每个组件的转换器。
第二种解决方案:我在 stackoverflow 上找到了 Global Entity Converter。一个转换器,我认为这是一个很好的解决方案。 (“p:autocomplete for a global Entity Converter”)。我使用它,我认为它工作正常。 但是经过几次测试我发现了另一个大问题,实体的版本。
实体转换器的问题1:
我的 hashcode 或 equals 中没有版本字段(我一无所获)。我只读过这个(The JPA hashCode() / equals() dilemma)。问题是实体不会在哈希图中被替换,并且在某些情况下我得到一个乐观锁定异常,因为“旧”实体留在哈希图中。
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
解决方案:我认为我可以通过向我的实体添加一个接口来检查版本是否存在来解决这个问题。
界面:
public interface EntityVersionCheck {
public boolean hasVersion();
}
实施:
@Override
public boolean hasVersion() {
return true;
}
转换器:
@Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if(entity instanceof EntityVersionCheck && ((EntityVersionCheck)entity).hasVersion()){
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
这种解决方案适用于乐观锁定异常,但带来了另一个问题!
实体转换器的问题2:
<p:selectOneMenu value="#{organisation.leader}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
如果一个组织已经有一个领导者。如果领导者也在人员列表中,它将被新的 uuid 替换。领导者将被设置为 null 或转换异常,因为 uuid 不再存在于 hashmap 中。这意味着他使用了 organization.leader 的转换器并将领导者添加到 hashmap 中。然后是persons-列出并在hashmap中添加所有其他人,如果他也存在于persons中,则覆盖来自organization.leader的uuid。
现在有两种情况:
当我选择其他领导时,它可以正常工作。
如果我不更改“当前”选择并提交组织。领导者尝试查找他的“旧”uuid,但人员列表中的其他人已覆盖它并且 uuid 不存在且组织不存在。领导者为空。
我找到了另一个解决方案,这是我找到的最终解决方案但是,这是一个非常非常奇怪的解决方案,我会做得更好,但我什么也没找到。
最终解决方案 我将“旧”uuid 添加到“新”对象中。
@Override
public String getAsString(FacesContext context, UIComponent component,
Object entity) {
synchronized (entities) {
String currentuuid = null;
if (entity instanceof EntityVersionCheck
&& ((EntityVersionCheck) entity).hasVersion()) {
currentuuid = entities.get(entity);
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
if (currentuuid == null) {
currentuuid = UUID.randomUUID().toString();
}
entities.put(entity, currentuuid);
return currentuuid;
} else {
return entities.get(entity);
}
}
}
问题:我如何使它变得更好和正确?
【问题讨论】:
-
你真的需要这种带有内部缓存的转换器吗?您最好让所有实体都从具有抽象实体 ID 的基本实体扩展,然后对其进行转换。另请参阅此答案以获得启动:stackoverflow.com/a/17343582 如果您需要缓存以避免不必要的数据库命中,您可以在必要时将缓存利用到 JPA(第二级容器管理缓存,例如 JBoss 服务器上的 Infinispan)。它会自动决定是从缓存返回实体还是直接从数据库返回,而不需要
@Version。 -
我错过了这个问题或您要存档的内容?我不明白您为什么要在表示层中处理版本控制?您的多对一选项是否有级联保存选项,例如 jpa 是否也保存组织的领导者?为什么还会发生异常。
-
balusc:“内部”缓存应该避免额外的 db 访问,因为我需要的所有对象都在我的 bean 中,我认为没有理由额外的 db 访问。 (我认为每次额外的数据库访问会更好。)@djmj:我寻找更好的方法来实现这一点。版本处理只是解决哈希图中存在相同对象但具有旧版本的原因。 (它通常不会被替换,因为 '!entities.containsKey(entity)' 返回 true。我编辑我的第一篇文章来解释异常。
-
换句话说,二级 JPA 缓存也不是一个选项,您真的需要使用
@Version和本地缓存继续这条路径吗?你完全了解二级 JPA 缓存是什么吗? -
不,那是个误会。我对您的回答只是我解决方案的原因。我看了一下二级缓存解决方案,但这需要一些时间并测试二级缓存的行为。
标签: jsf primefaces jpa-2.0 converter