当前位置: 首页 > 知识库问答 >
问题:

为具有Java泛型的实体实现转换器

淳于博文
2023-03-14

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

>

getAsString接收并返回转换为String

代码基本如下(省略检查):

@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的子类

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

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

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

如果有,还有其他方法吗?

共有3个答案

曾航
2023-03-14

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

java prettyprint-override">@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);
    }
}

车诚
2023-03-14

以下是我的解决方案:

  • 我想你对JPA感兴趣(不是Hibernate)
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}" ...
裴卓君
2023-03-14

最简单的方法是让所有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())非常重要,否则您将面临验证错误:值无效。Class#isAssignableFrom()测试是为了避免在基于Hibernate的代理上测试失败,而无需返回到特定于Hibernate的Hibernate#getClass(Object)helper方法。

并拥有这样的基础服务(是的,我忽略了您正在使用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等将服务注入转换器。另请参见如何在@FacesConverter中注入@EJB、@PersistenceContext、@inject、@Autowired等?因此,您需要将其引用为converter=“#{baseEntityConverter}”,而不是converter=“baseEntityConverter”

如果您碰巧经常将这样的转换器用于UISelectOne/UISelect许多组件

 类似资料:
  • 它是关于java中具有两种泛型类型(一种用于返回类型,另一种用于形式参数)的泛型方法以及如何实现它。我想我在图片中缺少一些东西来让它工作。 事情是这样的... 这是工作 : 这不起作用: java编译器告诉我: 好吧,就是这样。提前感谢大家!希望它能在未来帮助别人! P、 D:这不是枚举类型的问题。它发生在类和层次结构之间。所以别怪Enum了,我试过了,但没用。 对于泛型参数 我们可以做一个一般规

  • 我试图创建一个通用对象转换器,为我的dto<->实体类。我创建了一个抽象类,它有两个函数代表两种转换,然后在我的具体转换器类中扩展了它。 但我希望有一个通用的转换服务,我可以在启动期间注册所有的转换器,然后方便地调用一个方法来处理彼此之间的转换。 到目前为止,我想说的是: 转换器抽象类 转炉混凝土等级 我想实现这样的目标(这是我需要帮助的地方): 该服务看起来如下所示:

  • 和函数类似,实现(implementation)也需要关注保持泛型。(原文:Similar to functions, implementations require care to remain generic.) struct S; // 具体类型 `S` struct GenericVal<T>(T,); // 泛型类型 `GenericVal` // GenericVal 的实现,此处我们

  • 问题内容: 我宁愿喜欢Java 7引入的泛型的菱形语法- 从节省时间的角度来看,并不是那么多(毕竟,大多数IDE都会为您填补这一点),而仅仅是因为它使代码看起来更简洁。由于这个原因和其他原因(主要是我正在开发一个新软件,并且Java 7中的一些新API会很有用),我很可能会切换现有代码库以使用/要求Java 7。 但是,已经有相当一部分已经写成钻石前的语法,我希望在整个过程中始终使用菱形语法。是否

  • 我面临一个java泛型的问题,我的计划是实现一个二叉查找树(key 中的if语句不被接受,我认为这是因为我重写了comareTo,但是我应该如何比较泛型? 还尝试了而不是,结果相同。 最好的问候 编辑:编译器说:在这一行的多个标记-比较类型中的方法comareTo(捕获#1-of?)不适用于参数(可比)-行断点: KeyValPair[line: 39]-comareTo(KeyValPair)

  • 在java中,是否可以使用通配符定义抽象方法,但在实现中使用具体类型 eg: 在这样的抽象类中定义抽象方法 实现如下抽象方法,其中CorporateServer扩展了用户: