当前位置: 首页 > 知识库问答 >
问题:

如何在JSF转换器中使用分离的entitys版本

鲜于勇
2023-03-14

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

情境:我有一个带有primefaces和openjpa的web应用程序。我有20个组件(autocompletes selectedmenues)需要一个转换器,它们使用持久性实体。

信息:我只想使用jsf,primefaces!(没有什么比omnifaces或其他东西更特别的了。)问题就在下面。这只是测试代码。它还不完整,还有一些奇怪的事情。但这充其量只能解释我的问题。

实体示例:(仅字段和哈希代码相等)

@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上找到了全局实体转换器。一个转换器,我认为这是一个很好的解决方案。(“p:自动完成全局实体转换器”)。我使用它,我认为它工作正常。但是经过几次测试,我发现了另一个大问题,实体的版本。

实体转换器的问题1:

我的hashcode或equals中没有版本字段(我没有发现任何关于它的内容)。我只读了这个(JPA hashCode()/equals()困境)。问题是实体不会在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或转换异常,因为uuid在hashmap中不再存在。这意味着他将转换器用于organisation.leader并将领导者添加到hashmap中。接下来是人员-列出并添加hashmap中的所有其他人,如果他也存在于人员中,则覆盖organisation.leader中的uuid。

现在有两个案例:

>

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

    如果我不更改“当前”选择并提交组织。领导者试图找到他的“旧”uuid,但人员列表中的另一个人已经覆盖了该uuid,并且uuid不存在,组织也不存在。leader为空。

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

    最终解决方案我将“旧”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个答案

    乐华晖
    2023-03-14

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

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

    或者如果id不应该在html源代码中公开,请使用guid map或公共标识符。

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

    每当我需要转换视图参数时,我都会使用dao方法,而bean还没有构建。

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

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

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

     类似资料:
    • 然后开始正确地调用转换器。这向我提出了一个问题,即何时应该命名转换器(通过属性),以及何时告诉他们转换器应该与哪个类一起使用(与属性一起使用)就足够了。在使用PrimeFaces时,我从来不需要命名任何转换器,只是针对这个特定的组件。不同的元件对转换器有不同的需求吗?还是我只是把转换器的概念搞错了?

    • 在我的代码中,我想要 对象“膳食创建”中字段“日期时间”上的字段错误:拒绝值[2020-05-11T11:08];代码[TypeMismatch.MealScreate.DateTime,TypeMismatch.DateTime,TypeMismatch.java.time.LocalDateTime,TypeMismatch];参数[org.springframework.context.su

    • 问题内容: 我正在编写我的第一个Java EE 6 Web应用程序作为学习练习。我没有使用框架,只有JPA 2.0,EJB 3.1和JSF 2.0。 我有一个自定义转换器,可以将存储在SelectOne组件中的JPA实体转换回实体。我正在使用InitialContext.lookup获取对会话Bean的引用以查找相关实体。 我想创建一个通用的实体转换器,所以不必为每个实体创建一个转换器。我以为我会

    • 主要内容:转换器标签JSF有转换器将其UI组件的数据转换为托管bean中使用的对象,反之亦然。 例如,我们可以将文本转换为日期对象,并且可以验证输入的格式。 要使用转换器标签,我们必须在节点中使用URI的以下命名空间。 转换器标签 以下是JSF 2.0中的重要转换代码: 标签 描述 将字符串转换为所需格式的数字值 将字符串转换为所需的日期格式 自定义转换器 创建一个自定义转换器

    • 我正在使用JmsTemplate转换和发送事件对象。我已经向JmsTemplate注册了MappingJackson2MessageConverter。 我正在使用并从该主题获取消息。我不知道如何将此消息转换回我的对象? JmsTemplate的messageconverter是 我尝试调用并传递此消息对象进行转换,但没有成功。如何将我收到的消息对象转换为我想要的自定义对象?

    • 主要内容:JSF自定义转换器实例我们可以在JSF中创建自己的自定义转换器。 以下列表是我们可以在JSF中创建自定义转换器的步骤。 通过实现接口创建一个转换器类。 实现上述接口的和方法。 使用注解为自定义转换器分配唯一的ID。 JSF自定义转换器实例 打开 NetBeans IDE 创建一个Web工程:CustomConverter,其目录结构如下所示 - 创建以下文件代码,文件:index.xhtml 的代码内容如下所示 - 文