使用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
与此完全相同(当然除了MyService
和MyEntity
的类型),我想知道是否值得使用单个通用转换器。 通用本身的实现并不困难,但我不确定声明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 extends Number> 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(); } } }
像其他转换器一样用它