Hibernate進行了分類整合發現其實Hibernate分為三大部分:核心對象、映射、HQL,這三大部分開發過程中最常使用,前幾篇討論了核心對象及對象之間的轉換方法,接下來討論Hibernate的映射使用方法。
Hibernate一個重要的功能就是映射,它能夠在對象模型和關系模型之間轉換,是面向對象編程思想提倡使用的,使用映射程序開發人員只需要關心對象模型中代碼的編寫。對象和關系數據庫之間的映射通常是由XML文檔來定義的。這個映射文檔被設計為易讀的,并且可以手動修改。這種映射關系我總結為下圖:
映射是通過XML來定義的,使用Hibernate創建的session來管理,最后由session使用JTA把更改提交到數據庫中。session可以理解為持久化管理器,管理持久層中的對象,它是由sessionFactory創建的。使用Hibernate編程時首先要連接數據庫,所以首先要去查看xml中有關數據庫連接的配置,根據文檔的配置創建sessionFactory(可以理解為數據庫鏡像),再由sessionFactory創建一個session,最后由session統一將更改提交到數據庫,這才完成了一部所有的操作。
使用過程
1.創建映射文件,映射文件以.hbm.xml為后綴名,標明它是Hibernate的映射文件;
2.在映射文件中注冊實體類,并將實體類的屬性添加到映射類中,在添加屬性時要指定兩種值分別是id和property,id指明它是一個實體的唯一標示,property指明它是表的字段列;
3.提交修改,同步數據。
Note:開發過xml數據轉到數據庫的開發人員很快就能理解這種映射其實是一種批量更新、批量創建的過程,映射也不例外,Hibernate規定了一套映射標準,按照標準就能夠實現轉換,它的內部實現還是死的,所以它只是相對的靈活易用。
一個簡單的實體類映射過程:
1. 實體類User1的屬性代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.hibernate; import java.util.Date; public class User1 { private String id; private String name; private String password; private Date createTime; private Date expireTime; public String getId() { return id; } public String getName() { return name; } public void setName(String name) { this .name = name; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this .createTime = createTime; } public Date getExpireTime() { return expireTime; } public void setExpireTime(Date expireTime) { this .expireTime = expireTime; } } |
2. User1.java的映射文件User1.hbm.xml的內部代碼實現:
基礎數據庫中能夠設置的在Hibernate中同樣提供了設置的方法,可以使用標簽屬性來設置具體的映射關系。
類-->表使用class標簽,常用屬性:
(1)name:映射實體類,它的值需要設置為需要轉化成表的實體類的名稱,在同步時會根據該屬性查找相應的實體類。
(2)table:映射數據庫表的名稱,如果要映射的表的名稱和實體類名稱不相同,使用該屬性來指定映射的表,如果不存在的話會根據該屬性值創建一個表。
查看上圖中配置生成的表結構,如下圖:
其中的表名更改為了t_user1;id字段更名為user_id,字段長度為32位;createTime屬性,映射為數據庫字段create_time,并修改為date類型。
屬性-->字段使用id或property標簽,常用屬性:
(1)name:功能類似于class標簽的name,值定實體類的映射屬性名;
(2)column:類似于實體類class標簽的table,指定映射表的列名稱,如果不存在將會創建;
(3)type:指定映射到數據庫中字段的數據類型,根據需要查看文檔即可;
(4)generator,它是可選的,用來為一個持久類生成唯一的標識。
1
2
3
4
5
6
|
< id name = "id" type = "long" column = "cat_id" > < generator class = "org.hibernate.id.TableHiLoGenerator" > < param name = "table" >uid_table</ param > < param name = "column" >next_hi_value_column</ param > </ generator > </ id > |
所有的生成器都實現org.hibernate.id.IdentifierGenerator接口。 這是一個非常簡單的接口;某些應用程序可以選擇提供他們自己特定的實現。當然, Hibernate提供了很多內置的實現。下面介紹常用的幾種:
(1)identity:返回的標識符是long, short 或者int類型的。類似于數據庫的自增字段。
(2)sequence:在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的標識符是long, short或者 int類型的。在整個數據庫中自增,而不是單個的表中自增,需要指定單個的表中自增需要添加屬性。
(3)uuid:用一個128-bit的UUID算法生成字符串類型的標識符, 這在一個網絡中是唯一的(使用了IP地址)。UUID被編碼為一個32位16進制數字的字符串。類似于.NET生成的序列號。
(4)native:根據底層數據庫的能力選擇identity, sequence 或者hilo中的一個。靈活的方式,會根據使用的數據庫來確定使用的標識類型,MySQL會選擇identity,Oracle選擇sequence。
(5)assigned:手動為實體類制定一個標識id。這是 <generator>元素沒有指定時的默認生成策略。
(6)foreign:使用另外一個相關聯的對象的標識符。通常和<one-to-one>聯合起來使用。
開發人員往往習慣了手動配置的方法根據文檔的說明來編寫配置屬性,這樣做很原始,初學者建議采用手動配置的方法,有助于思考。另外還有很多第三方工具,通過可視化的方法來配置然后生成xml配置文檔,提高了開發效率,類似的工具如:XDoclet、Middlegen和AndorMDA。
關聯映射之多對一
上面討論了Hibernate的基本映射,一個實體類對應著一張表,在相應的Hibernate Mapping文件中使用<class>標簽映射。并且實體類中的普通屬性對應著表字段,使用<property>標簽映射。另外在構造實體類時應注意:在實體類中應實現無參的默認的構造函數,提供一個標示,建議不要使用final修飾實體類,為實體類生成getter和setter方法,最后介紹了幾種主要的主鍵生成策略,接下來討論多對一映射。
這種多對一關聯映射反應到對象模型中它是一種聚合關系,User是group的一部分,group中存在User,它們兩個的生命周期是不相同的,可反應為下圖:
那么這種多對一關系映射在Hibernate中是如何設置的呢?下面將會介紹兩種方法,使用<many-to-one>標簽直接映射,或者使用<many-to-one>的cascade級聯修改表。
1、Many-to-one直接映射
從字面意思上就能夠理解,它是指多對一的關系,many指的是多的一端,one指的是少的一端,在使用時往往在多的一端的hbm中使用該標簽,并將<many-to-one>的name屬性設置為該映射文件對應的類中的one一端的屬性,如:<many-to-one name="group" column="groupid"></many-to-one>,該標簽添加在了User.hbm.xml中,它對應many;標簽中的name值為group對映射one,并且在User.java中會有一個名為group的屬性。接下來看下具體實現實現的代碼類。
(1)User.java類代碼,其中有一個名為group的屬性,它將會作為<many-to-one>的one一端的name值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class User { private String name; public String GetName(){ return name; } public void SetName(String name){ this .name=name; } private Group group; public Group GetGroup(){ return group; } public void SetGroup(Group group){ this .group=group; } } |
(2)User.hbm.xml中的<many-to-one>,name的值為User.java中one端的屬性值,它會在數據庫中生成一個新列,可以將該新列理解為User表的外鍵。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<? xml version = "1.0" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2014-5-14 23:39:25 by Hibernate Tools 3.4.0.CR1 --> < hibernate-mapping > < class name = "com.hibernate.User" table = "USER" > < id name = "id" type = "java.lang.Long" > < column name = "ID" /> < generator class = "assigned" /> </ id > <!-- name的值group為User.java中的一個對應的one中的一個屬性,它會自動在表中生成一列,所以使用column對列進行了重命名 --> < many-to-one name = "group" column = "groupid" ></ many-to-one > </ class > </ hibernate-mapping > |
(3)測試上面的映射關系,向表中寫入兩個User對象分別為user1和user2,命名為張三和李四,使用session保存對象,向數據庫中寫入數據,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public void testSave1(){ Session session= null ; try { session=GetSession.getSession(); session.beginTransaction(); Group group= new Group(); group.SetName( "動力節點" ); User user1= new User(); user1.SetName( "張三" ); user1.SetGroup(group); User user2= new User(); user2.SetName( "李四" ); user2.SetGroup(group); session.save(user1); session.save(user2); //會報TransientObjectException錯誤 //在清理緩存時發生錯誤TransientObjectException //因為Group為Transient狀態,沒有被Session,在數據庫中沒有匹配的數據 //而User為Persistent狀態,在清理緩存時Hibernate在緩存中無法找到Group對象 //揭露:Persistent狀態的對象不能引用Transient狀態的對象 //該問題在testSave2方法中更改 session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { GetSession.CloseSession(session); } } |
但是使用上面的代碼在執行寫入時會報錯TransientObjectException,這是因為在保存User對象時它會按照<many-to-one>中添加的group去內存中查找group對象,但是上面的代碼中group對象一直都是在Transient狀態中,并沒有被session管理,也就是說查找不到session對象,而User對象進入了Persistent狀態,于是會報此錯誤。正確的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public void testSave2(){ Session session= null ; try { session=GetSession.getSession(); session.beginTransaction(); Group group= new Group(); group.SetName( "動力節點" ); session.save(group); //此處將group對象設置為Persistent對象 User user1= new User(); user1.SetName( "張三" ); user1.SetGroup(group); User user2= new User(); user2.SetName( "李四" ); user2.SetGroup(group); session.save(user1); session.save(user2); //可以正確的保存數據 //因為Group和User都是Persistent狀態的對象 //所以在Hibernate清理緩存時在session中可以找到關聯對象 session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { GetSession.CloseSession(session); } } |
2、級聯映射
除了上面所說的將group對象和user對象都轉化到Persistent對象外,還可以使用cascade級聯映射屬性,在<many-to-one>屬性中添加cascade屬性,并復制為save-update,在group對象并非為Persistent狀態時即可寫入數據庫。這樣只需要將兩個user對象的Group屬性設置為同一個group對象即可實現多對一的映射關系,此時User.hbm.xml中對應的內容為如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<? xml version = "1.0" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2014-5-14 23:39:25 by Hibernate Tools 3.4.0.CR1 --> < hibernate-mapping > < class name = "com.hibernate.User" table = "USER" > < id name = "id" type = "java.lang.Long" > < column name = "ID" /> < generator class = "assigned" /> </ id > <!-- 級聯修改表 --> < many-to-one name = "group" column = "groupid" cascade = "save-update" ></ many-to-one > </ class > </ hibernate-mapping > |
Note:cascade設置為save-update后即可實現向數據庫中級聯修改、添加和刪除,但是具體的級聯查詢操作卻不可以。
對應的測試配置文件的方法為如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
//級聯cascade public void testSave3(){ Session session= null ; try { session=GetSession.getSession(); session.beginTransaction(); Group group= new Group(); group.SetName( "動力節點" ); User user1= new User(); user1.SetName( "張三" ); user1.SetGroup(group); User user2= new User(); user2.SetName( "李四" ); user2.SetGroup(group); session.save(user1); session.save(user2); //沒有拋出TransientObjectException異常 //因為使用了級聯 //Hibernate會首先保存User的關聯對象Group //Group和User就都是Persistent狀態的對象了 session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { GetSession.CloseSession(session); } } |
3、對比升華
兩種方法同樣實現了多對一的映射方法,結果上是相同的,但在實現上很不相同。無論是第一種還是第二種采用的都是<many-to-one>在many一端的映射文件中添加該標簽,并將標簽的name屬性賦值為該映射文件注冊的類中的one一端的屬性值,這樣就完成了多對一的基本映射,這是相同點。不同點是直接映射關系沒有采用Hibernate字段的屬性,這樣在實現上較靈活,不但支持增刪改,而且可以查詢;第二種的cascade級聯修改則采用了Hibernate提供的方法,此種方法只支持增刪改,并不支持查詢。