为具有Java Generics的实体实现转换器 [英] Implement converters for entities with Java Generics

查看:144
本文介绍了为具有Java Generics的实体实现转换器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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




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




(省略检查):

  @ManagedBean(name =myConverter)
@SessionScoped
public class MyConverter实现Converter {
private MyService myService;
$ b $ * / *
@Override
public Object getAsObject(FacesContext facesContext,UIComponent uiComponent,String value){
int id = Integer.parseInt(值);
返回myService.getById(id);

$ b @Override
public String getAsString(FacesContext facesContext,UIComponent uiComponent,Object value){
return((MyEntity)value).getId()。toString ();


code $


由于大量 MyService MyEntity )类型的转换器当然),我想知道是否值得使用单个通用转换器。
泛型的实现本身并不困难,但我不确定正确的方法来声明Bean。



可能的解决方案是以下内容:
$ b 1 1 - 编写通用实现,我们称之为 MyGenericConverter ,没有任何Bean注解



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

  @ManagedBean(name =myFooConverter)
@SessionScoped
public class MyFooConverter实现MyGenericConverter< Foo> {b $ b / * ... * /
}

意识到也许Generic不是真的需要,所以也许我可以简单地编写一个基类来实现这两个方法,并根据需要编写子类。



有几个不必要的细节(例如,我必须以某种方式抽象 MyService 类),所以我的第一个问题是:它是否值得麻烦?



如果有的话,还有其他方法吗?

解决方案

最简单的就是让所有的JPA实体都像这样从一个基础实体扩展出来:

  public abstract class BaseEntity< T extends Number>实现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
&& amp; other.getClass()。isAssignableFrom(getClass())
&& getClass()。isAssignableFrom(other.getClass()))
? getId()。equals(((BaseEntity<>)other).getId())
:(other == this);

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

}

请注意,正确 equals()(和 hashCode()),否则您将面对验证错误:值无效 Class#isAssignableFrom()测试是为了避免在例如基于Hibernate的代理无需退回到特定于Hibernate的 Hibernate#getClass(Object) 帮助程序方法



有一个这样的基础服务(是的,我忽略了你使用Spring的事实,只是为了给出基本的想法):

  @Stateless 
public class BaseService {

@PersistenceContext
private EntityManager em;

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

}

并执行转换器如下:

  @ManagedBean 
@ApplicationScoped
@SuppressWarnings({rawtypes,unchecked})/ /我们并不关心BaseEntity的实际类型。
public class BaseEntityConverter实现Converter {

@EJB
private BaseService baseService;
$ b $ @Override
public String getAsString(FacesContext context,UIComponent component,Object value){
if(value == null){
return; $(b)b

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 a valid User,modelValue)),e);


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

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

}

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



如果您碰巧使用这种转换器的频率比 UISelectOne / UISelectMany 组件(< h:selectOneMenu> 和朋友),您可能会发现 OmniFaces SelectItemsConverter 更有用。它根据< f:selectItems> 中的值进行转换,而不是每次都进行(可能是昂贵的)数据库调用。


I'm working on JSF project with Spring and Hibernate which among other things has a number of Converters that follow the same pattern:

  • getAsObject receives the string representation of the object id, converts it to a number, and fetch the entity of the given kind and the given id

  • getAsString receives and entity and returns the id of the object converted to String

The code is essentially what follows (checks omitted):

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

Given the large number of Converters that are exactly like this (except for the type of MyService and MyEntity of course), I was wondering if it was worth using a single generic converter. The implementation of the generic by itself is not difficult, but I'm not sure about the right approach to declare the Beans.

A possible solution is the following:

1 - Write the generic implementation, let's call it MyGenericConverter, without any Bean annotation

2 - Write the specific converter ad a subclass of MyGenericConverter<T> and annotate it as needed:

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

While writing this I realized that maybe a Generic is not really needed, so maybe I could simply write a base class with the implementation of the two methods, and subclass as needed.

There a few non trivial details that have to be taken care of (like the fact that I'd have to abstract the MyService class in some way) so my first question is : is it worth the hassle ?

And if so, are there other approaches ?

解决方案

Easiest would be to let all your JPA entities extend from a base entity like this:

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

}

Note that it's important to have a proper equals() (and hashCode()), otherwise you will face Validation Error: Value is not valid. The Class#isAssignableFrom() tests are to avoid failing tests on e.g. Hibernate based proxies without the need to fall back to Hibernate-specific Hibernate#getClass(Object) helper method.

And have a base service like this (yes, I'm ignoring the fact that you're using Spring; it's just to give the base idea):

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

}

And implement the converter as follows:

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

}

Note that it's registered as a @ManagedBean instead of a @FacesConverter. This trick allows you to inject a service in the converter via e.g. @EJB. See also How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter? So you need to reference it as converter="#{baseEntityConverter}" instead of converter="baseEntityConverter".

If you happen to use such a converter more than often for UISelectOne/UISelectMany components (<h:selectOneMenu> and friends), you may find OmniFaces SelectItemsConverter much more useful. It converts based on the values available in <f:selectItems> instead of making (potentially expensive) DB calls everytime.

这篇关于为具有Java Generics的实体实现转换器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆