2012-08-04 212 views
1

我有幾個實體具有基本的Long ID和其他屬性。這些實體與另一個實體具有一對多關係,爲第一個實體保存不同的自定義(用戶輸入)翻譯。基本上有一個實體對,一個持有所有非翻譯內容,另一個持有文本屬性的多個翻譯。JPA與Java泛型

爲了減少代碼和註釋的重複,我想爲這些對之一中的每個實體創建一個抽象類。對於多種翻譯我創造了這樣一個類:

@MappedSuperclass 
public abstract class CustomTranslations 
{ 
    @Id 
    protected Long id; 
    @Id 
    protected String locale; 
} 

對於主要實體我有這樣的:

@MappedSuperclass 
public abstract class CustomTranslationsHolder<T extends CustomTranslations> 
{ 
    @OneToMany(fetch=FetchType.EAGER) 
    @JoinColumn(name="ID") 
    @MapKey(name="locale") 
    protected Map<String, T> translationsByLocale; 
} 

所以說實體對之一是Foo。我想有這樣的:

@Entity 
@Table(name="FOO_TRANSLATIONS") 
public class FooTranslations extends CustomTranslations 
{ 
    private String title; 
    private String description; 
    .... 
} 

這:

@Entity 
public class Foo extends CustomTranslationsHolder<FooTranslations> 
{ 
    @Id 
    private Long id; 
    private String whatever; 
    private Integer blah; 
    ... 
    public String getTitle(String locale) 
    { 
     return translationsByLocale.get(locale).getTitle(); 
    } 
} 

這一切編譯得很好,但在服務器啓動時,我得到以下錯誤:

Exception Description: Neither the instance method or field named [locale] exists for the item class [class java.lang.Void], and therefore cannot be used to create a key for the Map. 
at org.eclipse.persistence.exceptions.ValidationException.mapKeyNotDeclaredInItemClass(ValidationException.java:1332) 
at org.eclipse.persistence.internal.queries.MapContainerPolicy.initializeKey(MapContainerPolicy.java:517) 
at org.eclipse.persistence.internal.queries.MapContainerPolicy.getKeyType(MapContainerPolicy.java:438) 
at org.eclipse.persistence.internal.jpa.metamodel.MapAttributeImpl.<init>(MapAttributeImpl.java:167) 
at org.eclipse.persistence.internal.jpa.metamodel.ManagedTypeImpl.initialize(ManagedTypeImpl.java:1158) 
at org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl.initialize(MetamodelImpl.java:459) 
at org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl.<init>(MetamodelImpl.java:111) 
at org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl.<init>(MetamodelImpl.java:130) 
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.getMetamodel(EntityManagerSetupImpl.java:2566) 
at org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate.getMetamodel(EntityManagerFactoryDelegate.java:592) 
at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.getMetamodel(EntityManagerFactoryImpl.java:506) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.invokeProxyMethod(AbstractEntityManagerFactoryBean.java:376) 
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean$ManagedEntityManagerFactoryInvocationHandler.invoke(AbstractEntityManagerFactoryBean.java:517) 
at $Proxy8.getMetamodel(Unknown Source) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:176) 
at $Proxy12.getMetamodel(Unknown Source) 
at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:60) 
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:149) 
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:87) 
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:70) 
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:137) 
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:125) 
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:41) 
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142) 
... 44 more 

所以,它聽起來像它可以在T上找到屬性locale的地圖。它似乎應該知道TCustomTranslations,因此知道CustomTranslations上有一個locale字段。

這是EclipseLink的問題嗎?還是有沒有辦法我可以這樣做?我很想知道Hibernate如何處理完全相同的代碼。任何信息或建議,將不勝感激。

+0

您使用的是什麼版本的eclipselink? – Chris 2012-08-13 15:21:52

+0

這是2.3.2 – dnc253 2012-08-13 20:17:25

回答

8

接受的答案是錯的。泛型在JPA或EclipseLink中似乎不被很好的支持(可能是因爲它們使用字節碼編織而不是反射)。 無論何種類型的擦除,這些信息都可以在運行時使用反射。

T的實際運行時類型被擦除,但T延伸CustomTranslations的事實不是。這是在編譯時指定的,並存儲在可從Class實例訪問的類元數據中。

您可以撥打CustomTranslationsHolder.class.getTypeParameters()它會返回一個數組一個TypeVariable元素名稱爲「T」上,您可以撥打getBounds()將返回"CustomTranslations"。因此,地圖中的值可以被證明包含locale屬性。

我會向其他JPA提供商提交錯誤和/或測試。

0

JPA規範說:

Persistent relationships defined by a mapped superclass must be unidirectional.

這就是爲什麼你不能定義CustomTranslationsHolder爲被映射的父,因爲它與其他映射超關係多對一。這個類可能是一個實體。

@Entity 
public abstract class CustomTranslationsHolder<T extends CustomTranslations> 
{ 
    @OneToMany(fetch=FetchType.EAGER) 
    @JoinColumn(name="ID") 
    @MapKey(name="locale") 
    protected Map<String, T> translationsByLocale; 
} 

但在這種情況下,JPA將無法在運行時不幸提取T類。

我建議檢討你的設計。