使用 Java 泛型为实体实现转换器

     2023-02-23     184

关键词:

【中文标题】使用 Java 泛型为实体实现转换器【英文标题】:Implement converters for entities with Java Generics 【发布时间】: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 类)所以我的第一个问题是:这值得麻烦吗?

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

【问题讨论】:

【参考方案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 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 组件(&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);
    

【讨论】:

使用泛型为异步队列提供任务类型

】使用泛型为异步队列提供任务类型【英文标题】:ProvideTasktypeforasyncqueueusinggenerics【发布时间】:2018-12-0413:09:50【问题描述】:我现在有这个:exporttypeEVCb=(err:any,val?:any)=>void;exporttypeTask=(cb:EVCb)=>void;exportconstq=async.queue((task:T... 查看详情

使用泛型为异步队列提供任务类型

】使用泛型为异步队列提供任务类型【英文标题】:ProvideTasktypeforasyncqueueusinggenerics【发布时间】:2018-12-0413:09:50【问题描述】:我现在有这个:exporttypeEVCb=(err:any,val?:any)=>void;exporttypeTask=(cb:EVCb)=>void;exportconstq=async.queue((task:T... 查看详情

java示例代码_实现具有泛型关系的多态JPA实体

java示例代码_实现具有泛型关系的多态JPA实体 查看详情

把实体类转成xml让list的最外层标签失效

使用C#实现实体类和XML相互转换一.实体类转换成XML将实体类转换成XML需要使用XmlSerializer类的Serialize方法,将实体类序列化publicstaticstringXmlSerialize<T&...简单实体类和xml文件的相互转换最近写一个题目,要求将一组员工实体类转换成... 查看详情

java中泛型的深入理解(代码片段)

...数据类型。集合体系的全部接口和实现类都是支持泛型的使用的。泛型的好处:统一数据类型。把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。泛型可以在很... 查看详情

java泛型java使用泛型的意义

Java泛型Java使用泛型的意义@authorixenos  直接意义在编译时保证类型安全 根本意义a)类型安全问题源自可复用性代码的设计,泛型保证了类型安全的复用模板b)使用复用性模板时不用手动强制类型转换 三种泛型实现... 查看详情

java子类重写父类带泛型的方法

...T的子类了,为什么还是报错?谢谢指教!特殊变量super*使用特殊变量super提供对父类的访问*可以使用super访问父类被子类隐藏的变量或覆盖的方法*每个子类构造方法的第一条语句都是隐含的调用super,如果父类没有这种形式的构... 查看详情

泛型(generic)

...其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList 查看详情

利用泛型和反射实现idatareader转实体

publicstaticTReaderToModel<T>(IDataReaderrow){//1、使用与指定参数匹配最高的构造函数,来创建指定类型的实例TypemodelType=typeof(HShopingCarModel);Tmodel=Activator.CreateInstance<T>();for(inti=0;i<row.FieldCount;i++) 查看详情

c#中的泛型

...其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList<int>,MyList<st 查看详情

再谈怎样以最简单的方法将泛型为string类型的集合或string类型的数组转化为逗号间隔字符串形式

    今天review代码,看见某些大爷在将泛型为String类型的集合或String类型的数组转化为逗号间隔字符串形式时仍然仅仅顾结果不注重过程,“大爷”咱能负点责任吗?     将泛型为String类型的集合... 查看详情

java泛型实现原理

...型信息,仅仅Class的实例中包含了类型参数的定义信息。泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本转换成非泛型版本。基本上,擦除... 查看详情

java示例代码_使用泛型实现enum

java示例代码_使用泛型实现enum 查看详情

java中的泛型

...前,java并不直接支持泛型实现,泛型编程的实现是通过使用继承的一些基本概念来完成的。1.1使用Object表示泛型java中的基本思想就是可以通过使用想Objec 查看详情

java泛型简单剖析与使用(代码片段)

...为泛型化的了,这带来了很多好处。类型安全:使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运... 查看详情

java泛型的实现原理

...JAVA的人都知道一点泛型,明白常出现的位置和大概怎么使用。  在类上为:class类名<T>{}  在方法上为:public<T>void方法名(Tx){}  就不再赘述了。  2.泛型就是将类型变成了参数去传入,使得可以使用的类型多... 查看详情

java基础-泛型的优点

1、性能  对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。装箱和拆箱的操作很容易实现,但是性能损失较大。假如使用泛型,就可以避免装箱和拆箱操作... 查看详情

java示例代码_使用泛型类型强制转换集合

java示例代码_使用泛型类型强制转换集合 查看详情