2010-01-07 57 views
13

我正在考慮Hibernate管理的類層次結構的表佈局,當然每個子類的表技術在一般意義上都是最合適的。但是,通過邏輯思考,我對它的性能有一些擔憂,特別是隨着子類數量的增加。Hibernate的每個子類的繼承策略的效率

舉一個非常簡短的(和經典)例如,假設您有以下類:

public abstract class Animal { 
    int pkey; 
    String name; 
} 

public class Dog extends Animal { 
    long numSlippersChewed; // int is not large enough... 
} 

public class Cat extends Animal { 
    short miceCaught; // ... but here int is far bigger than required :-) 
} 

(我eliding getter和setter和Hibernate映射等等,僅僅假設他們是基本明顯的情況)。

這些實體的數據庫表是有意義的,你會得到很好的非規範化等等。但是,Hibernate爲了取出個體動物而做了什麼查詢呢?我能想到的至少兩種情況下,這可能會發生:

  1. 有一個對一個(或一個一對多)映射,如Human類的pet領域的一些其他實體。這將存儲pkey,所以當Hibernate獲取一個Human對象時,它也需要獲取相應的Animal對象。當給定動物的pkey時,Hibernate會使用什麼查詢來提取和解組實際動物數據,因爲它可能位於CatDog表中?
  2. HQL如from Animal where name='Rex'(讓我們假設名稱是唯一的)。這與上述類似,因爲它可以讓您在超類表中標識一行,但您不知道要檢查哪個子類表以獲取更多詳細信息。 HQL是否甚至允許您發出查詢from抽象類? (儘管使用子類特定的東西很好,例如from Cat where miceCaught > 5)。

我可以想到兩種方式,這可以在SQL中完成,而且看起來都不漂亮。一種是針對給定的pkey在每個子類表上運行exists查詢,然後從返回命中的表中加載。或者,Hibernate可以在所有表​​中執行一些可怕的聯合查詢 - 實質上是模擬每個層次表的方案,因爲結果集將包含所有可能子類的屬性,並且子類表中的各個選擇返回null作爲無關參數。後一種情況可能甚至需要添加一個合成鑑別器列,以便Hibernate可以知道哪個子類表實際返回了該行,從而知道應該分析哪些Java類。


事情變得多毛太多,如果你有具體的類型的亞型:

public class Greyhound extends Dog { 
    float lifetimeRacingWinnings; 
} 

現在對於一個給定的動物p鍵,有可能是在DogGreyhound表有效行,這意味着我的第一種手動檢查與pkey相對應的類的方法變得更加困難。

我非常擔心的原因是我會希望在類層次結構上使用這種方法,最多有70個類,最大嵌套鏈的級別爲4-5,因此對所有這些進行聯合查詢是可能會有可怕的表現。 Hibernate有沒有什麼小竅門可以保持這種相對高性能?或者正在通過pkey加載對這些類中的一個的引用需要很長時間?

+0

也許你想看到http://stackoverflow.com/questions/2700680/table-per-subclass-inheritance-relationship-how-to-query-against-the-parent-clas – 2010-06-18 01:49:40

+0

出於好奇我不知道是否其他一些基於JPA對象的存儲系統也有同樣的問題(即ObjectDB)。換句話說,也許標準的關係數據庫可能不是最好的技術,特別是因爲你有一個巨大的對象圖。 – 2010-12-25 15:36:38

回答

8

你會發現Hibernate用一系列LEFT JOIN語句爲每個子類寫一個未知動物類型的查詢。所以查詢會隨着子類數量的增加而減慢,並且會試圖返回更廣泛的結果集。所以你是正確的,它不能很好地擴展大類的層次。

有了HQL,你可以直接查詢子類,並訪問它的屬性。然後將會用一個INNER JOIN進行渲染。

我還沒有嘗試過這種多層次的繼承。如果上述內容還沒有讓你失望,建議你嘗試一下,看看 - 你可以打開SQL調試輸出來查看發送到數據庫的內容,或者簡單地配置你的數據庫。

+0

謝謝 - 我考慮過測試自己,但由於某種原因,如果沒有用於性能測試的實際數據,這並不值得,並且我不想以我的擔憂開始這種方式。然而,假設測試用例至少讓我看到所使用的技術,並將其從一個Hibernate性能問題減少到一個SQL性能問題,這應該更容易推理。 – 2010-01-07 14:18:23

+0

參考你的第三段:由於我發佈的SQL顯示,多層次的繼承似乎沒有太大的變化。據我所知,唯一的區別是用於生成合成鑑別符的'case'語句中的子句的順序。所以從性能角度來看,這應該是相同的。 (當然,當具體實例化一個子類時,會有更多的表加入,但這並不關我太多)。 – 2010-01-07 18:16:51

+0

將其標記爲自反思以來就已被接受,它清楚地回答了我所問的問題,即使它沒有提供我所期望的魔力。 – 2010-01-08 15:42:21

3

David M's helpful answer之後我決定拼湊一個骨架測試。

我在三級層次結構中創建了一個抽象超類ADTestA和25個具體子類(我希望你可以猜出它們的名字)。每個類都有一個整數字段,其名稱與其字母相對應 - 例如,類ADTestG除了b字段外,還有一個int字段g,它從其直接父節點ADTestBpkeya字段繼承級抽象超類。

發出HQL查詢from ADTestA where pkey=1導致下面的SQL:

select adtesta0_.pkey as pkey0_, adtesta0_.a as a0_, adtesta0_1_.b as b1_, 
     adtesta0_2_.c as c2_, adtesta0_3_.d as d3_, adtesta0_4_.e as e4_, 
     adtesta0_5_.f as f5_, adtesta0_6_.g as g6_, adtesta0_7_.h as h7_, 
     adtesta0_8_.i as i8_, adtesta0_9_.j as j9_, adtesta0_10_.k as k10_, 
     adtesta0_11_.l as l11_, adtesta0_12_.m as m12_, adtesta0_13_.n as n13_, 
     adtesta0_14_.o as o14_, adtesta0_15_.p as p15_, adtesta0_16_.q as q16_, 
     adtesta0_17_.r as r17_, adtesta0_18_.s as s18_, adtesta0_19_.t as t19_, 
     adtesta0_20_.u as u20_, adtesta0_21_.v as v21_, adtesta0_22_.w as w22_, 
     adtesta0_23_.x as x23_, adtesta0_24_.y as y24_, adtesta0_25_.z as z25_, 
     case 
      when adtesta0_6_.pkey is not null then 6 
      when adtesta0_7_.pkey is not null then 7 
      when adtesta0_8_.pkey is not null then 8 
      when adtesta0_9_.pkey is not null then 9 
      when adtesta0_10_.pkey is not null then 10 
      when adtesta0_11_.pkey is not null then 11 
      when adtesta0_12_.pkey is not null then 12 
      when adtesta0_13_.pkey is not null then 13 
      when adtesta0_14_.pkey is not null then 14 
      when adtesta0_15_.pkey is not null then 15 
      when adtesta0_16_.pkey is not null then 16 
      when adtesta0_17_.pkey is not null then 17 
      when adtesta0_18_.pkey is not null then 18 
      when adtesta0_19_.pkey is not null then 19 
      when adtesta0_20_.pkey is not null then 20 
      when adtesta0_21_.pkey is not null then 21 
      when adtesta0_22_.pkey is not null then 22 
      when adtesta0_23_.pkey is not null then 23 
      when adtesta0_24_.pkey is not null then 24 
      when adtesta0_25_.pkey is not null then 25 
      when adtesta0_1_.pkey is not null then 1 
      when adtesta0_2_.pkey is not null then 2 
      when adtesta0_3_.pkey is not null then 3 
      when adtesta0_4_.pkey is not null then 4 
      when adtesta0_5_.pkey is not null then 5 
      when adtesta0_.pkey is not null then 0 
     end as clazz_ 
from ADTestA adtesta0_ 
      left outer join ADTestB adtesta0_1_ on adtesta0_.pkey=adtesta0_1_.pkey 
      left outer join ADTestC adtesta0_2_ on adtesta0_.pkey=adtesta0_2_.pkey 
      left outer join ADTestD adtesta0_3_ on adtesta0_.pkey=adtesta0_3_.pkey 
      left outer join ADTestE adtesta0_4_ on adtesta0_.pkey=adtesta0_4_.pkey 
      left outer join ADTestF adtesta0_5_ on adtesta0_.pkey=adtesta0_5_.pkey 
      left outer join ADTestG adtesta0_6_ on adtesta0_.pkey=adtesta0_6_.pkey 
      left outer join ADTestH adtesta0_7_ on adtesta0_.pkey=adtesta0_7_.pkey 
      left outer join ADTestI adtesta0_8_ on adtesta0_.pkey=adtesta0_8_.pkey 
      left outer join ADTestJ adtesta0_9_ on adtesta0_.pkey=adtesta0_9_.pkey 
      left outer join ADTestK adtesta0_10_ on adtesta0_.pkey=adtesta0_10_.pkey 
      left outer join ADTestL adtesta0_11_ on adtesta0_.pkey=adtesta0_11_.pkey 
      left outer join ADTestM adtesta0_12_ on adtesta0_.pkey=adtesta0_12_.pkey 
      left outer join ADTestN adtesta0_13_ on adtesta0_.pkey=adtesta0_13_.pkey 
      left outer join ADTestO adtesta0_14_ on adtesta0_.pkey=adtesta0_14_.pkey 
      left outer join ADTestP adtesta0_15_ on adtesta0_.pkey=adtesta0_15_.pkey 
      left outer join ADTestQ adtesta0_16_ on adtesta0_.pkey=adtesta0_16_.pkey 
      left outer join ADTestR adtesta0_17_ on adtesta0_.pkey=adtesta0_17_.pkey 
      left outer join ADTestS adtesta0_18_ on adtesta0_.pkey=adtesta0_18_.pkey 
      left outer join ADTestT adtesta0_19_ on adtesta0_.pkey=adtesta0_19_.pkey 
      left outer join ADTestU adtesta0_20_ on adtesta0_.pkey=adtesta0_20_.pkey 
      left outer join ADTestV adtesta0_21_ on adtesta0_.pkey=adtesta0_21_.pkey 
      left outer join ADTestW adtesta0_22_ on adtesta0_.pkey=adtesta0_22_.pkey 
      left outer join ADTestX adtesta0_23_ on adtesta0_.pkey=adtesta0_23_.pkey 
      left outer join ADTestY adtesta0_24_ on adtesta0_.pkey=adtesta0_24_.pkey 
      left outer join ADTestZ adtesta0_25_ on adtesta0_.pkey=adtesta0_25_.pkey 
where adtesta0_.pkey=1 

這不是很漂亮,而且不符合,我希望能夠避免在每個等級表的有效模擬。

所以看起來這些查詢將會非常昂貴。我會想一想他們需要多長時間一次(比如說,我知道我想要一個ADTestP的實例,並要求其中一個剛剛加入所需的父表中)。不過,我有一種感覺,從其他實體的引用來看,這是不可避免的。換句話說,來自類型爲ADTestA的字段的一對一映射始終會涉及到這種查找。 (另一方面,替代策略也不希望帶來希望的燈塔;按照層次結構的路線,並且在單個表中實際上有數百個列,聽起來也不是很有效......)

+0

+1反饋 - 謝謝。 – 2010-01-07 16:44:33

1

只要您僅通過Hibernate訪問數據庫,並且您沒有重要數據或準備編寫小型遷移腳本,您應該能夠在開發過程中對每個子類/層次結構的表做出決定處理。這是一個ORM的美麗,它抽象的數據庫結構...

另一方面,我是「喜歡構圖而不是繼承」(Prefer composition over inheritance?)的一個粉絲,我很可疑的是一個模型與70 4-5級別的課程不能簡化......但我會讓你自己思考那個問題,畢竟我不知道你試圖解決什麼問題。

+0

不幸的是,這些表格主要是通過Hibernate訪問的,但對於偶爾訪問的其他非Java工具來說,設計至少是必須的。我可能會在這裏混淆關注點的相關性,但是在引入新表的同時,類層次結構正在重構,數據庫性能是主要動力之一。我在某種程度上同意抽象問題,但是Joel寫了抽象漏洞;如果在Hibernate中無法以高性能的方式完成這項工作,我理想的是儘可能早地發現它。 – 2010-01-07 16:41:23

+0

哦 - 我確實考慮寫一份關於合法子類數量的免責聲明,因爲我認爲有人會強調它是一種氣味。 :-)繼續動物比喻,我正在編寫類似於動物園或獸醫的代碼,所以我們合法地有一個'Animal'>'脊椎動物'>'哺乳動物'''食肉動物''''犬''''狗'類型對於許多不同的動物(是的,在層次結構的每個級別都有有用的行爲)。使用這個類結構的Java代碼比它所取代的幾乎平坦的代碼更好,更乾淨。 – 2010-01-07 16:50:18