【问题标题】:Implement converters for entities with Java Generics使用 Java 泛型为实体实现转换器
【发布时间】:2013-06-24 22:44:31
【问题描述】:

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

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

鉴于大量的Converters 与此完全相同(当然MyServiceMyEntity 的类型除外),我想知道是否值得使用单个通用转换器。 泛型本身的实现并不难,但我不确定声明 Bean 的正确方法。

可能的解决方案如下:

1 - 编写通用实现,我们称之为MyGenericConverter,没有任何Bean注解

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

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

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

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

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

【问题讨论】:

    标签: java spring jsf generics converter


    【解决方案1】:

    最简单的方法是让您的所有 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 validClass#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 组件(&lt;h:selectOneMenu&gt; 和朋友),您可能会发现OmniFaces SelectItemsConverter 更有用。它根据&lt;f:selectItems&gt; 中可用的值进行转换,而不是每次都进行(可能很昂贵)数据库调用。

    【讨论】:

    • 不建议在 equals 和 hashCode 方法中使用“id”属性:他们建议使用业务键相等来实现 equals() 和 hashCode():docs.jboss.org/hibernate/stable/core.old/reference/en/html/… 复合 id 怎么样?
    • 没错。但这些不属于 BaseEntity。您可以只覆盖特定实体子类中的 equals/hashCode。将所有可能的情况都直接放在 BaseEntity 中是不可能和丑陋的。
    【解决方案2】:

    这是我考虑到的解决方案:

    • 我假设您对 JPA(不是 Hibernate)感兴趣
    • 我的解决方案不需要扩展任何类并且应该适用于任何 JPA 实体 bean,它只是您使用的一个简单类,也不需要实现任何服务或 DAO。唯一的要求是转换器直接依赖于 JPA 库,这可能不是很优雅。
    • 它使用辅助方法来序列化/反序列化 bean 的 id。它只转换实体 bean 的 id 并将字符串与 classname 和 id 序列化并转换为 base64 复合。这是可能的,因为在 jpa 中,实体的 id 必须实现可序列化。此方法的实现是在 java 1.7 中,但您可以在那边找到 java
    导入 java.io.ByteArrayInputStream; 导入 java.io.ByteArrayOutputStream; 导入 java.io.IOException; 导入 java.io.ObjectInput; 导入 java.io.ObjectInputStream; 导入 java.io.ObjectOutput; 导入 java.io.ObjectOutputStream; 导入 javax.faces.bean.ManagedBean; 导入 javax.faces.bean.ManagedProperty; 导入 javax.faces.bean.RequestScoped; 导入 javax.faces.component.UIComponent; 导入 javax.faces.context.FacesContext; 导入 javax.faces.convert.Converter; 导入 javax.faces.convert.ConverterException; 导入 javax.persistence.EntityManagerFactory; /** * 用于 jsf 的 jpa 实体的通用转换器 * * 使用这种形式将 jpa 实例转换为字符串: @ 将字符串转换为通过 id 搜索的实例 * 数据库 * * 这可能要归功于 jpa 要求所有实体 ID * 实现可序列化 * * 要求: - 您必须提供名称为“entityManagerFactory”的实例 * 注入 - 记得在你的所有实体中实现 equals 和 hashCode * 上课!! * */ @ManagedBean @RequestScoped 公共类 EntityConverter 实现 Converter { 私有静态最终字符 CHARACTER_SEPARATOR = '@'; @ManagedProperty(value = "#{entityManagerFactory}") 私有 EntityManagerFactory entityManagerFactory; 公共无效 setEntityManagerFactory(EntityManagerFactory entityManagerFactory){ this.entityManagerFactory = entityManagerFactory; } 私有静态最终字符串空=“”; @覆盖 public Object getAsObject(FacesContext context, UIComponent c, String value) { if (value == null || value.isEmpty()) { 返回空值; } int index = value.indexOf(CHARACTER_SEPARATOR); String clazz = value.substring(0, index); String idBase64String = value.substring(index + 1, value.length()); 实体管理器实体管理器=空; 尝试 { 类 entityClazz = Class.forName(clazz); 对象 id = convertFromBase64String(idBase64String); entityManager = entityManagerFactory.createEntityManager(); 对象 object = entityManager.find(entityClazz, id); 返回对象; } 捕捉(ClassNotFoundException e){ throw new ConverterException("找不到 Jpa 实体" + clazz, e); } 捕捉(IOException e){ throw new ConverterException("无法反序列化 jpa 类的 id" + clazz, e); }最后{ 如果(实体管理器!=空){ entityManager.close(); } } } @覆盖 public String getAsString(FacesContext context, UIComponent c, Object value) { 如果(值 == 空){ 返回空; } 字符串 clazz = value.getClass().getName(); 字符串 idBase64String; 尝试 { idBase64String = convertToBase64String(entityManagerFactory.getPersistenceUnitUtil().getIdentifier(value)); } 捕捉(IOException e){ throw new ConverterException("无法为类序列化 id" + clazz, e); } 返回 clazz + CHARACTER_SEPARATOR + idBase64String; } // UTILITY METHODS, (可以重构移动到另一个地方) 公共静态字符串 convertToBase64String(Object o) 抛出 IOException { 返回 javax.xml.bind.DatatypeConverter.printBase64Binary(convertToBytes(o)); } 公共静态对象 convertFromBase64String(String str) 抛出 IOException,ClassNotFoundException { 返回 convertFromBytes(javax.xml.bind.DatatypeConverter.parseBase64Binary(str)); } 公共静态字节 [] convertToBytes(对象对象)抛出 IOException { 尝试 (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) { out.writeObject(object); 返回 bos.toByteArray(); } } 公共静态对象 convertFromBytes(byte[] bytes) 抛出 IOException,ClassNotFoundException { 尝试(ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInput in = new ObjectInputStream(bis)) { 返回 in.readObject(); } } }

    像另一个转换器一样使用它

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

    【讨论】:

      【解决方案3】:

      您的实体不需要从BaseEntity 继承,因为EntityManagerFactory 包含所有必要的(元)信息。 您还可以重用 JSF Converters 来转换/解析 id。

      @FacesConverter(value = "entityConverter", managed = true)
      public class EntityConverter implements Converter<Object> {
      
          @Inject
          private EntityManager entityManager;
      
          @Override
          public Object getAsObject(FacesContext context, UIComponent component, String value) {
              Class<?> entityType = component.getValueExpression("value").getType(context.getELContext());
              Class<?> idType = entityManager.getMetamodel().entity(entityType).getIdType().getJavaType();
              Converter idConverter = context.getApplication().createConverter(idType);
              Object id = idConverter.getAsObject(context, component, value);
              return entityManager.getReference(entityType, id);
          }
      
          @Override
          public String getAsString(FacesContext context, UIComponent component, Object value) {
              Object id = entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(value);
              Converter idConverter = context.getApplication().createConverter(id.getClass());
              return idConverter.getAsString(context, component, id);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2010-12-15
        • 1970-01-01
        • 2023-04-04
        • 1970-01-01
        • 2021-08-12
        • 1970-01-01
        • 2017-10-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多