使用 Java 泛型为实体实现转换器 [英] Implement converters for entities with Java Generics
问题描述
我正在使用 Spring 和 Hibernate 开发 JSF 项目,其中有许多遵循相同模式的 Converter
:
I'm working on JSF project with Spring and Hibernate which among other things has a number of Converter
s that follow the same pattern:
getAsObject
接收对象 id 的字符串表示,将其转换为数字,并获取给定种类和给定 id 的实体
getAsObject
receives the string representation of the object id, converts it to a number, and fetch the entity of the given kind and the given id
getAsString
接收实体并返回转换为String
getAsString
receives and entity and returns the id of the object converted to String
代码基本如下(省略检查):
The code is essentially what follows (checks omitted):
@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
的类型除外),我想知道是否值得使用单个通用转换器.实现泛型本身并不困难,但我不确定声明 Bean 的正确方法.
Given the large number of Converter
s that are exactly like this (except for the type of MyService
and MyEntity
of course), I was wondering if it was worth using a single generic converter.
The implementation of the generic by itself is not difficult, but I'm not sure about the right approach to declare the Beans.
可能的解决方案如下:
1 - 编写通用实现,我们称之为 MyGenericConverter
,没有任何 Bean 注释
1 - Write the generic implementation, let's call it MyGenericConverter
, without any Bean annotation
2 - 将特定转换器写入 MyGenericConverter
的子类,并根据需要对其进行注释:
2 - Write the specific converter ad a subclass of MyGenericConverter<T>
and annotate it as needed:
@ManagedBean(name="myFooConverter")
@SessionScoped
public class MyFooConverter implements MyGenericConverter<Foo> {
/* ... */
}
在写这篇文章时,我意识到可能并不真正需要泛型,所以也许我可以简单地编写一个包含这两种方法实现的基类,并根据需要编写子类.
While writing this I realized that maybe a Generic is not really needed, so maybe I could simply write a base class with the implementation of the two methods, and subclass as needed.
有一些重要的细节需要处理(比如我必须以某种方式抽象 MyService
类)所以我的第一个问题是:值得吗?麻烦吗?
There a few non trivial details that have to be taken care of (like the fact that I'd have to abstract the MyService
class in some way) so my first question is : is it worth the hassle ?
如果是这样,还有其他方法吗?
And if so, are there other approaches ?
推荐答案
最简单的方法是让所有 JPA 实体从这样的基础实体扩展:
Easiest would be to let all your JPA entities extend from a base entity like this:
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)
辅助方法.
Note that it's important to have a proper equals()
(and hashCode()
), otherwise you will face Validation Error: Value is not valid. The Class#isAssignableFrom()
tests are to avoid failing tests on e.g. Hibernate based proxies without the need to fall back to Hibernate-specific Hibernate#getClass(Object)
helper method.
并拥有这样的基本服务(是的,我忽略了您使用 Spring 的事实;这只是提供基本想法):
And have a base service like this (yes, I'm ignoring the fact that you're using Spring; it's just to give the base idea):
@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
.另请参阅如何注入@EJB、@PersistenceContext、@在@FacesConverter 中注入、@Autowired 等? 所以你需要将它引用为 converter="#{baseEntityConverter}"
而不是 converter="baseEntityConverter"
.
Note that it's registered as a @ManagedBean
instead of a @FacesConverter
. This trick allows you to inject a service in the converter via e.g. @EJB
. See also How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter? So you need to reference it as converter="#{baseEntityConverter}"
instead of converter="baseEntityConverter"
.
如果你碰巧经常使用这样的转换器 UISelectOne
/UISelectMany
组件(
和朋友们),你可能会发现 OmniFaces SelectItemsConverter
更有用.它根据
中可用的值进行转换,而不是每次都进行(可能很昂贵的)数据库调用.
If you happen to use such a converter more than often for UISelectOne
/UISelectMany
components (<h:selectOneMenu>
and friends), you may find OmniFaces SelectItemsConverter
much more useful. It converts based on the values available in <f:selectItems>
instead of making (potentially expensive) DB calls everytime.
这篇关于使用 Java 泛型为实体实现转换器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!