持久層的解決方案有許多,尤其以持久層框架的出現爲持久層的開發帶來福音。本章以Hibernate和Spring兩個最流行的框架來討論持久層問題,包括Hibernate實體狀態、Hibernate關聯關係、Hibernate連接表、Spring與JDBC、Spring與Hibernate、Spring與IBatis.
6.1 問題:Hibernate中的實體狀態
6.1.1 怎樣理解實體狀態
程序員M在使用Hibernate時總是有些驚奇,原因在於Hinberate中對各個實體的狀態有着不可思議的控制魔力。比如:有時候在更改實體的屬性時,與數據庫中對應的列值也會被更改,而有時候又無法對應更新起來。想要更新的時候無法更新,不想更新的卻更新了。
這就引發了程序員M的問題:在什麼情況下這個實體是持久化一致的,什麼時候又不一致呢?在Hibernate中實體有三個狀態:瞬時、持久化和脫管。要想解決持久化一致性的問題,就必須瞭解各個狀態各代表了什麼,而且在什麼操作後這些狀態會發生變化。
6.1.2 實體狀態的定義
Hibernate中實體有三個狀態:瞬時、持久化和脫管。下面先來看看Hibernate對這三個狀態是怎麼定義的。
(1)瞬時:一個實體通過new操作符創建後,沒有和Hibernate的Session建立關係,也沒有手動賦值過該實體的持久化標識(持久化標識可以認爲映射表的主鍵)。此時該實體中的任何屬性的更新都不會反映到數據庫表中。
(2)持久化:當一個實體和Hibernate的Session創建了關係,並獲取了持久化標識,而且在Hibernate的Session生命週期內存在。此時針對該實體任何屬性的更改都會直接影響到數據庫表中一條記錄對應字段的更新,也即與對應數據庫表保持同步。
(3)脫管:當一個實體和Hibernate的Session創建了關係,並獲取了持久化標識,而此時Hibernate的Session的生命週期結束,實體的持久化標識沒有被改動過。針對該實體的任何屬性的修改都不會及時反映到數據庫表中。
這三種狀態有兩個很重要的點需要掌握:Hibernate的Session和持久化標識。通過這兩個條件就可以判斷出究竟是3種狀態中的哪一個。3種不同狀態通過Hibernate的Session和持久化標識可以互相之間進行轉換,如圖6.1所示。
圖6.1 3個狀態的轉化
舉個簡單的例子,假如Room實體的屬性id表示持久化標識,那麼:
(1)創建的Room實例爲瞬時狀態,將表中對應的主鍵手動設置到id屬性,此時就是脫管狀態。
(2)創建的Room實例爲瞬時狀態,不設置id或設置的id在表中找不到對應,此時調用Hibernate Session的持久化方法,將成爲持久化狀態。
(3)Room實體在持久化狀態下關閉Hibernate Session,此時就是脫管狀態。
(4)Room實體在脫管狀態下調用Hibernate Session的持久化方法,此時就是持久化狀態。
(5)Room實體在持久化狀態下關閉Hibernate Session,隨後清除id屬性的值,此時就是瞬時狀態。
6.1.3 實體狀態的代碼實現:瞬時—持久化
從瞬時狀態到持久化狀態,Hibernate提供瞭如下的實現,見例6.1(以下代碼省略了配置映射文件的部分)。
例6.1:瞬時—持久化的實現
public void run() {
//創建瞬時狀態的UserInf實例
UserInfo userInfo = new UserInfo();
//設置UserInfo屬性,持久化標識id屬性在映射中爲自增長,不用設置
userInfo.setName("RW");
userInfo.setSex("M");
//啓動Session
Session session = HibernateSessionFactory.currentSession();
//啓動事務
Transaction tx = session.beginTransaction();
//瞬時—持久化的實現,保存UserInfo代表的一條記錄到數據庫
①session.save(userInfo);
//打印結果
System.out.println("---Id:" + userInfo.getId());
System.out.println("---Name:" + userInfo.getName());
System.out.println("---Sex:" + userInfo.getSex());
//對持久化的UserInfo進行屬性的更新,此時將同步數據庫
②userInfo.setName("RW2");
userInfo.setSex("F");
//不用調用update方法,持久化狀態的UserInfo會自動同步數據庫
//打印結果
System.out.println("---Id:" + userInfo.getId());
System.out.println("---Name:" + userInfo.getName());
System.out.println("---Sex:" + userInfo.getSex());
//提交事務
tx.commit();
//關閉Hibernate Session
HibernateSessionFactory.closeSession();
}
針對該段代碼將執行如下SQL語句:
Hibernate:
/* ①session.save(userInfo);的動作 */
insert
into
userinfo
(NAME, SEX, roomid, id)
values
(?, ?, ?, ?)
Hibernate:
/* ②userInfo.setName("RW2");
userInfo.setSex("F"); 的動作*/
update
userinfo
set
NAME=?,
SEX=?,
roomid=?
where
id=?
當瞬時狀態轉變爲持久化狀態時,需要自行調用持久化方法(如:session.save())來執行SQL。而在持久化狀態時,Hibernate控制器會自動偵測到改動,執行SQL同步數據庫。
6.1.4 實體狀態的代碼實現:脫管-持久化、持久化-脫管
從脫管狀態和持久化狀態雙重轉變,Hibernate提供瞭如下的實現,見例6.2(以下代碼省略了配置映射文件的部分)。
例6.2:脫管狀態和持久化狀態雙重轉變
public void run() {
//創建UserInfo實例
UserInfo userInfo = new UserInfo();
//啓動Session
Session session = HibernateSessionFactory.currentSession();
//啓動事務
Transaction tx = session.beginTransaction();
//得到持久化UserInfo,此時UserInfo爲持久化狀態
//與數據庫中主鍵爲11117的記錄同步
①session.load(userInfo,new Long(11117));
//提交事務
tx.commit();
//關閉Hibernate Session
HibernateSessionFactory.closeSession();
//關閉Hibernate Session後UserInfo的狀態爲脫管狀態
//此時依然能夠得到數據庫在持久化狀態時的數據
System.out.println("---Id:" + userInfo.getId());
System.out.println("---Name:" + userInfo.getName());
System.out.println("---Sex:" + userInfo.getSex());
//對userInfo實體的屬性的操作將不影響數據庫中主鍵爲11117的記錄
②userInfo.setName("RW3");
userInfo.setSex("M");
//啓動Session
session = HibernateSessionFactory.currentSession();
//啓動事務
tx = session.beginTransaction();
//從脫管狀態到持久化狀態的轉變,此時將更新數據庫中對應主鍵爲11117的記錄
③session.update(userInfo);
//提交事務
tx.commit();
//關閉Hibernate Session
HibernateSessionFactory.closeSession();
}
針對該段代碼將執行如下SQL語句:
Hibernate:
/* ①session.load(userInfo,new Long(11117))的動作 */
select
userinfo0_.id as id0_0_,
userinfo0_.NAME as NAME0_0_,
userinfo0_.SEX as SEX0_0_,
userinfo0_.roomid as roomid0_0_
from
userinfo userinfo0_
where
userinfo0_.id=?
Hibernate:
/* ③session.update(userInfo)的動作 */
update
userinfo
set
NAME=?,
SEX=?,
roomid=?
where
id=?
可以看到②userInfo.setName("RW3")這一部分的代碼沒有直接同步數據庫的表,因爲此時Hibernate Session已經關閉了,此時是脫管狀態。而直到再次打開Hibernate Session並調用③session.update(userInfo),此時由於持久化標識存在於UserInfo實例,因此將從脫管狀態轉變爲持久化狀態,同步數據庫。
6.1.5 持久化方法對狀態的影響
在Hibernate中定義了多個持久化方法,這些方法的調用對實體狀態是有影響的。注意,並不是每一個持久化方法都會將實體狀態變爲持久化狀態。在之前的代碼中,已經使用到的持久化方法爲session.save()、session.load()、session.update()。下面是另外一些持久化方法的調用方式。
(1)session.delete()方法
該方法將已經存在的表記錄刪除,其所影響的狀態是從持久化、脫管狀態轉變爲瞬時狀態,見例6.3。
例6.3:session.delete()方法對狀態的變化
public void run() {
//創建UserInfo實例
UserInfo userInfo = new UserInfo();
//啓動Session
Session session = HibernateSessionFactory.currentSession();
//啓動事務
Transaction tx = session.beginTransaction();
//得到持久化UserInfo,此時UserInfo爲持久化狀態
//與數據庫中主鍵爲11117的記錄同步
①session.load(userInfo,new Long(11117));
//刪除持久化狀態的UserInfo實體,此時UserInfo實體爲瞬時狀態
②session.delete(userInfo);
//提交事務
tx.commit();
//關閉Hibernate Session
HibernateSessionFactory.closeSession();
//由於執行了session.delete因此UserInfo實體爲瞬時狀態,在數據庫中找不到主鍵爲11117的數據
//此時依然能夠顯示該實體的屬性
System.out.println("---Id:" + userInfo.getId());
System.out.println("---Name:" + userInfo.getName());
System.out.println("---Sex:" + userInfo.getSex());
//更新UserInfo實體的持久化標識,使其成爲脫管狀態
③userInfo.setId(11116);
//啓動Session
session = HibernateSessionFactory.currentSession();
//啓動事務
tx = session.beginTransaction();
//調用delete方法將脫管狀態的UserInfo實體轉變爲瞬時狀態
④session.delete(userInfo);
//提交事務
tx.commit();
//關閉Hibernate Session
HibernateSessionFactory.closeSession();
}
針對該段代碼將執行如下SQL語句:
Hibernate:
/* ①session.load(userInfo,new Long(11117))的動作 */
select
userinfo0_.id as id0_0_,
userinfo0_.NAME as NAME0_0_,
userinfo0_.SEX as SEX0_0_,
userinfo0_.roomid as roomid0_0_
from
userinfo userinfo0_
where
userinfo0_.id=?
Hibernate:
/* ②session.delete(userInfo)的動作 */
delete
from
userinfo
where
id=?
Hibernate:
/* ④session.delete(userInfo)的動作 */
delete
from
userinfo
where
id=?
可以看到,兩句delete語句分別對應了持久化狀態的UserInfo和脫管狀態的UserInfo的刪除動作。之後兩種狀態的UserInfo都會成爲瞬時狀態。