8

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

  • getAsObject接收对象 id 的字符串表示,将其转换为数字,并获取给定种类和给定 id 的实体

  • getAsString接收和实体并返回转换为的对象的idString

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

@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某种方式抽象类)所以我的第一个问题是:这值得麻烦吗?

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

4

3 回答 3

17

最简单的方法是让您的所有 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()(and 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);
        }
    }

}

请注意,它被注册为 a@ManagedBean而不是 a @FacesConverter。这个技巧允许您通过例如在转换器中注入服务@EJB。另请参阅如何在 @FacesConverter 中注入 @EJB、@PersistenceContext、@Inject、@Autowired 等?因此,您需要将其引用为converter="#{baseEntityConverter}"而不是converter="baseEntityConverter".

如果您碰巧经常将这种转换器用于UISelectOne/UISelectMany组件(<h:selectOneMenu>和朋友),您可能会发现OmniFaces SelectItemsConverter更有用。它根据可用的值进行转换,<f:selectItems>而不是每次都进行(可能很昂贵)数据库调用。

于 2013-06-27T12:46:41.613 回答
0

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

  • 我假设您对 JPA(不是 Hibernate)感兴趣
  • 我的解决方案不需要扩展任何类,应该适用于任何 JPA 实体 bean,它只是您使用的一个简单类,也不需要实现任何服务或 DAO。唯一的要求是转换器直接依赖于 JPA 库,这可能不是很优雅。
  • 它使用辅助方法来序列化/反序列化 bean 的 id。它只转换实体 bean 的 id 并将字符串与 classname 和 id 序列化并转换为 base64 复合。这是可能的,因为在 jpa 中实体的 id必须实现可序列化。这个方法的实现是在 java 1.7 中,但是你可以在那里找到 java < 1.7 的另一个实现
导入 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));
    }

    public static byte[] convertToBytes(Object object) 抛出 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}" ...
于 2016-10-16T10:46:53.520 回答
0

您的实体不需要从 a 继承,BaseEntity因为它EntityManagerFactory包含所有必要的(元)信息。您还可以重用 JSFConverters来转换/解析 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);
    }
}
于 2021-02-25T09:15:53.617 回答