如何在JSF转换器中使用分离的实体+版本

时间:2014-05-10 12:46:09

标签: jsf primefaces jpa-2.0 converter

我在使用版本转换实体时遇到问题。我做了一个简单的例子来解释我的问题,因为“真正的”应用程序很大并且包含许多不必要的东西。

情况:我有一个带有primefaces和openjpa的Web应用程序。我有20个组件(自动复合+选择),需要转换器,他们使用持久性实体。

信息:我只想使用jsf,primefaces吧! (没有什么特别喜欢omnifaces或其他东西。)问题位于底部。这只是测试代码。 NOT 完成并且有一些奇怪的事情。但这充分解释了我的问题。

示例实体:(仅字段和哈希码+等于)

@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并使用组件“value”中的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与实体转换器:

我的版本字段不在我的哈希码或等号中(我没有发现它)。我只读过它(The JPA hashCode() / equals() dilemma)。问题是实体不会在hashmap中被替换,并且在某些情况下我得到乐观锁定异常,因为“旧”实体保留在hashmap中。

 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或转换异常,因为在hashmap中uuid不再存在。这意味着他使用转换器进行organisation.leader并将领导者添加到hashmap。比来人员 - 列出并添加所有其他人在hashmap中并覆盖organisation.leader中的uuid,如果他也存在于人。

现在有两种情况:

  • 当我选择其他领导者时,它会正常工作。

  • 如果我不改变“当前”选择并提交organisation.leader尝试找到他的“旧”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);
        }
    }
}

问题:我如何做得更好更好?

1 个答案:

答案 0 :(得分:2)

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

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

如果不应在html源中公开id,请使用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

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