2015-09-28 64 views
7

我有一個奇怪的問題,即hibernate沒有在多對一的關係中創建預期的實體類型。我們有子類層次結構下面的實體(簡化):Hibernate在關係中創建錯誤的實體子類型

@Entity 
@Table(name = "A") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class A { 

    @Id 
    ... 
    public Long getId() { ... } 
    ... 
} 

@Entity 
@DiscriminatorValue("1") 
public class A1 extends A { 
    ... 
} 

@Entity 
@DiscriminatorValue("2") 
public class A2 extends A { 
    ... 
} 


@Entity 
@Table(name = "B") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class B<AClass extends A> { 

    protected AClass a; 

    @Id 
    ... 
    public Long getId() { ... } 
    ... 

    public abstract AClass getA(); 
    public void setA(AClass a) { ... } 
} 

@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    ... 

    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A_ID") 
    public A1 getA() { ... } 
} 

@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    ... 

    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A_ID") 
    public A2 getA() { ... } 
} 

persistence.xml兩個實體在

A2 
A1 
B2 
B1 

現在我在DB創建A1的實例和B1的順序聲明:

A1 a1 = new A1(); 
entityManager.persist(a1); 
B1 b1 = new B1(); 
b1.setA(a1); 
entityManager.persist(b1); 

我可以看到實例保存到數據庫的每個ID都是1,DISCRIMINATOR也是1,B中的A_ID也是1.

現在當我試圖讓B(在另一個Hibernate的Session):

B b = entityManager.find(B.class, 1L); 

我得到異常:

org.hibernate.PropertyAccessException: Exception occurred inside getter of B 
Caused by: java.lang.ClassCastException: A2 cannot be cast to A1 
at B1.getA(B1.java:61) 
... 108 more 

調試,我發現,Hibernate是創造正確的實體鍵入B1併爲與A的關係創建類型A2的不正確實體。如果persistence.xml中的順序發生更改,則會創建正確的類型A1。在這種情況下,hibernate似乎並未將A表的DISCRIMINATOR列考慮在內,但總是創建在配置中聲明的第一個子類型。這怎麼解決?註釋有問題嗎?

(我也有其在超B中第一註釋的具體實施方法getA()的,但是這會導致類似的問題。)

回答

3

隨着休眠5.0.2.Final我能夠使你的例子工作使用@ManyToOne(..., targetEntity = A.class)。我也用普通的吸氣器代替了public abstract AClass getA();

@Entity 
@Table(name = "B") 
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) 
public abstract class B<AClass extends A> { 
    private Long id; 
    private AClass a; 

    @Id 
    @GeneratedValue 
    public Long getId() { 
     return id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = A.class) 
    @JoinColumn(name = "A_ID") 
    public AClass getA() { 
     return a; 
    } 

    public void setA(AClass a) { 
     this.a = a; 
    } 
} 
@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    // no need to override getA() 
} 
@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    // no need to override getA() 
} 

我沒有找到有關此問題的文檔中任何事情。所以,我只有我的意見:

  • 沒有targetEntity = A.class休眠甚至沒有查詢表ADISCRIMINATOR列時急切地從AB一起提取行,就像它已經做了一個關於實際類型的A決定。
  • 當我添加targetEntity = A.class時,A.DISCRIMINATOR出現在查詢中,而對象是用類A的正確子類創建的。
+0

謝謝你的回答。昨天我有機會測試這個,它在問題的場景中工作:) – Gandalf

3

您在兩個B1使用相同的連接列(A_ID)和B2子類。

使用不同的一個在每個子類:

@Entity 
@DiscriminatorValue("1") 
public class B1 extends B<A1> { 
    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A1_ID") 
    public A1 getA() { ... } 
} 

@Entity 
@DiscriminatorValue("2") 
public class B2 extends B<A2> { 
    @Override 
    @ManyToOne(fetch = EAGER) 
    @JoinColumn(name = "A2_ID") 
    public A2 getA() { ... } 
} 

雖然它可能是有意義的重用列(具有不同列一個無論如何都將是null對於依賴於子類的每個記錄),似乎Hibernate使用列名內部唯一標識同一個表內的一些映射元素。這就是爲什麼它可能會忽略B1中多對一映射的定義,並使用B2中的一個作爲它的原因(因爲B2B1之前在persistence.xml之前定義)。

+0

Thanks Dragan for your answer! :)我還沒有時間去測試它是否有效,因爲其他答案有效並且具有一些優勢:1.代碼少。 2.減少數據庫列3.我們可以在數據庫列上保留非空和外鍵約束。 – Gandalf

相關問題