使用Java Generics为实体实现转换器

Mar*_*ele 7 java generics jsf spring converter

我正在使用Spring和Hibernate开发JSF项目,其中包括许多Converter遵循相同模式的s:

  • getAsObject 接收对象id的字符串表示形式,将其转换为数字,并获取给定种类的实体和给定的id

  • getAsString receive和entity并返回转换为的对象的id String

代码基本上是以下内容(省略检查):

@ManagedBean(name="myConverter")
@SessionScoped
public class MyConverter implements Converter {
    private MyService myService;

    /* ... */
    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) {
        int id = Integer.parseInt(value);
        return myService.getById(id);
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) {
        return ((MyEntity)value).getId().toString();
    }
}
Run Code Online (Sandbox Code Playgroud)

鉴于大量的ConverterS中的完全一样(除了类型MyServiceMyEntity当然的),我想知道,如果它使用一个通用的转换器是值得的.通用本身的实现并不困难,但我不确定声明Beans的正确方法.

可能的解决方案如下:

1 - 编写通用实现,让我们调用它MyGenericConverter,不需要任何Bean注释

2 - 将特定转换器ad写为子类,MyGenericConverter<T>并根据需要对其进行注释:

@ManagedBean(name="myFooConverter")
@SessionScoped
public class MyFooConverter implements MyGenericConverter<Foo> {
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

在写这篇文章时,我意识到可能并不真正需要Generic,所以也许我可以简单地用两种方法的实现来编写基类,并根据需要创建子类.

有一些非常重要的细节需要处理(就像我必须以MyService某种方式抽象课程这样的事实)所以我的第一个问题是:值得麻烦吗?

如果是这样,还有其他方法吗?

Bal*_*usC 17

最简单的方法是让所有JPA实体从这样的基本实体扩展:

public abstract class BaseEntity<T extends Number> implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract T getId();

    public abstract void setId(T id);

    @Override
    public int hashCode() {
        return (getId() != null) 
            ? (getClass().getSimpleName().hashCode() + getId().hashCode())
            : super.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other != null && getId() != null
                && other.getClass().isAssignableFrom(getClass()) 
                && getClass().isAssignableFrom(other.getClass())) 
            ? getId().equals(((BaseEntity<?>) other).getId())
            : (other == this);
    }

    @Override
    public String toString() {
        return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
    }

}
Run Code Online (Sandbox Code Playgroud)

请注意,拥有正确的equals()(和hashCode())非常重要,否则您将面临验证错误:值无效.该Class#isAssignableFrom()测试是为了避免失败的基于,例如Hibernate代理的测试,而不需要回落到休眠特定Hibernate#getClass(Object)的辅助方法.

并且有这样的基本服务(是的,我忽略了你使用Spring的事实;它只是给出了基本的想法):

@Stateless
public class BaseService {

    @PersistenceContext
    private EntityManager em;

    public BaseEntity<? extends Number> find(Class<BaseEntity<? extends Number>> type, Number id) {
        return em.find(type, id);
    }

}
Run Code Online (Sandbox Code Playgroud)

并按如下方式实现转换器:

@ManagedBean
@ApplicationScoped
@SuppressWarnings({ "rawtypes", "unchecked" }) // We don't care about BaseEntity's actual type here.
public class BaseEntityConverter implements Converter {

    @EJB
    private BaseService baseService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null) {
            return "";
        }

        if (modelValue instanceof BaseEntity) {
            Number id = ((BaseEntity) modelValue).getId();
            return (id != null) ? id.toString() : null;
        } else {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
        }
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            Class<?> type = component.getValueExpression("value").getType(context.getELContext());
            return baseService.find((Class<BaseEntity<? extends Number>>) type, Long.valueOf(submittedValue));
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid ID of BaseEntity", submittedValue)), e);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

请注意,它已注册为a @ManagedBean而不是a @FacesConverter.这个技巧允许您通过例如在转换器中注入服务@EJB.另请参阅如何在@FacesConverter中注入@ EJB,@ PersistenceContext,@ Inject,@ Autowired等?所以你需要引用它converter="#{baseEntityConverter}"而不是converter="baseEntityConverter".

如果你碰巧比UISelectOne/ UISelectManycomponents(<h:selectOneMenu>和朋友)更常使用这样的转换器,你会发现OmniFaces SelectItemsConverter更有用.它基于可用的值进行转换,<f:selectItems>而不是每次都进行(可能是昂贵的)DB调用.

  • 在equals和hashCode方法中不推荐使用"id"属性:他们建议使用Business key equality实现equals()和hashCode():https://docs.jboss.org/hibernate/stable/core.old/reference/ en/html/persistent-classes-equalshashcode.html复合id怎么样? (2认同)