昨天發佈了 Hibernate 學習筆記第一篇後,今天第二篇來襲~ 此篇筆記是 Hibernate 學習的重點和難點,包括 Hibernate 中的映射關聯關係、Hibernate 的檢索策略與檢索方式(HQL、QBC)、Hibernate 的二級緩存,還包括管理 Session ,如何使 Sess... ...
五、映射一對多關聯關係
1. 單向多對一 即 單向 n-1
1)單向 n-1 關聯只需從 n 的一端可以訪問 1 的一端
① 域模型: 從 Order 到 Customer 的多對一單向關聯需要在Order 類中定義一個 Customer 屬性, 而在 Customer 類中無需定義存放 Order 對象的集合屬性
② 關係數據模型:ORDERS 表中的 CUSTOMER_ID 參照 CUSTOMER 表的主鍵 (多的那一端的數據表需要加上 s,否則建表不成功)
2)使用 many-to-one 映射
3)測試多對一 映射
① save() 保存
② get() 查詢
③ update() 修改 和 刪除
2、雙向一對多 即 雙向1-n
1)雙向 1-n 與 雙向 n-1 是完全相同的兩種情形
2)域模型:從 Order 到 Customer 的多對一雙向關聯需要在Order 類中定義一個 Customer 屬性, 而在 Customer 類中需定義存放 Order 對象的集合屬性
3)需要在 1 的一端即 Customer 中添加 n 的一端(Order)的集合關係,並註意
4)測試雙向多對一映射
① save() 保存操作
> 映射:n(Order)這一端需要進行常規的多對一映射;在 1(Customer)這 一端使用 set 節點映射:
其中:inverse 屬性:
inverse = false 的為主動方,inverse = true 的為被動方, 由主動方負責維護關聯關係,在沒有設置 inverse=true 的情況下,父子兩邊都維護父子關係
> 測試:
② get() 獲取操作
> 測試
③ update() 操作 常規操作
六、映射一對一關聯關係
1. 一對一關聯關係
1)理解:一個部門只有一個部門經理,一個部門經理管理一個部門
2)域模型:Department 中有 Manager 的引用,Manager 中也有 Department 的引用
3) ① 基於外鍵映射的 1 - 1
> 對於基於外鍵的1-1關聯,其外鍵可以存放在任意一邊,在需要存放外鍵一端,增加many-to-one元素。為many-to-one元素增加 unique=“true” 屬性來表示為1-1關聯, 如下:
> 另一端需要使用one-to-one元素,該元素使用 property-ref 屬性指定使用被關聯實體主鍵以外的欄位作為關聯欄位
> 測試:
save() 保存:
get() 獲取:
② 基於主鍵映射的 1 -1
> 指一端的主鍵生成器使用 foreign 策略,表明根據”對方”的主鍵來生成自己的主鍵,自己並不能獨立生成主鍵. <param> 子元素指定使用當前持久化類的哪個屬性作為 “對方”
> 採用foreign主鍵生成器策略的一端增加 one-to-one 元素映射關聯屬性,其one-to-one屬性還應增加 constrained=“true” 屬性;另一端增加one-to-one元素映射關聯屬性
註:constrained(約束):指定為當前持久化類對應的資料庫表的主鍵添加一個外鍵約束,引用被關聯的對象(“對方”)所對應的資料庫表主鍵
> 測試:
save() :
get() 獲取:
七、映射多對多的關聯關係
1.單向多對多:
1) 域模型:Category (類別) 和 Item (商品) 之間的關係模型,只在 Category 類中定義 Set<Item>,即單向 n-n 關聯只需一端使用集合屬性
2) n-n 的關聯必須使用連接表 CATEGORIES_ITEMS
3)與 1-n 映射類似,必須為 set 集合元素添加 key 子元素,指定CATEGORIES_ITEMS 表中參照 CATEGORIES 表的外鍵為 C_ID. 與 1-n 關聯映射不同的是,建立 n-n 關聯時, 集合中的元素使用 many-to-many. many-to-many 子元素的 class 屬性指定 items 集合中存放的是 Item 對象, column 屬性指定 CATEGORIES_ITEMS 表中參照 ITEMS 表的外鍵為 I_ID
4) 單向多對多的測試,get() 方法會連接中間表查詢
2.雙向多對多
1) 域模型:Category (類別) 和 Item (商品) 之間的關係模型,在 Category 類中定義 Set<Item>,在 Item 類中定義 Set<Category>,即
雙向 n-n 關聯需要兩端都使用集合屬性
2)雙向n-n關聯必須使用連接表
3)在雙向 n-n 關聯的兩邊都需指定連接表的表名及外鍵列的列名. 兩個集合元素 set 的 table 元素的值必須指定,而且必須相同。set元素的兩個子元素:key 和 many-to-many 都必須指定 column 屬性,其中,key 和 many-to-many 分別指定本持久化類和關聯類在連接表中的外鍵列名,因此兩邊的 key 與 many-to-many 的column屬性交叉相同
> Category.hbm.xml 中的 Set 節點的配置:
> Item.hbm.xml 中的 Set 節點的配置:
註:對於雙向 n-n 關聯, 必須把其中一端的 inverse 設置為 true, 否則兩端都維護關聯關係可能會造成主鍵衝突.即:
八、映射繼承關係(瞭解,開發中使用很少)
1.使用 subclass 進行映射:
1)採用 subclass 的繼承映射可以實現對於繼承關係中父類和子類使用同一張表
2)因為父類和子類的實例全部保存在同一個表中,因此需要在該表內增加一列,使用該列來區分每行記錄到低是哪個類的實例----這個列被稱為辨別者列(discriminator).
3)在這種映射策略下,使用 subclass 來映射子類,使用 class 和 subclass 的 discriminator-value 屬性指定辨別者列的值
4)所有子類定義的欄位都不能有非空約束。如果為那些欄位添加非空約束,那麼父類的實例在那些列其實並沒有值,這將引起資料庫完整性衝突,導致父類的實例無法保存到資料庫中
5)具體配置:只生成 父類的 .hbm.xml 文件即可,在此配置文件中對子類進行映射
6)測試:
save():
查詢操作:
7)使用 subclass 進行映射的缺點:
> 使用了 辨別者列
> 子類獨有的欄位不能添加非空約束
> 若繼承層次較深,則數據表的欄位也會較多
2.使用 joined-subclass 進行映射
1)採用 joined-subclass 元素的繼承映射可以實現每個子類一張表
2)採用這種映射策略時,父類實例保存在父類表中,子類實例由父類表和子類表共同存儲。因為子類實例也是一個特殊的父類實例,因此必然也包含了父類實例的屬性。於是將子類和父類共有的屬性保存在父類表中,子類增加的屬性,則保存在子類表中。
3)在這種映射策略下,無須使用鑒別者列,但需要為每個子類使用 key 元素映射共有主鍵。
4)子類增加的屬性可以添加非空約束。因為子類的屬性和父類的屬性沒有保存在同一個表中
5)具體映射:只生成 父類的 .hbm.xml 文件即可,在此配置文件中對子類進行映射
6)測試
save() 操作:
query() 查詢操作:
7)優點:
> 不需要使用辨別者列
>子類獨有的欄位能添加非空約束
>沒有冗餘的欄位
3.使用 union-subclass 進行映射
1)採用 union-subclass 元素可以實現將每一個實體對象映射到一個獨立的表中。
2)子類增加的屬性可以有非空約束 --- 即父類實例的數據保存在父表中,而子類實例的數據保存在子類表中。
3)子類實例的數據僅保存在子類表中, 而在父類表中沒有任何記錄
4)在這種映射策略下,子類表的欄位會比父類表的映射欄位要多,因為子類表的欄位等於父類表的欄位、加子類增加屬性的總和
5)在這種映射策略下,既不需要使用鑒別者列,也無須使用 key 元素來映射共有主鍵.
6)使用 union-subclass 映射策略是不可使用 identity 的主鍵生成策略
7)具體映射:只生成 父類的 .hbm.xml 文件即可,在此配置文件中對子類進行映射,如下:
8)測試:
save() : 插入效率還可以,對於子類對象,只需把記錄插入到自己的一張表中
query():查詢父類記錄,需把父表和子表記錄彙總到一起再做查詢,性能稍差
9)優點:
> 不需要使用辨別者列
>子類獨有的欄位能添加非空約束
缺點:
> 存在冗餘的欄位
>若更新父表的欄位,則更新的效率較低
九、Hibernate 的檢索策略
1.類級別的檢索策略,僅適用於 session 的 load() 方法
1)分為 立即檢索(立即載入檢索方法指定的對象) 和 延遲檢索(延遲載入檢索方法指定的對象,在使用具體的非 id 屬性時再進行載入)
2)類級別的檢索策略可以通過 <class> 元素的 lazy 屬性進行設置
3)如果程式載入一個對象的目的是為了訪問它的屬性, 可以採取立即檢索.
4)如果程式載入一個持久化對象的目的是僅僅為了獲得它的引用, 可以採用延遲檢索。註意出現懶載入異常!
5)註:①無論 <class> 元素的 lazy 屬性是 true 還是 false, Session 的 get() 方法及 Query 的 list() 方法在類級別總是使用立即檢索策略
②若 <class> 元素的 lazy 屬性為 true 或取預設值(true), Session 的 load() 方法不會執行查詢數據表的 SELECT 語句, 僅返回代理類對象的實例, 該代理類實例有如下特征:
–由 Hibernate 在運行時採用 CGLIB 工具動態生成
–Hibernate 創建代理類實例時, 僅初始化其 OID 屬性
–在應用程式第一次訪問代理類實例的非 OID 屬性時, Hibernate 會初始化代理類實例
6)測試:
2.一對多和多對多的檢索策略
1)Set 節點的 lazy 屬性
2)Set 節點的 batch-size 屬性
3)Set 節點的 fetch 屬性
3.多對一 和 一對一 關聯檢索策略
1)many-to-one 節點的 lazy 屬性,設置是否延遲檢索,預設為 proxy,為延遲檢索,可取值: proxy false
2)lazy fetch batch-size 屬性的使用如下:
十、Hibernate 的檢索方式(使用 Oracle 資料庫,在資料庫中建立了my_department 和 my_employee 兩張數據表,並導入了數據)
重點學習:
1. HQL(Hibernate Query Language) 檢索方式:使用面向對象的 HQL 查詢語言
2.HQL查詢的 HelloWorld:
3.HQL 的分頁查詢
兩個方法:
1)setFirstResult(int firstResult) :設置開始檢索的位置,參數 firstResult 表示這個對象在查詢結果中的索引位置
2)setMaxResults(int maxResults):設置一次檢索多少個,即一頁顯示多少條記錄
4.HQL 的命名查詢:特點是可以把 hql 語句配置在 .hbm.xml 文件中
1)在 Employee.hbm.xml 文件中定義<query> 節點,用於定義一個 HQL 查詢語句, 它和 <class> 元素併列
5.HQL 的投影查詢:查詢結果僅包含實體的部分屬性. 通過 SELECT 關鍵字實現.
1)註:Query 的 list() 方法返回的集合中包含的是數組類型的元素, 每個對象數組代表查詢結果的一條記錄
2)上面的方式不方便使用,使用下麵的方式可以返回一個對象的集合,方便使用
註:在 hql 語句中使用了 Employee 的構造器,所以在 Employee 類中必須定義一個同樣的構造器
6.HQL 的報表查詢:即在 HQL 語句中可以使用 GROUP BY 或 HAVING 或 min() 或 max() 或 sum() 或 count() 等聚集函數
補:使用 HQL 可以實現刪除操作:
7.HQL 的左外連接 和 迫切左外連接
1)迫切左外連接(儘量使用迫切左外連接)
①LEFT JOIN FETCH 關鍵字表示迫切左外連接檢索策略
②list() 方法返回的集合中存放實體對象的引用, 每個 Department 對象關聯的 Employee 集合都被初始化, 存放所有關聯的 Employee 的實體對象.
2)左外連接
①LEFT JOIN 關鍵字表示左外連接查詢
②list 方法返回的集合中存放的是對象數組類型,如果希望 list 集合中僅包含 Department 對象,可以在 HQL 查詢語句中使用 SELECT 關鍵字
8.QBC(Query By Criteria)查詢和本地 SQL查詢 Criteria:標準 條件
1)QBC 查詢的 HelloWorld:
2)帶有 AND 和 OR 關鍵字的 QBC 查詢
3)QBC 的統計查詢
4)使用 QBC 進行排序和分頁
註:關於 QBC 查詢的更多實例可查看 Hibernate 的示例文檔:
hibernate-release-4.3.11.Final\documentation\manual\en-US\html_single\index.html#querycriteria
5)本地 SQL 查詢
十一、Hibernate 的二級緩存
SessionFactory 級別的緩存
1. 配置 Hibernate類級別的二級緩存:
> 加入 EHCache 的三個 jar 包 hibernate-release-4.2.4.Final\lib\optional\ehcache 目錄下
> 加入 EHCache 的配置文件 hibernate-release-4.2.4.Final\project\etc 目錄下,放到 src 目錄下
> 在 Hibernate.cfg.xml 中 啟用二級緩存 配置使用二級緩存的產品 設置對哪一個類使用二級緩存
> 註:還可以在 對應類的 .hbm.xml 文件中使用 cache 節點配置使用緩存的策略,而省略在 Hibernate.cfg.xml 中設置對哪一個類使用二級緩存
2.配置 Hibernate 集合級別的二級緩存:
> 配置對集合使用二級緩存:註:還需要對集合中元素對應的持久化類也使用二級緩存,否則會多出 n 條 SQL 語句
註:也可以在 .hbm.xml 中配置,註意集合的二級緩存在 Set 節點中配置
3.詳解 ehcache.xml 配置文件:
4.查詢緩存:預設情況下, 設置的緩存對 HQL 及 QBC 查詢時無效的, 但可以通過以下方式使其是有效的
註:查詢緩存依賴於二級緩存
> 在 hibernate 配置文件中聲明開啟查詢緩存
> 調用 Query 或 Criteria 的 setCacheable(true) 方法
5.管理 Session ,使 Session 的生命周期和本地線程綁定
步驟(用於測試):
1)在 Hibernate.cfg.xml 文件中配置管理 Session 的方式
2)構建一個 HibernateUtils 類,用於獲取與當前線程綁定的 Session
public class HibernateUtils { //單例 private HibernateUtils() {}
private static HibernateUtils instance = new HibernateUtils();
public static HibernateUtils getInstance() { return instance; }
private SessionFactory sessionFactory;
//獲取 Session 對象的方法 public Session getSession() { return getSessionFactory().getCurrentSession(); }
//獲取 SessionFactory 對象的方法 public SessionFactory getSessionFactory() { if(sessionFactory == null) { Configuration configuration = new Configuration().configure(); ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } return sessionFactory; }
}
3)構建一個 DepartmentDao 類
public class DepartmentDao { public void save(Department dept) { /** * 內部獲取 Session 對象,獲取和當前線程綁定的 Session 對象 * 優點: 1.不需要從外部傳入 Session對象 * 2. 多個 DAO 方法可以使用一個事務 */ Session session = HibernateUtils.getInstance().getSession(); System.out.println(session.hashCode()); session.save(dept); } /** * 若需要傳入一個 Session 對象, 則意味著上一層(Service)需要獲取到 Session 對象. * 這說明上一層需要和 Hibernate 的 API 緊密耦合. 所以不推薦使用此種方式. */ public void save(Session session, Department dept) { session.save(dept); } }
4)測試:
6、建議使用原生的 JDBC 的API 進行批量操作