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



我有一个版本的实体转换的问题。我举了一个简单的例子来解释我的问题,因为"真正的"应用程序太大,包含许多不必要的东西。

情况:我有一个web应用程序与primefaces和openjpa。我有20个组件(自动完成+选择菜单)需要一个转换器,它们使用持久性实体。

information:我只想用jsf,primefaces !(没有什么特别的,像全面人或其他东西。)问题在底部。这只是测试代码。它是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上找到了全局实体转换器。一个转换器为所有,我认为这是一个很好的解决方案。("p:全局实体转换器的自动完成")。我用它,我认为它工作得很好。BUT经过几次测试后,我发现了另一个大问题,实体的版本。

问题1与实体转换器:

我有版本字段不在我的哈希码或等号(我没有发现任何关于它)。我只读了这个(JPA hashCode()/equals()困境)。问题是实体不会在哈希图中被替换,在某些情况下,我得到一个乐观的锁定异常,因为"旧"实体留在哈希图中。

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

此解决方案适用于乐观锁定异常,但会带来另一个问题!

问题与实体转换器:

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

如果组织中已有领导。如果该领导人也在人员列表中,则将用新的uid替换。leader将被设置为null或转换异常,因为uid在hashmap中不再存在。这意味着他为组织使用转换器。Leader并将Leader添加到hashmap中。然后是persons——在hashmap中列出并添加所有其他人员,并覆盖来自组织的uid。领袖,如果他存在于人身上的话。

现在有两种情况:

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

  • 如果我不更改"当前"选项并提交组织。领导试图找到他的"旧"uid,但人员列表中的另一个人已经覆盖了它,并且uid不存在,组织。

我找到了另一个解决方案,这是我的最终解决方案但是我发现,这是一个非常非常奇怪的解决方案,我会做得更好,但我什么也没找到。

最终解决我将"旧"uid添加到"新"对象中。

 @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有效,您只是想让它更通用:

将实例保持在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

这个基本方法非常灵活,可以很容易地扩展到许多用例。

最新更新