hibernate 继承映射
在域模型中,類與類之間除了關聯關系和聚集關系,還可以存在繼承關系,在下圖所示的域模型中,Deparment類和Employee類之間為一對多的雙向關聯關系,Employee類有兩個子類:Skiller類和Sales類。由于Java只允許一個類最多有一個直接的父類,因此Employee類、 Skiller類和Sales類構成了一棵繼承關系樹。
?在面向對象的范疇中,還存在多態的概念,多態建立在繼承關系的基礎上。簡單地理解,多態是指當一個Java應用變量被聲明為Employee類時,這個變量實際上既可以引用Employee類自己的實例,Skiller類的實例,也可以引用Sales類的實例。Department類的getEmps()方法通過HibernateAPI從數據庫中檢索出所有Employee對象。getEmps()方法返回的集合既可以包含Employee類自己的實例,Skiller類的實例,也可以引用Sales類的實例。,這種查詢被稱為多態查詢。數據庫表之間并不存在繼承關系,那么如何把域模型的繼承關系映射到關系數據模型中呢?hibernate有以下三種映射方式:
繼承關系樹的根類對應一個表:對關系數據模型進行非常規設計,在數據庫表中加入額外的區分子類型的字段。通過這種方式,可以使關系數據模型支持繼承關系和多態。
繼承關系樹的每個類對應一個表(子類與父類通過外鍵關聯):在關系數據模型中用外鍵參照關系來表示繼承關系。
繼承關系樹的每個具體類對應一個表:關系數據模型完全不支持域模型中的繼承關系和多態。
1.繼承關系樹的根類對應一個表employee(整個繼承樹一張表):
employee的表結構如下所示:
mysql> desc employee;
+------------+--------------+------+-----+---------+----------------+
| Field????? | Type???????? | Null | Key | Default | Extra????????? |
+------------+--------------+------+-----+---------+----------------+
| id???????? | int(11)????? | NO?? | PRI | NULL??? | auto_increment |
| type?????? | int(11)????? | NO?? |???? | NULL??? |??????????????? |
| name?????? | varchar(255) | YES? | UNI | NULL??? |??????????????? |
| depart_id? | int(11)????? | YES? | MUL | NULL??? |??????????????? |
| skill????? | varchar(255) | YES? |???? | NULL??? |??????????????? |
| saleAmount | int(11)????? | YES? |???? | NULL??? |??????????????? |
+------------+--------------+------+-----+---------+----------------+
實體類Department和Employee請參看我前面的文章,Skiller和Sales分別如下所示:
Java代碼 ??Employee.hbm.xml映射文件如下:
Xml代碼 ??測試類如下:
Java代碼 ??程序運行后,控制臺打印信息如下所示:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, type) values (?, ?, 0)
Hibernate: insert into Employee (name, depart_id, skill, type) values (?, ?, ?, 1)
Hibernate: insert into Employee (name, depart_id, saleAmount, type) values (?, ?, ?, 2)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_,employee0_.depart_id as depart4_1_0_, employee0_.skill as skill1_0_,employee0_.saleAmount as saleAmount1_0_, employee0_.type as type1_0_from Employee employee0_ where employee0_.id=?
class com.reiyen.hibernate.domain.Employee
employee表中記錄如下所示:
mysql> select * from employee;
+----+------+-----------------+-----------+-------+------------+
| id | type | name??????????? | depart_id | skill | saleAmount |
+----+------+-----------------+-----------+-------+------------+
|? 1 |??? 0 | employee1 name1 |???????? 1 | NULL? |?????? NULL |
|? 2 |??? 1 | employee2 name2 |???????? 1 | j2se? |?????? NULL |
|? 3 |??? 2 | employee3 name3 |???? ? ? 1 | NULL? |?????? 1000 |
+----+------+-----------------+-----------+-------+------------+
3 rows in set (0.00 sec)
將測試代碼中注釋為1的語句改成:
Java代碼 ?再運行,控制臺打印的class如下所示(因為hibernate支持多態查詢): class com.reiyen.hibernate.domain.Skiller
打印的查詢語句還是如上面所示的沒有改變。
在上面修改的基礎上,再將測試代碼中注釋為2的語句改成:
Java代碼 ??再運行,則控制臺打印的查詢語句為:
Hibernate: select skiller0_.id as id1_0_,skiller0_.name as name1_0_, skiller0_.depart_id as depart4_1_0_,skiller0_.skill as skill1_0_ from Employee skiller0_ where skiller0_.id=? and skiller0_.type=1
class com.reiyen.hibernate.domain.Skiller
優點:操作效率高
缺點:如果說給employee增加子類的話,必須修改表結構,給表結構增加一個字段;同時表中對應子類的字段不能有非空約束.
2.繼承關系樹的每子類對應一個表(joined-subclass),表結構如下所示:
?修改Employee.hbm.xml映射文件如下所示:
?測試類不變,只是將測試代碼中注釋為1的語句改成:
Java代碼 ?則控制臺打印的信息如下所示:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into skiller (skill, employee_id) values (?, ?)
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into sales (sale_amount, employee_id) values (?, ?)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.depart_id as depart3_1_0_, employee0_1_.skill as skill2_0_, employee0_2_.sale_amount as sale2_3_0_, case when employee0_1_.employee_id is not null then 1 when employee0_2_.employee_id is not null then 2 when employee0_.id is not null then 0 end as clazz_0_ from Employee employee0_ left outer join skiller employee0_1_ on employee0_.id=employee0_1_.employee_id left outer join sales employee0_2_ on employee0_.id=employee0_2_.employee_id where employee0_.id=?
class com.reiyen.hibernate.domain.Skiller
?從打印的SQL語句可以看出,此時,如果保存的是Employee對象的子類的實例的話,則要在兩張表中保存記錄;如果查詢的是子類對象的話,是三張表關聯在一起進行查詢。
?
在上面修改的基礎上,再將測試代碼中注釋為2的語句改成:
Java代碼 ??再運行,則控制臺打印的查詢語句為:
Hibernate: select skiller0_.employee_id as id1_0_, skiller0_1_.name as name1_0_, skiller0_1_.depart_id as depart3_1_0_, skiller0_.skill as skill2_0_ from skiller skiller0_ inner join Employee skiller0_1_ on skiller0_.employee_id=skiller0_1_.id where skiller0_.employee_id=?
此時只關聯兩張表查詢。
數據庫中表記錄如下所示:
mysql> select * from employee;
+----+-----------------+-----------+
| id | name??????????? | depart_id |
+----+-----------------+-----------+
|? 1 | employee1 name1 |???????? 1 |
|? 2 | employee2 name2 |???????? 1 |
|? 3 | employee3 name3 |???????? 1 |
+----+-----------------+-----------+
3 rows in set (0.00 sec)
mysql> select * from skiller;
+-------------+-------+
| employee_id | skill |
+-------------+-------+
|?????????? 2 | j2se? |
+-------------+-------+
1 row in set (0.00 sec)
mysql> select * from sales;
+-------------+-------------+
| employee_id | sale_amount |
+-------------+-------------+
|?????????? 3 |??????? 1000 |
+-------------+-------------+
1 row in set (0.00 sec)
?
3.混合使用,假設如果Sales的屬性很多,而Skiller的屬性很少,這時可以混使用“一個類繼承體系一張表”和“每個子類一張表”,表結構如下所示:
?Employee.hbm.xml映射文件如下所示:
?此時測試類不變,只是將測試代碼中注釋為1的語句改成:
Java代碼 ?然后在上面的基礎上運行原程序,則控制臺會打印出如下異常信息:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, type) values (?, ?, 0)
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not insert: [com.reiyen.hibernate.domain.Employee]
Caused by: com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Unknown column 'type' in 'field list'
這是因為我在hibernate.cfg.xml配置文件中配置了此項:
Xml代碼 ??所以在此次程序運行時,會刪除數據庫中的employee表,sales表,而employee表中有skiller表的外鍵關聯,所以不能刪除employee數據表了,所以拋出了上面的異常。此時你再查看數據庫表,如下所示 :
mysql> select * from employee;
+----+-----------------+-----------+
| id | name??????????? | depart_id |
+----+-----------------+-----------+
|? 1 | employee1 name1 |???????? 1 |
|? 2 | employee2 name2 |???????? 1 |
|? 3 | employee3 name3 |???????? 1 |
+----+-----------------+-----------+
3 rows in set (0.00 sec)
mysql> select * from skiller;
+-------------+-------+
| employee_id | skill |
+-------------+-------+
|?????????? 2 | j2se? |
+-------------+-------+
1 row in set (0.00 sec)
mysql> select * from sales;
Empty set (0.00 sec)
所以得先手動刪除skiller數據表,然后再來運行程序:
?
如果Employee.hbm.xml配置文件中
Xml代碼 ?不配置discriminator-value="1",則會拋出如下異常:
java.lang.ExceptionInInitializerError
Caused by: org.hibernate.MappingException: Could not format discriminator value to SQL string
因為如果discriminator-value沒有顯式的給定值的話,則與name屬性的值保持一致,即為Skiller ,所以會拋出如上異常!
?
4.繼承關系樹的每個具體類對應一個表(union-subclass)
表結構如下所示:
Employee.hbm.xml映射文件如下:
Xml代碼 ??此時主鍵增長不能再是:
Xml代碼 ?因為如果使用native的話三張表會產生相同的id值,這樣當根據id查詢Employee時就會出錯了。所以如果你配置成native時會拋出如下異常(因為Employee實體類中id對應 的是int了,所以在此使用hilo(高低位)主鍵生成方式):
org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: com.reiyen.hibernate.domain.Skiller
?
運行測試程序后,此時控制臺打印信息如下所示:
Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, id) values (?, ?, ?)
Hibernate: insert into skiller (name, depart_id, skill, id) values (?, ?, ?, ?)
Hibernate: insert into sales (name, depart_id, sale_amount, id) values (?, ?, ?, ?)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.depart_id as depart3_1_0_, employee0_.skill as skill2_0_, employee0_.sale_amount as sale1_3_0_, employee0_.clazz_ as clazz_0_ from ( select id, null as sale_amount, depart_id, null as skill, name, 0 as clazz_ from Employee union select id, null as sale_amount, depart_id, skill, name, 1 as clazz_ from skiller union select id, sale_amount, depart_id, null as skill, name, 2 as clazz_ from sales ) employee0_ where employee0_.id=?
class com.reiyen.hibernate.domain.Skiller
執行查詢時,首先使用子查詢,在子查詢中使用union將三張表的結果全成一張表,然后再在合成的結果集中進行查詢。
如果Employee是一個抽象類,你不想在數據表中對應相應的數據表,則可以設置abstract="true".如下所示:
Xml代碼 ?? 此外,如果繼承關系中有接口,可以把它當作抽象類對待。
三種映射方式的比較和選擇
為了方便說明為三種方式按順序標號為[1]整個繼承樹一張表;[2]每子類對應一個表(joined-subclass);[4]每個具體類對應一個表(union-subclass)。
1、復雜度:
??? [1]簡單;
??? [2]表較多且之間有外鍵約束;
??? [4]包含重復字段;
2、查詢性能:
??? [1]效率高;
??? [2]需要表內連接或左外連接;
??? [4]若查詢父類需查所有子類表;
3、可維護性:
??? [1]只需修改一個表;
??? [2]若某個類屬性變化只修改這個類對應的表;
??? [4]若父類屬性變化需要修改所有子類對應的表;
綜上,選擇時,可以參考以下原則:
1、子類屬性不是非常多時,優先考慮[1],因為其性能最佳。
2、子類屬性非常多,且對性能要求不是很嚴格時,優先考慮[2]
總結
以上是生活随笔為你收集整理的hibernate 继承映射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hibernate组件(Componen
- 下一篇: hibernate 集合类(Collec