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

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

  • getAsObject接收对象id的字符串表示forms,将其转换为数字,并获取给定种类的实体和给定的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与此完全相同(当然除了MyServiceMyEntity的类型),我想知道是否值得使用单个通用转换器。 通用本身的实现并不困难,但我不确定声明Beans的正确方法。

可能的解决方案如下:

1 – 编写通用实现,让我们称之为MyGenericConverter ,不带任何Bean注释

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

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

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

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

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

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

 public abstract class BaseEntity 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错误:值无效 。 Class#isAssignableFrom()测试是为了避免在例如基于Hibernate的代理上进行测试失败,而不需要回退到特定于Hibernate的Hibernate#getClass(Object)辅助方法。

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

 @Stateless public class BaseService { @PersistenceContext private EntityManager em; public BaseEntity find(Class> 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>) 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在转换器中注入服务。 另请参阅如何在@FacesConverter中注入@ EJB,@ PersistenceContext,@ Inject,@ Autowired等? 所以你需要引用它作为converter="#{baseEntityConverter}"而不是converter="baseEntityConverter"

如果碰巧UISelectOne / UISelectMany组件( 和朋友)经常使用这样的转换器,您可能会发现OmniFaces SelectItemsConverter更有用。 它基于可用的值进行转换,而不是每次都进行(可能很昂贵的)DB调用。

以下是我的解决方案:

  • 我认为你对JPA感兴趣(不是Hibernate)
  • 我的解决方案不需要扩展任何类,并且应该适用于任何JPA实体bean,它只是您使用的简单类, 也不需要实现任何服务或DAO 。 唯一的要求是转换器直接依赖于JPA库,这可能不是很优雅。
  • 它使用辅助方法来序列化/反序列化bean的id。 它只转换实体bean的id,并将字符串与classname和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;

 / **
  * jsf的jpa实体的通用转换器
  * 
  *将jpa实例转换为具有以下forms的字符串:@从字符串转换为按id搜索的实例
  *数据库
  * 
  *由于jpa需要所有实体ID这一事实,这是可能的
  *实现可序列化
  * 
  *要求: - 您必须提供名称为“entityManagerFactory”的实例
  *注入 - 请记住在所有实体中实现equals和hashCode
  *课程!!
  * 
  * /
 @ManagedBean
 @RequestScoped
公共类EntityConverter实现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 =“”;

     @覆盖
     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;
        尝试{
             class entityClazz = Class.forName(clazz);
             Object id = convertFromBase64String(idBase64String);

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

            返回对象;

         } catch(ClassNotFoundException e){
            抛出新的ConverterException(“未找到Jpa实体”+ clazz,e);
         } catch(IOException e){
            抛出新的ConverterException(“无法反序列化jpa类的id”+ clazz,e);
         }最后{
        如果(EntityManager的!= NULL){
             entityManager.close();  
         }
     }

     }

     @覆盖
     public String getAsString(FacesContext context,UIComponent c,Object value){
         if(value == null){
            返回空
         }
         String clazz = value.getClass()。getName();
         String idBase64String;
        尝试{
             idBase64String = convertToBase64String(entityManagerFactory.getPersistenceUnitUtil()。getIdentifier(value));
         } catch(IOException e){
            抛出新的ConverterException(“无法序列化类的id”+ clazz,e);
         }

         return clazz + CHARACTER_SEPARATOR + idBase64String;
     }

     //实用方法,(可以重构将其移动到另一个地方)

     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)抛出IOException {
         try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)){
             out.writeObject(对象);
             return bos.toByteArray();
         }
     }

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

 }

像其他转换器一样用它