1.主鍵生成策略
1.1 主鍵的兩種類型
- 自然主鍵:把數(shù)據(jù)表中的某一業(yè)務(wù)字段作為表的主鍵。如一張用戶表中磨取,把用戶的用戶名作為用戶表的主鍵骂蓖。這樣做的前提條件是鸭蛙,1.用戶的用戶名不能為空,2.用戶的用戶名不能重復(fù),用戶的用戶名不能修改卓练。這樣盡管也是可以的隘蝎,但不能很好的滿足業(yè)務(wù)需求的改變,所以不推薦使用自然主鍵的方式襟企。
- 代理主鍵:單獨為數(shù)據(jù)表設(shè)置一個字段作為數(shù)據(jù)表的主鍵嘱么。作為主鍵的這個字段沒有業(yè)務(wù)含義,一般直接取名為id顽悼,通常為整數(shù)類型曼振,因為整型要比字符型節(jié)省數(shù)據(jù)庫的空間,所以一般都是使用代理主鍵的方式設(shè)置數(shù)據(jù)表的主鍵蔚龙。
注意:在開發(fā)中冰评,建議使用代理主鍵。
1.2 hibernate 中主鍵的生成策略
- assigned 自然主鍵類型
在程序中設(shè)置主鍵木羹。如果在映射表中不設(shè)置generator
屬性甲雅,hibernate 默認(rèn)使用該主鍵生成策略。但不建議使用這種方式坑填,盡量要減少手動對主鍵的操作抛人。 - increment 代理主鍵類型
用于整型類型,由 hibernate 自動以遞增的方式生成脐瑰,每次增量為一妖枚,但只有當(dāng)沒有其他進(jìn)程相同一張表中插入數(shù)據(jù)時,才可以使用苍在,不能在集群環(huán)境下使用绝页。 - identity 代理主鍵類型
由底層數(shù)據(jù)庫設(shè)置主鍵,與 hibernate 無關(guān)忌穿。但前提是使用的數(shù)據(jù)庫要支持自動增長數(shù)據(jù)類型抒寂,如 MySQL 是支持主鍵自動生成的,但 Oracle 就不支持主鍵自動生成掠剑。如果數(shù)據(jù)庫支持主鍵自增屈芜,是可以采用該主鍵生成策略的。 - sequence 代理主鍵類型
由底層數(shù)據(jù)庫根據(jù)序列生成主鍵朴译,與 hibernate 無關(guān)井佑。但前提是數(shù)據(jù)庫要支持序列,Oracle 是支持的眠寿。如果數(shù)據(jù)庫支持序列躬翁,是可以采用該主鍵生成策略的。 - hilo 代理主鍵類型
hibernate 生成主鍵盯拱,hilo 是 high low (高低位方式)的縮寫盒发,是 hibernate 常用的一種生成方式例嘱,需要一張額外的表來保存 hi(高位)的值,并手動設(shè)置 max_lo 的值宁舰,然后通過算法公式(hi * (max_lo + 1) + 0)來生成主鍵拼卵。這種生成策略可以跨數(shù)據(jù)庫,但由hilo算法生成的標(biāo)志只能保證在一個數(shù)據(jù)庫是唯一的蛮艰。 - natve 代理主鍵類型
根據(jù)底層數(shù)據(jù)庫腋腮,自動選擇identity、sequence壤蚜、hilo 策略即寡。但由于生成策略的控制權(quán)在 hibernate 手上,不建議采用袜刷,并且這種生成策略效率比較低聪富。 - uuid 代理主鍵類型
由 hibernate 使用 128 為的UUID算法來生成標(biāo)識符(主鍵),該算法可以在網(wǎng)絡(luò)環(huán)境中生成唯一字符串的標(biāo)識符水泉。長度是一個 32 位的十六進(jìn)制字符串善涨,占用控空間比較大,對應(yīng)數(shù)據(jù)庫的char/varchar類型草则。這種生成策略與數(shù)據(jù)庫無關(guān)钢拧,所以可以跨數(shù)據(jù)庫,方便數(shù)據(jù)庫移植炕横,效率也很高源内,因為不訪問數(shù)據(jù)庫就可以生成主鍵值,并且可以保證唯一性份殿。
2.持久化類
2.1 持久化類的編寫規(guī)則
實體類經(jīng)過 hibernate 操作轉(zhuǎn)換成持久化類膜钓,下面還是使用實體類說明規(guī)則。
- 實體類提供無參的構(gòu)造方法卿嘲。
無參的構(gòu)造方法就算是不寫也可以猾浦,因為 jdk 會幫我們做录淡,但最好加上這個無參的構(gòu)造方法箩兽。 - 實體類的屬性要是私有的薇溃,并使用公開的 set 和 get 方法操作
hibernate 在底層會將查詢到的數(shù)據(jù)進(jìn)行封裝,使用反射生成類的實例梅肤。 - 實體類中要有屬性作為唯一值
hibernate 要通過唯一的標(biāo)識區(qū)分內(nèi)存中是否有一個持久化類司蔬,在 java 中是通過地址區(qū)分是否是同一個對象的,在關(guān)系型數(shù)據(jù)庫的表中是通過主鍵區(qū)分是否有一條記錄的姨蝴,在內(nèi)存中俊啼,hibernate 是不允許出現(xiàn)兩個OID (對象唯一標(biāo)識符)相同的持久化類的。 - 實體類屬性的基本類型建議使用基本數(shù)據(jù)類型的包裝類
包裝類和基本數(shù)據(jù)類型的默認(rèn)值是不同的左医,比如 int 類型的默認(rèn)值是 0授帕,Integer 類型的默認(rèn)值是 null同木。并且包裝類的語義描述比基本數(shù)據(jù)類型更加清晰,比如豪墅,一個學(xué)生的成績泉手,可以是 0 分黔寇,也可以是 100 分偶器,但如果這個學(xué)生沒有成績,用基本的數(shù)據(jù)類型就很難表示了缝裤,但包裝類就可以用 null 來表示屏轰,這樣不會產(chǎn)生歧義。 - 映射的實體類不要使用final關(guān)鍵字修飾
hibernate 有延遲加載機(jī)制憋飞,這個機(jī)制會產(chǎn)生代理對象霎苗,產(chǎn)生代理對象是通過字節(jié)碼的增強(qiáng)技術(shù)來完成的,其實就是產(chǎn)生了當(dāng)前類的子類對象實現(xiàn)的榛做,而是用 final 關(guān)鍵字修飾就無法產(chǎn)生子類唁盏。
2.2 持久化類的三種狀態(tài)
- 瞬時態(tài)(臨時態(tài))(自由態(tài))
瞬時態(tài)是對象只是 new 了出來,在內(nèi)存開辟了空間检眯,但還沒有和 session 關(guān)聯(lián)厘擂,也即是還沒有使用 session 操作內(nèi)存中的對象,這時候在數(shù)據(jù)庫里面是沒有記錄的锰瘸。 - 持久態(tài)
new 出來的實體化類對象經(jīng)過 session 的操作刽严,被加入到 session 的緩存中,并且與這個對象關(guān)聯(lián)的 session 也沒有關(guān)閉避凝,這個時候就是持久態(tài)舞萄,在數(shù)據(jù)庫中存在對應(yīng)的記錄,每條記錄對應(yīng)唯一的持久化對象管削,注意持久化對象是在還未提交事務(wù)錢就已經(jīng)是持久態(tài)了倒脓。 - 托管態(tài)(游離態(tài))(離線態(tài))
某個持久態(tài)的實例在和 session 對象關(guān)聯(lián)后,session 被關(guān)閉時含思,這個對象就變成了托管態(tài)崎弃,這個對象屬性值發(fā)生改變時,hibernate 就無法檢測到茸俭,因為這個實例對象已經(jīng)失去了和 session 的關(guān)聯(lián)吊履。
關(guān)于這三種狀態(tài)的理解,可以結(jié)合下面的 curd 操作和一級緩存來理解调鬓。
3.curd 操作
實體類的代碼
package cc.wenshixin.entity;
public class Notice {
private int id; // 公告序號
private String title; // 公告標(biāo)題
private String content; // 公告內(nèi)容
private String people; // 發(fā)布人
private String date; // 發(fā)布日期
public Notice()
{
}
public Notice(String title, String content, String people, String date) {
super();
this.title = title;
this.content = content;
this.people = people;
this.date = date;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getPeople() {
return people;
}
public void setPeople(String people) {
this.people = people;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
@Override
public String toString() {
return "Notice [id=" + id + ", title=" + title + ", content=" + content + ", people=" + people + ", date=" + date
+ "]";
}
}
hibernate 自定義的工具類艇炎,方便操作 hibernate。
package cc.wenshixin.utility;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtility {
private static Configuration cfg = null;
private static SessionFactory sessionFactory = null;
//靜態(tài)代碼塊
static
{
//加載核心配置文件
cfg = new Configuration().configure();
sessionFactory = cfg.buildSessionFactory();
}
/*提供方法返回sessionFactory*/
public static SessionFactory getSessionFactory()
{
return sessionFactory;
}
/*提供于本地線程綁定的session方法*/
public static Session getSession()
{
return sessionFactory.getCurrentSession();
}
}
下面的操作都是使用JUnit測試工具測試的代碼腾窝。
3.1 增加操作
增加操作讓持久化類從瞬時態(tài)變?yōu)槌志脩B(tài)缀踪。
@Test
public void testSave()
{
//得到sessionFactory對象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得session對象
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
/*執(zhí)行curd操作*/
Notice notice = new Notice("實驗室開放", "同學(xué)們課外可自由選擇實驗", "admin", "2017-10-1");
session.save(notice);
//執(zhí)行事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.2 查詢操作
hibernate 的刪改操作都是基于查詢操作實現(xiàn)的居砖。
@Test
public void testGet()
{
//得到sessionFactory對象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
//執(zhí)行查詢操作
Notice notice = session.get(Notice.class, 2);
//這里要先重寫toString()方法
System.out.println(notice.toString());
//提交事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.3 刪除操作
下面展示了兩種方式來刪除一條記錄,但建議使用第一種驴娃,先查詢后刪除的方式奏候,應(yīng)該避免第二種直接設(shè)置主鍵對應(yīng)屬性值的方式。
@Test
public void testDelete()
{
//得到sessionFactory對象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對象
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
//執(zhí)行刪除操作
//第一種方法
//Notice notice = session.get(Notice.class, 3);
//第二種方法
//Notice notice = new Notice();
//notice.setId(2);
session.delete(notice);
//提交事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.4 修改操作
先得到持久態(tài)的對象唇敞,再對這個對象進(jìn)行操作蔗草。
@Test
public void testUpdate()
{
//得到sessionFactory對象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對象
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
//執(zhí)行更新操作
Notice notice = session.get(Notice.class, 2);
notice.setTitle("我改變了");
session.update(notice);
//執(zhí)行事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.5 增加或更新操作
saveOrUpdate()方法是更具持久化對象的狀態(tài)來做增加或者更新操作的,對象如果是瞬時態(tài)疆柔,那么執(zhí)行事務(wù)就做增加操作咒精,如果對象是托管態(tài),那么執(zhí)行事務(wù)就做更新操作旷档,但此時要注意模叙,更新操作要把持久化類的所有屬性都設(shè)置值,否則沒有設(shè)置屬性值的字段為null鞋屈,下面的代碼就會產(chǎn)生這種情況范咨,所以不推薦使用托管態(tài)修改數(shù)據(jù)表種的記錄。
@Test
public void testSaveOrUpdate()
{
//得到sessionFactory對象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對象
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
//執(zhí)行增加或更新操作
//Notice notice = new Notice("新的公告", "公告內(nèi)容", "admin", "2017-10-9");
Notice notice = new Notice();
notice.setId(4);
notice.setPeople("admin");
session.saveOrUpdate(notice);
//提交事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.6 持久化類狀態(tài)之間的轉(zhuǎn)化
- 瞬時態(tài)轉(zhuǎn)其他狀態(tài)
瞬時態(tài)轉(zhuǎn)持久態(tài):執(zhí)行 session 對象的 save()方法或者 saveOrUpdate()方法
瞬時態(tài)轉(zhuǎn)托管態(tài):為瞬時態(tài)對象設(shè)置持久化標(biāo)識厂庇,也即是調(diào)用 setId()方法
Notice notice = new Notice(); //瞬時態(tài)
notice渠啊。setId(2); //托管態(tài)
- 持久態(tài)轉(zhuǎn)其他狀態(tài)
持久化對象可以通過 session 對象執(zhí)行 get()和 load()方法,或者 Query 查詢(后面會說到)從數(shù)據(jù)庫種獲得宋列。
持久態(tài)轉(zhuǎn)瞬時態(tài):執(zhí)行session的delete()方法
持久態(tài)轉(zhuǎn)托管態(tài):執(zhí)行 session 的close()昭抒、clear() 或者 evict() 方法,evict()方法用于清除一級緩沖中的某一個對象炼杖,close()方法是用來關(guān)閉 session 對象灭返,清除整個一級緩存,clear()方法用于清除一級緩存中的所有對象坤邪。 - 托管態(tài)轉(zhuǎn)氣態(tài)狀態(tài)
托管態(tài)對象是無法直接得到的熙含,是由其他狀態(tài)對象轉(zhuǎn)化而來的,而托管態(tài)和瞬時態(tài)的區(qū)別就是 OID 有沒有值艇纺。
托管態(tài)轉(zhuǎn)持久態(tài):執(zhí)行 session 的 update()怎静、saveOrUpdate()或者lock()方法
托管態(tài)轉(zhuǎn)瞬時態(tài):將托管態(tài)的持久化的 OID標(biāo)識設(shè)置為 null,也即是將作為主鍵的屬性值設(shè)置為 null
注意:由于持久化態(tài)對象的值改變黔衡,其實不用調(diào)用 update()方法或者 saveOrUpdate()方法蚓聘,在執(zhí)行完事務(wù)后就可以自動更新數(shù)據(jù)庫的(在一級緩存中會解釋自動更新),但是還是建議把方法加上盟劫,便于閱讀代碼夜牡。
4.一級緩存
4.1 什么是一級緩存
首先我們要明白什么是緩存,數(shù)據(jù)庫本身其實就是一個文件系統(tǒng)侣签,并且我們知道使用流的方式操作文件效率不高塘装,所以我們把數(shù)據(jù)放到內(nèi)存里面急迂,這樣就可以直接讀取內(nèi)存里面的數(shù)據(jù),提高讀取的效率蹦肴。
hibernate 框架提供了很多的優(yōu)化方式僚碎,一級緩沖就是優(yōu)化方式之一。hibernate 還有二級緩存阴幌,但現(xiàn)在已經(jīng)不適用了勺阐,使用 redis技術(shù)來代替了。
hibernate 的一級緩存就是指 session 緩存裂七,session 緩沖就是一塊內(nèi)存空間皆看,用來存放相互管理的 java 對象,在使用 hibernate 查詢對象時背零,先根據(jù)對象的 OID(唯一標(biāo)識符)去一級緩存中查找,如果找到就直接從一級緩存中取出使用无埃,不用再去數(shù)據(jù)庫查詢了徙瓶,這樣就提高了查詢效率,如果一級緩存中沒有嫉称,就要去數(shù)據(jù)庫中查詢侦镇,然后把查到的數(shù)據(jù)信息放到一級緩存中。hibernate 的一級緩存的作用就是減少對數(shù)據(jù)庫的訪問织阅。
4.2 一級緩存的特點
- 1.hibernate 的一級緩存默認(rèn)時打開的壳繁。
- 2.hibernate 的一級緩存使用范圍就是 session 范圍,是從 session 創(chuàng)建到 session 關(guān)閉荔棉。
- 3.hibernate 的一級緩存闹炉,存儲數(shù)據(jù)必須是持久化數(shù)據(jù)。
4.3 驗證一級緩存的存在
Notice notice1 = session.get(Notice.class, 1);
System.out.println(notice1);
Notice notice2 = session.get(Notice.class, 1);
System.out.println(notice2);
//比較的是對象的指向的地址是否一樣
System.out.println(notice1==notice2);
連續(xù)執(zhí)行查詢操作润樱,觀察控制臺的輸出渣触,發(fā)現(xiàn)只出現(xiàn)了一次查詢的 sql 語句,這就說明第二次的查詢不是在數(shù)據(jù)庫中查詢得到的壹若,而是直接從 hibernate 的一級緩存中取的嗅钻,并且比較兩個對象引用的地址也是true。
4.4 解釋持久化類自動更新
在前面我們說持久化類改變屬性值后店展,不需使用 update()方法就可以自動更新數(shù)據(jù)庫里面的記錄养篓,我們需要指導(dǎo) hibernate 一級緩存的內(nèi)部結(jié)構(gòu)。在執(zhí)行完查詢操作后赂蕴,把查詢到的數(shù)據(jù)放到緩沖區(qū)柳弄,并且復(fù)制一份數(shù)據(jù)到快照區(qū),直接通過 set 方法改變持久化對象的屬性值睡腿,也會改變緩沖區(qū)里面的內(nèi)容语御,在提交事務(wù)時比較緩沖區(qū)和快照區(qū)里面的數(shù)據(jù)是否一致峻贮,如果不一致,就更新數(shù)據(jù)庫中的記錄应闯,并更新快照區(qū)中的數(shù)據(jù)纤控。快照區(qū)的作用就是確保一級緩存中的數(shù)據(jù)和數(shù)據(jù)庫中的數(shù)據(jù)一致碉纺。
5.事務(wù)操作
hibernate 是 jdbc 的輕量級封裝船万,hibernate 的事務(wù)處理就是數(shù)據(jù)庫的事務(wù)處理。
5.1 什么是事務(wù)
在數(shù)據(jù)庫操作上骨田,一項事務(wù)是由一條或多條操作數(shù)據(jù)庫的 sql 語句組成的一個不可分割的工作單元耿导。只有當(dāng)事務(wù)中的所有操作都正常完成,整個事務(wù)才會被提交到數(shù)據(jù)庫中态贤。如果事務(wù)中由一項操作沒有完成舱呻,則整個事務(wù)就會被回滾。事務(wù)簡單理解起來就是悠汽,一組邏輯上的操作箱吕,組成這組操作的各個單元,要么一起成功柿冲,要么一起失敗茬高,具有統(tǒng)一性。
5.2 事務(wù)的四個特性詳解
事務(wù)有很嚴(yán)格的定義假抄,需要同時滿足下面的四個特性怎栽,這四個特性通常稱之為 ACID 特性。
- 原子型(Atomic):表示將事務(wù)中所做的操作捆綁成一個不可分割的單元宿饱,即對事務(wù)所進(jìn)行的數(shù)據(jù)修改等操作熏瞄,要么全部執(zhí)行,要么全都不執(zhí)行刑棵。
- 一致性(Consistency):表示事務(wù)完成時巴刻,必須使所有的數(shù)據(jù)都保持一致狀態(tài)。
- 隔離性(Isolation):指一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾蛉签,即一個事務(wù)內(nèi)部的操作以及使用的數(shù)據(jù)對并發(fā)的其他事務(wù)都是隔離的胡陪,并發(fā)執(zhí)行的各個事務(wù)之間不能互相干擾。
- 持久性(Durability):持久性也稱永久性碍舍,指一個事務(wù)一旦被提交柠座,它對數(shù)據(jù)庫中的數(shù)據(jù)改變就應(yīng)該是永久性的。提交后其他事務(wù)對其他操作或故障不會對它有任何影響片橡。
5.3 事務(wù)的并發(fā)問題
在實際應(yīng)用中妈经,數(shù)據(jù)庫是要被多個用戶共同訪問的,在多個事務(wù)同時使用相同的數(shù)據(jù)時,可能會發(fā)生并發(fā)的問題吹泡。
- 臟讀:一個事務(wù)讀取到了另一個事務(wù)未提交的數(shù)據(jù)骤星。
- 不可重復(fù)度:一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的 update 的數(shù)據(jù),導(dǎo)致在同一個事務(wù)中的查詢結(jié)果不一致爆哑。
- 虛讀/幻讀:一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的 insert 的數(shù)據(jù)洞难,導(dǎo)致在同一個事務(wù)中的多次查詢結(jié)果不一致。
5.4 事務(wù)的隔離級別
為了避免上面所說的事務(wù)并發(fā)問題發(fā)生揭朝,所以在標(biāo)準(zhǔn)的 SQL 規(guī)范中队贱,定義了四個事務(wù)隔離級別,不同的隔離級別對事務(wù)的處理是不同的潭袱。
- 讀未提交(Read Uncommitted柱嫌, 1級):一個事務(wù)在執(zhí)行過程中,即可以訪問其事務(wù)未提交的新插入的數(shù)據(jù)屯换,又可以訪問未提交的修改數(shù)據(jù)编丘。如果一個事務(wù)已經(jīng)開始寫數(shù)據(jù),而另一個事務(wù)則不允許同時進(jìn)行寫操作趟径,但允許其他事務(wù)讀此行數(shù)據(jù)瘪吏,此隔離級別可防止丟失更新。
- 已提交讀(Read Commited蜗巧,2級):一個事務(wù)在執(zhí)行過程中,既可以訪問其他事務(wù)成功提交的新插入的數(shù)據(jù)蕾盯,又可以訪問成功修改的數(shù)據(jù)幕屹。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行级遭。此隔離級別可有效防止臟讀望拖。
- 可重復(fù)讀(Repeated Read,4級):一個事務(wù)在執(zhí)行過程中挫鸽,可以訪問其他事務(wù)成功提交的新插入的數(shù)據(jù)说敏,但不可以訪問成功修改的數(shù)據(jù)。讀取數(shù)據(jù)的事務(wù)將會禁止寫事務(wù)(但允許讀事務(wù))丢郊,寫事務(wù)則禁止任何其他事務(wù)盔沫,此隔離級別可有效的防止不可重復(fù)讀和臟讀。
- 序列化/串行化(Serializable枫匾,8級):提供嚴(yán)格的事務(wù)隔離架诞,它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個接著一個地執(zhí)行干茉,但不能并發(fā)執(zhí)行谴忧。此隔離級別可有效的防止臟讀,不可重復(fù)讀和幻讀。
事務(wù)的隔離級別是由數(shù)據(jù)庫提供的沾谓,但并不是所有數(shù)據(jù)庫都支持四種隔離級別的委造。在使用數(shù)據(jù)庫時,隔離級別越高均驶,安全性越高昏兆,性能越低。在實際的開發(fā)中辣恋,不會選擇最高或者最低的隔離級別亮垫,使用數(shù)據(jù)庫默認(rèn)的即可。
5.5 hibernate 事務(wù)規(guī)范代碼
在 hibernate 中伟骨,可以通過代碼來操作管理事務(wù)饮潦,如通過 Transaction tx = session.beginTransaction();
開啟一個事務(wù),持久化操作后携狭,通過 tx.commit();
提交事務(wù)继蜡,如果事務(wù)出現(xiàn)異常,要通過 tx.rollback();
操作來撤銷事務(wù)(回滾事務(wù))逛腿。
除了在代碼中對事務(wù)開啟稀并,提交和回滾操作外,還可以在 hibernate 的配置文件中對事務(wù)進(jìn)行配置单默。在配置文件中碘举,可以設(shè)置事務(wù)的隔離級別。其具體的配置方法是在 hibernate.cfg.xml 文件中的 property
標(biāo)簽中進(jìn)行的搁廓。配置方法:<property name="hibernate.connection.isolation">4</property>
,并且我們在進(jìn)行正真的事務(wù)管理時引颈,需要考慮到事務(wù)的應(yīng)用場景,事務(wù)的控制不應(yīng)該放在 DAO 層境蜕,而應(yīng)該放在 Service 層調(diào)用多個 DAO 實現(xiàn)一個業(yè)務(wù)邏輯的操作蝙场。其實最主要的是如何保證在 Service 中開啟事務(wù)時使用的 Session 對象和 DAO 中多個操作使用的是同一個 Session 對象。
下面有兩種解決辦法粱年。
- 可以在業(yè)務(wù)層獲取到 Session售滤,并將 Session 作為參數(shù)傳遞給 DAO。
- 可以使用 ThreadLocal 將業(yè)務(wù)層獲取的 Session 綁定到當(dāng)前線程台诗,然后在 DAO 中獲取 Session 時都從當(dāng)前的線程中獲取完箩。
第二種方式時最優(yōu)的方案,而且具體的實現(xiàn)拉庶,hibernate 已經(jīng)在內(nèi)部完成了嗜憔,我們只需要配置一下。hibernate5 種提供了三種管理 Session 對象的方法氏仗。
- Session 對象的生命周期與本地線程綁定
- Session 對象的生命周期與 JTA(Java Transaction API吉捶,Java事務(wù)API夺鲜,是一個Java企業(yè)版的應(yīng)用程序接口)事務(wù)綁定
- hibernate 委托程序管理 Session 對象的生命周期
在 hibernate 的配置文件中,hibernate.current_session_context_class 屬性用于指定 Session 管理方式呐舔,可選值有:1. tread币励,Session 對象的生命周期與本地線程綁定;2. jta珊拼,Session 對象的生命周期與 JTA 事務(wù)綁定食呻;managed,hibernate 委托程序來管理 Session 對象的生命周期澎现。在這里我們選擇 tread 值仅胞,在 hibernate.cfg.xml 中進(jìn)行配置:<property name="hibernate.current_session_context_class">thread</property>
,并且在 hibernate 中提供了 getCurrentSession()方法來創(chuàng)建一個 Session 和本地線程 TreadLocal 綁定的方法剑辫。
/*提供于本地線程綁定的session方法*/
public static Session getSession()
{
return sessionFactory.getCurrentSession();
}
hibernate 提供的這個與本地線程綁定的 Session 可以不用關(guān)閉干旧,當(dāng)線程執(zhí)行結(jié)束后,就會自動關(guān)閉了妹蔽。
下面給出事務(wù)操作的規(guī)范代碼寫法椎眯。
代碼結(jié)構(gòu)如下:
try {
開啟事務(wù)
提交事務(wù)
}catch() {
回滾事務(wù)
}finally {
關(guān)閉
}
@Test
public void testTx1()
{
Session session = null;
Transaction tx = null;
try{
//得到與本地線程綁定的 Session
session = HibernateUtility.getSession();
//開啟事務(wù)
tx = session.beginTransaction();
//添加操作
Notice notice = new Notice("本地線程綁定", "規(guī)范操作", "admin", "2017-10-8");
session.save(notice);
//提交事務(wù)
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滾事務(wù)
tx.rollback();
} finally {
//不需要關(guān)閉session
}
}
//下面的代碼只是上面代碼的對比
@Test
public void testTx2()
{
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
sessionFactory = HibernateUtility.getSessionFactory();
//不是與本地線程綁定的 Session,類似于單例模式胳岂。
session = sessionFactory.openSession();
//開啟事務(wù)
tx = session.beginTransaction();
//添加操作
Notice notice = new Notice("本地線程綁定", "規(guī)范操作", "admin", "2017-10-8");
session.save(notice);
//提交事務(wù)
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滾事務(wù)
tx.rollback();
} finally {
//需要關(guān)閉session
session.close();
sessionFactory.close();
}
}
6.hibernate 查詢相關(guān)API的簡單介紹
在前面编整,我們只進(jìn)行了簡單的 curd 操作,對于查詢操作乳丰,hibernate 還有幾種不同的 API 可以選擇使用掌测,在這里先簡單介紹一下,在后面還會詳細(xì)敘述产园。
6.1 Query 對象
使用 query 對象赏半,不需要寫 sql 語句,但要寫簡單的 hql(hibernate query language淆两,hibernate 的查詢語言) 語句。
hql 和 sql 語句的區(qū)別:
- hql 語句是直接使用實體類和屬性來做查詢
- sql 語句是要操作數(shù)據(jù)表和字段
hql語句的寫法:from 實體類的名稱
拂酣。
Query 對象的使用:
- 創(chuàng)建 query 對象
- 調(diào)用 query 對象里面的方法得到結(jié)果
示例代碼如下:
@Test
//查詢表中所有數(shù)據(jù)
public void testQuery1()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Query<Notice> query = session.createQuery("from Notice");
List<Notice> list = query.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
@Test
//有條件的查詢
public void testQuery2()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Query<Notice> query = session.createQuery("from Notice where title=?");
query.setString(0, "實驗室開放");
List<Notice> list = query.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
6.2 Criteria 對象
使用 criteria 對象秋冰,不需要寫語句,直接調(diào)用方法來實現(xiàn)婶熬。
criteria 對象的使用:
- 創(chuàng)建 criteria 對象
- 調(diào)用對象里面的方法得到結(jié)果
示例代碼如下:
@Test
//查詢表中所有數(shù)據(jù)
public void testCriteria1()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Notice.class);
List<Notice> list = criteria.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
@Test
//有條件的查詢
public void testCriterial2()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Notice.class);
criteria.add(Restrictions.eq("title", "實驗室開放"));
List<Notice> list = criteria.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
6.3 SQLQuery 對象
從名字就可以看出是和 sql 有關(guān)的剑勾,直接寫 sql 語句,底層 hibernate 調(diào)用的是 sql 語句實現(xiàn)的赵颅。
SQLQuery 對象
- 創(chuàng)建 SQLQuery 對象
- 調(diào)用對象的方法得到結(jié)果
示例代碼如下:
@Test
public void testSQLQuery()
{
Session session = HibernateUtility.getSession();
Transaction tx = session.beginTransaction();
SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM notice_content");
List<Object[]> list = sqlQuery.list();
for(Object[] objects : list)
{
System.out.println(Arrays.toString(objects));
}
}