hibernate 继承映射
在域模型中,類與類之間除了關(guān)聯(lián)關(guān)系和聚集關(guān)系,還可以存在繼承關(guān)系,在下圖所示的域模型中,Deparment類和Employee類之間為一對(duì)多的雙向關(guān)聯(lián)關(guān)系,Employee類有兩個(gè)子類:Skiller類和Sales類。由于Java只允許一個(gè)類最多有一個(gè)直接的父類,因此Employee類、 Skiller類和Sales類構(gòu)成了一棵繼承關(guān)系樹(shù)。
?在面向?qū)ο蟮姆懂犞?#xff0c;還存在多態(tài)的概念,多態(tài)建立在繼承關(guān)系的基礎(chǔ)上。簡(jiǎn)單地理解,多態(tài)是指當(dāng)一個(gè)Java應(yīng)用變量被聲明為Employee類時(shí),這個(gè)變量實(shí)際上既可以引用Employee類自己的實(shí)例,Skiller類的實(shí)例,也可以引用Sales類的實(shí)例。Department類的getEmps()方法通過(guò)HibernateAPI從數(shù)據(jù)庫(kù)中檢索出所有Employee對(duì)象。getEmps()方法返回的集合既可以包含Employee類自己的實(shí)例,Skiller類的實(shí)例,也可以引用Sales類的實(shí)例。,這種查詢被稱為多態(tài)查詢。數(shù)據(jù)庫(kù)表之間并不存在繼承關(guān)系,那么如何把域模型的繼承關(guān)系映射到關(guān)系數(shù)據(jù)模型中呢?hibernate有以下三種映射方式:
繼承關(guān)系樹(shù)的根類對(duì)應(yīng)一個(gè)表:對(duì)關(guān)系數(shù)據(jù)模型進(jìn)行非常規(guī)設(shè)計(jì),在數(shù)據(jù)庫(kù)表中加入額外的區(qū)分子類型的字段。通過(guò)這種方式,可以使關(guān)系數(shù)據(jù)模型支持繼承關(guān)系和多態(tài)。
繼承關(guān)系樹(shù)的每個(gè)類對(duì)應(yīng)一個(gè)表(子類與父類通過(guò)外鍵關(guān)聯(lián)):在關(guān)系數(shù)據(jù)模型中用外鍵參照關(guān)系來(lái)表示繼承關(guān)系。
繼承關(guān)系樹(shù)的每個(gè)具體類對(duì)應(yīng)一個(gè)表:關(guān)系數(shù)據(jù)模型完全不支持域模型中的繼承關(guān)系和多態(tài)。
1.繼承關(guān)系樹(shù)的根類對(duì)應(yīng)一個(gè)表employee(整個(gè)繼承樹(shù)一張表):
employee的表結(jié)構(gòu)如下所示:
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??? |??????????????? |
+------------+--------------+------+-----+---------+----------------+
實(shí)體類Department和Employee請(qǐng)參看我前面的文章,Skiller和Sales分別如下所示:
Java代碼 ??Employee.hbm.xml映射文件如下:
Xml代碼 ??測(cè)試類如下:
Java代碼 ??程序運(yùn)行后,控制臺(tái)打印信息如下所示:
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)
將測(cè)試代碼中注釋為1的語(yǔ)句改成:
Java代碼 ?再運(yùn)行,控制臺(tái)打印的class如下所示(因?yàn)閔ibernate支持多態(tài)查詢): class com.reiyen.hibernate.domain.Skiller
打印的查詢語(yǔ)句還是如上面所示的沒(méi)有改變。
在上面修改的基礎(chǔ)上,再將測(cè)試代碼中注釋為2的語(yǔ)句改成:
Java代碼 ??再運(yùn)行,則控制臺(tái)打印的查詢語(yǔ)句為:
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
優(yōu)點(diǎn):操作效率高
缺點(diǎn):如果說(shuō)給employee增加子類的話,必須修改表結(jié)構(gòu),給表結(jié)構(gòu)增加一個(gè)字段;同時(shí)表中對(duì)應(yīng)子類的字段不能有非空約束.
2.繼承關(guān)系樹(shù)的每子類對(duì)應(yīng)一個(gè)表(joined-subclass),表結(jié)構(gòu)如下所示:
?修改Employee.hbm.xml映射文件如下所示:
?測(cè)試類不變,只是將測(cè)試代碼中注釋為1的語(yǔ)句改成:
Java代碼 ?則控制臺(tái)打印的信息如下所示:
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語(yǔ)句可以看出,此時(shí),如果保存的是Employee對(duì)象的子類的實(shí)例的話,則要在兩張表中保存記錄;如果查詢的是子類對(duì)象的話,是三張表關(guān)聯(lián)在一起進(jìn)行查詢。
?
在上面修改的基礎(chǔ)上,再將測(cè)試代碼中注釋為2的語(yǔ)句改成:
Java代碼 ??再運(yùn)行,則控制臺(tái)打印的查詢語(yǔ)句為:
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=?
此時(shí)只關(guān)聯(lián)兩張表查詢。
數(shù)據(jù)庫(kù)中表記錄如下所示:
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.混合使用,假設(shè)如果Sales的屬性很多,而Skiller的屬性很少,這時(shí)可以混使用“一個(gè)類繼承體系一張表”和“每個(gè)子類一張表”,表結(jié)構(gòu)如下所示:
?Employee.hbm.xml映射文件如下所示:
?此時(shí)測(cè)試類不變,只是將測(cè)試代碼中注釋為1的語(yǔ)句改成:
Java代碼 ?然后在上面的基礎(chǔ)上運(yùn)行原程序,則控制臺(tái)會(huì)打印出如下異常信息:
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'
這是因?yàn)槲以趆ibernate.cfg.xml配置文件中配置了此項(xiàng):
Xml代碼 ??所以在此次程序運(yùn)行時(shí),會(huì)刪除數(shù)據(jù)庫(kù)中的employee表,sales表,而employee表中有skiller表的外鍵關(guān)聯(lián),所以不能刪除employee數(shù)據(jù)表了,所以拋出了上面的異常。此時(shí)你再查看數(shù)據(jù)庫(kù)表,如下所示 :
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)
所以得先手動(dòng)刪除skiller數(shù)據(jù)表,然后再來(lái)運(yùn)行程序:
?
如果Employee.hbm.xml配置文件中
Xml代碼 ?不配置discriminator-value="1",則會(huì)拋出如下異常:
java.lang.ExceptionInInitializerError
Caused by: org.hibernate.MappingException: Could not format discriminator value to SQL string
因?yàn)槿绻鹍iscriminator-value沒(méi)有顯式的給定值的話,則與name屬性的值保持一致,即為Skiller ,所以會(huì)拋出如上異常!
?
4.繼承關(guān)系樹(shù)的每個(gè)具體類對(duì)應(yīng)一個(gè)表(union-subclass)
表結(jié)構(gòu)如下所示:
Employee.hbm.xml映射文件如下:
Xml代碼 ??此時(shí)主鍵增長(zhǎng)不能再是:
Xml代碼 ?因?yàn)槿绻褂胣ative的話三張表會(huì)產(chǎn)生相同的id值,這樣當(dāng)根據(jù)id查詢Employee時(shí)就會(huì)出錯(cuò)了。所以如果你配置成native時(shí)會(huì)拋出如下異常(因?yàn)镋mployee實(shí)體類中id對(duì)應(yīng) 的是int了,所以在此使用hilo(高低位)主鍵生成方式):
org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: com.reiyen.hibernate.domain.Skiller
?
運(yùn)行測(cè)試程序后,此時(shí)控制臺(tái)打印信息如下所示:
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
執(zhí)行查詢時(shí),首先使用子查詢,在子查詢中使用union將三張表的結(jié)果全成一張表,然后再在合成的結(jié)果集中進(jìn)行查詢。
如果Employee是一個(gè)抽象類,你不想在數(shù)據(jù)表中對(duì)應(yīng)相應(yīng)的數(shù)據(jù)表,則可以設(shè)置abstract="true".如下所示:
Xml代碼 ?? 此外,如果繼承關(guān)系中有接口,可以把它當(dāng)作抽象類對(duì)待。
三種映射方式的比較和選擇
為了方便說(shuō)明為三種方式按順序標(biāo)號(hào)為[1]整個(gè)繼承樹(shù)一張表;[2]每子類對(duì)應(yīng)一個(gè)表(joined-subclass);[4]每個(gè)具體類對(duì)應(yīng)一個(gè)表(union-subclass)。
1、復(fù)雜度:
??? [1]簡(jiǎn)單;
??? [2]表較多且之間有外鍵約束;
??? [4]包含重復(fù)字段;
2、查詢性能:
??? [1]效率高;
??? [2]需要表內(nèi)連接或左外連接;
??? [4]若查詢父類需查所有子類表;
3、可維護(hù)性:
??? [1]只需修改一個(gè)表;
??? [2]若某個(gè)類屬性變化只修改這個(gè)類對(duì)應(yīng)的表;
??? [4]若父類屬性變化需要修改所有子類對(duì)應(yīng)的表;
綜上,選擇時(shí),可以參考以下原則:
1、子類屬性不是非常多時(shí),優(yōu)先考慮[1],因?yàn)槠湫阅茏罴选?br /> 2、子類屬性非常多,且對(duì)性能要求不是很嚴(yán)格時(shí),優(yōu)先考慮[2]
總結(jié)
以上是生活随笔為你收集整理的hibernate 继承映射的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hibernate组件(Componen
- 下一篇: hibernate 集合类(Collec