2013-06-27 24 views
6

我正在用Spring和Hibernate JSF项目,除其他外有许多是遵循相同的模式Converter S:实施实体的转换器,Java泛型

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

  • getAsString接收和实体,并返回转换为String

  • 的对象的id

的代码基本上如下(省略检查)什么:

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

鉴于大量的Converter S中的值为酷似本(除的MyService当然MyEntity的类型),我想知道是否值得使用一个通用转换器。 泛型的实现本身并不困难,但我不确定正确的方法来声明Beans。

一个可能的解决方案是:

1 - 写一般的实现,让我们称之为MyGenericConverter,不含任何豆注释

2 - 写具体的转换器广告的子类的MyGenericConverter<T>和注释它作为需要:

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

在写这个,我意识到,也许是不是真的需要一个通用的,所以也许我可以简单地写一个基类的两个方法的实现, d子类。

有几个非常重要的细节需要处理(比如我必须以某种方式抽象MyService类),所以我的第一个问题是:是否值得花这个麻烦?

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

回答

15

最简单的是让你的所有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()); 
    } 

} 

注意,它有一个适当的equals()(和hashCode()),否则你将面临Validation Error: Value is not valid是很重要的。 Class#isAssignableFrom()测试是为了避免在例如基于Hibernate的代理无需回退到特定于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); 
    } 

} 

并实现转换,如下所示:

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

} 

注意它的注册作为@ManagedBean而不是@FacesConverter。这个技巧允许你通过例如注入一个服务在转换器中注入。@EJB。另请参阅How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter?因此,您需要将其作为converter="#{baseEntityConverter}"而不是converter="baseEntityConverter"来引用。

如果你碰巧使用这种转换器超过经常UISelectOne/UISelectMany组件(<h:selectOneMenu>和朋友),你可能会发现OmniFacesSelectItemsConverter有用得多。它根据<f:selectItems>中提供的值进行转换,而不是每次都进行(可能昂贵的)数据库调用。

+1

使用“ID”属性不建议在equals和hashCode方法中使用:他们建议使用Business key equal实现equals()和hashCode():https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes -equalshashcode.html复合ID如何? –

0

这里是我的解决这个方面的考虑:

  • 我asume你有兴趣JPA(未休眠)
  • 我的解决方案并不需要对扩展任何类,应该对任何JPA实体工作bean,它只是一个简单的类,也不需要实现任何服务或DAO。唯一的要求是转换器直接依赖于可能不太优雅的JPA库。
  • 它使用辅助方法来序列化/反序列化bean的id。它只转换实体bean的id,并将字符串与类名和id序列化并转换为base64。这是可能的,因为在jpa中实体的id必须是实现可序列化。这个方法的实现是在Java 1.7,但你可以找到适用于Java < 1.7另一个实现那边
 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.ObjectInput; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutput; 
import java.io.ObjectOutputStream; 

import javax.faces.bean.ManagedBean; 
import javax.faces.bean.ManagedProperty; 
import javax.faces.bean.RequestScoped; 
import javax.faces.component.UIComponent; 
import javax.faces.context.FacesContext; 
import javax.faces.convert.Converter; 
import javax.faces.convert.ConverterException; 
import javax.persistence.EntityManagerFactory; 

/** 
* Generic converter of jpa entities for jsf 
* 
* Converts the jpa instances to strings with this form: @ Converts from strings to instances searching by id in 
* database 
* 
* It is possible thanks to the fact that jpa requires all entity ids to 
* implement serializable 
* 
* Requires: - You must provide instance with name "entityManagerFactory" to be 
* injected - Remember to implement equals and hashCode in all your entity 
* classes !! 
* 
*/ 
@ManagedBean 
@RequestScoped 
public class EntityConverter implements Converter { 

    private static final char CHARACTER_SEPARATOR = '@'; 

    @ManagedProperty(value = "#{entityManagerFactory}") 
    private EntityManagerFactory entityManagerFactory; 

    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { 
     this.entityManagerFactory = entityManagerFactory; 
    } 

    private static final String empty = ""; 

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

     int index = value.indexOf(CHARACTER_SEPARATOR); 
     String clazz = value.substring(0, index); 
     String idBase64String = value.substring(index + 1, value.length()); 
EntityManager entityManager=null; 
     try { 
      Class entityClazz = Class.forName(clazz); 
      Object id = convertFromBase64String(idBase64String); 

     entityManager = entityManagerFactory.createEntityManager(); 
     Object object = entityManager.find(entityClazz, id); 

      return object; 

     } catch (ClassNotFoundException e) { 
      throw new ConverterException("Jpa entity not found " + clazz, e); 
     } catch (IOException e) { 
      throw new ConverterException("Could not deserialize id of jpa class " + clazz, e); 
     }finally{ 
     if(entityManager!=null){ 
      entityManager.close(); 
     } 
    } 

    } 

    @Override 
    public String getAsString(FacesContext context, UIComponent c, Object value) { 
     if (value == null) { 
      return empty; 
     } 
     String clazz = value.getClass().getName(); 
     String idBase64String; 
     try { 
      idBase64String = convertToBase64String(entityManagerFactory.getPersistenceUnitUtil().getIdentifier(value)); 
     } catch (IOException e) { 
      throw new ConverterException("Could not serialize id for the class " + clazz, e); 
     } 

     return clazz + CHARACTER_SEPARATOR + idBase64String; 
    } 

    // UTILITY METHODS, (Could be refactored moving it to another place) 

    public static String convertToBase64String(Object o) throws IOException { 
     return javax.xml.bind.DatatypeConverter.printBase64Binary(convertToBytes(o)); 
    } 

    public static Object convertFromBase64String(String str) throws IOException, ClassNotFoundException { 
     return convertFromBytes(javax.xml.bind.DatatypeConverter.parseBase64Binary(str)); 
    } 

    public static byte[] convertToBytes(Object object) throws IOException { 
     try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) { 
      out.writeObject(object); 
      return bos.toByteArray(); 
     } 
    } 

    public static Object convertFromBytes(byte[] bytes) throws IOException, ClassNotFoundException { 
     try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInput in = new ObjectInputStream(bis)) { 
      return in.readObject(); 
     } 
    } 

} 

使用它像另一个转换器

<h:selectOneMenu converter="#{entityConverter}" ...