为具有Java Generics的实体实现转换器 [英] Implement converters for entities with Java Generics
问题描述
我正在使用Spring和Hibernate开发JSF项目,其中包含一些遵循相同模式的 Converter
s:
-
getAsObject
接收对象id的字符串表示形式,将其转换为数字,给定种类的实体和给定的id
getAsString接收实体并返回该实体的id对象转换为字符串
(省略检查):
@ManagedBean(name =myConverter)
@SessionScoped
public class MyConverter实现Converter {
private MyService myService;
$ b $ * / *
@Override
public Object getAsObject(FacesContext facesContext,UIComponent uiComponent,String value){
int id = Integer.parseInt(值);
返回myService.getById(id);
$ b @Override
public String getAsString(FacesContext facesContext,UIComponent uiComponent,Object value){
return((MyEntity)value).getId()。toString ();
code $
由于大量 ( MyService
和 MyEntity
)类型的转换器
当然),我想知道是否值得使用单个通用转换器。
泛型的实现本身并不困难,但我不确定正确的方法来声明Bean。
可能的解决方案是以下内容:
$ b 1 1 - 编写通用实现,我们称之为 MyGenericConverter
,没有任何Bean注解
将特定转换器ad写入 MyGenericConverter< T>
的子类中,并根据需要对其进行注释: @ManagedBean(name =myFooConverter)
@SessionScoped
public class MyFooConverter实现MyGenericConverter< Foo> {b $ b / * ... * /
}
意识到也许Generic不是真的需要,所以也许我可以简单地编写一个基类来实现这两个方法,并根据需要编写子类。
有几个不必要的细节(例如,我必须以某种方式抽象 MyService
类),所以我的第一个问题是:它是否值得麻烦?
如果有的话,还有其他方法吗?
解决方案最简单的就是让所有的JPA实体都像这样从一个基础实体扩展出来:
public abstract class BaseEntity< T extends Number>实现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
&& amp; other.getClass()。isAssignableFrom(getClass())
&& getClass()。isAssignableFrom(other.getClass()))
? getId()。equals(((BaseEntity<>)other).getId())
:(other == this);
$ b @Override
public String toString(){
return String.format(%s [id =%d],getClass()。getSimpleName (),getId());
}
}
请注意,正确 equals()
(和 hashCode()
),否则您将面对验证错误:值无效。 Class#isAssignableFrom()
测试是为了避免在例如基于Hibernate的代理无需退回到特定于Hibernate的 Hibernate#getClass(Object)
帮助程序方法
有一个这样的基础服务(是的,我忽略了你使用Spring的事实,只是为了给出基本的想法):
@Stateless
public class BaseService {
@PersistenceContext
private EntityManager em;
public BaseEntity <?扩展Number> find(Class< BaseEntity< ;? extends Number>> type,Number id){
return em.find(type,id);
}
}
并执行转换器如下:
@ManagedBean
@ApplicationScoped
@SuppressWarnings({rawtypes,unchecked})/ /我们并不关心BaseEntity的实际类型。
public class BaseEntityConverter实现Converter {
@EJB
private BaseService baseService;
$ b $ @Override
public String getAsString(FacesContext context,UIComponent component,Object value){
if(value == null){
return; $(b)b
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 a valid User,modelValue)),e);
$ b @Override
public Object getAsObject(FacesContext context,UIComponent component,String value){
if(value == null || value.isEmpty()){
return null;
}
尝试{
Class<?> type = component.getValueExpression(value)。getType(context.getELContext());
返回baseService.find((Class< BaseEntity< ;? extends Number>>)类型,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,@ Inject,@Autowired等在@FacesConverter?所以你需要引用它作为 converter =#{baseEntityConverter}
而不是转换器=baseEntityConverter
。
如果您碰巧使用这种转换器的频率比 UISelectOne
/ UISelectMany
组件(< h:selectOneMenu>
和朋友),您可能会发现 OmniFaces SelectItemsConverter
更有用。它根据< f:selectItems>
中的值进行转换,而不是每次都进行(可能是昂贵的)数据库调用。
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
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
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();
}
}
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.
A possible solution is the following:
1 - Write the generic implementation, let's call it MyGenericConverter
, without any Bean annotation
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.
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 ?
解决方案 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());
}
}
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.
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);
}
}
And implement the converter as follows:
@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);
}
}
}
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"
.
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 Generics的实体实现转换器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!