【问题标题】:How to use detached entitys + version in a JSF Converter如何在 JSF 转换器中使用分离实体 + 版本
【发布时间】: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


【解决方案1】:

如果解决方案 1 有效,而您只是希望它更通用:

将您的实例保持在 bean 的范围内,您可以通过从中删除托管 bean 查找来使用更通用的转换器。您的实体应继承自具有标识符属性的基本实体。您可以在检索实体的 bean 中实例化此转换器。

如果 id 不应在 html 源代码中公开,则使用 guid 映射或公共标识符。

@ManagedBean(name = "personBean")
@ViewScoped
public class PersonBean implements Serializable {

    private List<Person> persons;
    private EntityConverter<Person> converter;

    // this.converter = new EntityConverter<>(persons);
}

<p:selectOneMenu converter="#{personBean.converter}">
    <f:selectItems value="#{personBean.persons}"/>              
</p:selectOneMenu>

抽象基实体转换器:

/**
 * Abstract Entity Object JSF Converter which by default converts by {@link Entity#getId()}
 */
public abstract class AEntityConverter<T extends Entity> implements Converter
{
    @Override
    public String getAsString(final FacesContext context, final UIComponent component, final Object value)
    {
        if (value instanceof Entity)
        {
            final Entity entity = (Entity) value;
            if (entity.getId() != null)
                return String.valueOf(entity.getId());
        }

        return null;
    }
}

缓存集合:

/**
 * Entity JSF Converter which holds a Collection of Entities
 */
public class EntityConverter<T extends Entity> extends AEntityConverter<T>
{
    /**
     * Collection of Entity Objects
     */
    protected Collection<T> entities;

    /**
     * Creates a new Entity Converter with the given Entity Object's
     * 
     * @param entities Collection of Entity's
     */
    public EntityConverter(final Collection<T> entities)
    {
        this.entities = entities;
    }

    @Override
    public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
    {
        if (value == null || value.trim().equals(""))
            return null;

        try
        {
            final int id = Integer.parseInt(value);

            for (final Entity entity : this.entities)
                if (entity.getId().intValue() == id)
                    return entity;
        }
        catch (final RuntimeException e)
        {
            // do something --> redirect to exception site
        }

        return null;
    }

    @Override
    public void setEntities(final Collection<T> entities)
    {
        this.entities = entities;
    }

    @Override
    public Collection<T> getEntities()
    {
        return this.entities;
    }
}

远程或数据库查找转换器:

/**
 * Entity Object JSF Converter
 */
public class EntityRemoteConverter<T extends Entity> extends AEntityConverter<T>
{
    /**
     * Dao
     */
    protected final Dao<T> dao;

    public EntityRemoteConverter(final EntityDao<T> dao)
    {
        this.dao = dao;
    }

    @Override
    public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
    {
        // check for changed value
        // no need to hit database or remote server if value did not changed!
        if (value == null || value.trim().equals(""))
            return null;

        try
        {
            final int id = Integer.parseInt(value);
            return this.dao.getEntity(id);
        }
        catch (final RuntimeException e)
        {
            // do someting
        }

        return null;
    }
}

每当我必须转换视图参数并且尚未构造 bean 时,我都会使用 dao 方法。

避免昂贵的查找 在 dao 方法中,您应该在进行潜在的昂贵查找之前检查值是否已更改,因为转换器可能在不同阶段被多次调用。

查看来源:http://showcase.omnifaces.org/converters/ValueChangeConverter

这种基本方法非常灵活,可以轻松扩展到许多用例。

【讨论】:

  • 谢谢,过几天我也会看看这个
  • 我试过了。它工作得很好,我是一个很好的“下一步”解决方案,没有任何版本问题。谢谢。
猜你喜欢
  • 2013-06-04
  • 2011-05-15
  • 1970-01-01
  • 1970-01-01
  • 2012-12-06
  • 2012-04-11
  • 1970-01-01
  • 2015-08-10
  • 2018-08-29
相关资源
最近更新 更多