1.主鍵生成策略
1.1 主鍵的兩種類型
- 自然主鍵:把數(shù)據(jù)表中的某一業(yè)務(wù)字段作為表的主鍵。如一張用戶表中,把用戶的用戶名作為用戶表的主鍵。這樣做的前提條件是,1.用戶的用戶名不能為空,2.用戶的用戶名不能重復(fù),用戶的用戶名不能修改。這樣盡管也是可以的,但不能很好的滿足業(yè)務(wù)需求的改變,所以不推薦使用自然主鍵的方式。
- 代理主鍵:?jiǎn)为?dú)為數(shù)據(jù)表設(shè)置一個(gè)字段作為數(shù)據(jù)表的主鍵。作為主鍵的這個(gè)字段沒有業(yè)務(wù)含義,一般直接取名為id,通常為整數(shù)類型,因?yàn)檎鸵茸址凸?jié)省數(shù)據(jù)庫的空間,所以一般都是使用代理主鍵的方式設(shè)置數(shù)據(jù)表的主鍵。
注意:在開發(fā)中,建議使用代理主鍵。
1.2 hibernate 中主鍵的生成策略
- assigned 自然主鍵類型
在程序中設(shè)置主鍵。如果在映射表中不設(shè)置generator
屬性,hibernate 默認(rèn)使用該主鍵生成策略。但不建議使用這種方式,盡量要減少手動(dòng)對(duì)主鍵的操作。 - increment 代理主鍵類型
用于整型類型,由 hibernate 自動(dòng)以遞增的方式生成,每次增量為一,但只有當(dāng)沒有其他進(jìn)程相同一張表中插入數(shù)據(jù)時(shí),才可以使用,不能在集群環(huán)境下使用。 - identity 代理主鍵類型
由底層數(shù)據(jù)庫設(shè)置主鍵,與 hibernate 無關(guān)。但前提是使用的數(shù)據(jù)庫要支持自動(dòng)增長數(shù)據(jù)類型,如 MySQL 是支持主鍵自動(dòng)生成的,但 Oracle 就不支持主鍵自動(dòng)生成。如果數(shù)據(jù)庫支持主鍵自增,是可以采用該主鍵生成策略的。 - sequence 代理主鍵類型
由底層數(shù)據(jù)庫根據(jù)序列生成主鍵,與 hibernate 無關(guān)。但前提是數(shù)據(jù)庫要支持序列,Oracle 是支持的。如果數(shù)據(jù)庫支持序列,是可以采用該主鍵生成策略的。 - hilo 代理主鍵類型
hibernate 生成主鍵,hilo 是 high low (高低位方式)的縮寫,是 hibernate 常用的一種生成方式,需要一張額外的表來保存 hi(高位)的值,并手動(dòng)設(shè)置 max_lo 的值,然后通過算法公式(hi * (max_lo + 1) + 0)來生成主鍵。這種生成策略可以跨數(shù)據(jù)庫,但由hilo算法生成的標(biāo)志只能保證在一個(gè)數(shù)據(jù)庫是唯一的。 - natve 代理主鍵類型
根據(jù)底層數(shù)據(jù)庫,自動(dòng)選擇identity、sequence、hilo 策略。但由于生成策略的控制權(quán)在 hibernate 手上,不建議采用,并且這種生成策略效率比較低。 - uuid 代理主鍵類型
由 hibernate 使用 128 為的UUID算法來生成標(biāo)識(shí)符(主鍵),該算法可以在網(wǎng)絡(luò)環(huán)境中生成唯一字符串的標(biāo)識(shí)符。長度是一個(gè) 32 位的十六進(jìn)制字符串,占用控空間比較大,對(duì)應(yīng)數(shù)據(jù)庫的char/varchar類型。這種生成策略與數(shù)據(jù)庫無關(guān),所以可以跨數(shù)據(jù)庫,方便數(shù)據(jù)庫移植,效率也很高,因?yàn)椴辉L問數(shù)據(jù)庫就可以生成主鍵值,并且可以保證唯一性。
2.持久化類
2.1 持久化類的編寫規(guī)則
實(shí)體類經(jīng)過 hibernate 操作轉(zhuǎn)換成持久化類,下面還是使用實(shí)體類說明規(guī)則。
- 實(shí)體類提供無參的構(gòu)造方法。
無參的構(gòu)造方法就算是不寫也可以,因?yàn)?jdk 會(huì)幫我們做,但最好加上這個(gè)無參的構(gòu)造方法。 - 實(shí)體類的屬性要是私有的,并使用公開的 set 和 get 方法操作
hibernate 在底層會(huì)將查詢到的數(shù)據(jù)進(jìn)行封裝,使用反射生成類的實(shí)例。 - 實(shí)體類中要有屬性作為唯一值
hibernate 要通過唯一的標(biāo)識(shí)區(qū)分內(nèi)存中是否有一個(gè)持久化類,在 java 中是通過地址區(qū)分是否是同一個(gè)對(duì)象的,在關(guān)系型數(shù)據(jù)庫的表中是通過主鍵區(qū)分是否有一條記錄的,在內(nèi)存中,hibernate 是不允許出現(xiàn)兩個(gè)OID (對(duì)象唯一標(biāo)識(shí)符)相同的持久化類的。 - 實(shí)體類屬性的基本類型建議使用基本數(shù)據(jù)類型的包裝類
包裝類和基本數(shù)據(jù)類型的默認(rèn)值是不同的,比如 int 類型的默認(rèn)值是 0,Integer 類型的默認(rèn)值是 null。并且包裝類的語義描述比基本數(shù)據(jù)類型更加清晰,比如,一個(gè)學(xué)生的成績(jī),可以是 0 分,也可以是 100 分,但如果這個(gè)學(xué)生沒有成績(jī),用基本的數(shù)據(jù)類型就很難表示了,但包裝類就可以用 null 來表示,這樣不會(huì)產(chǎn)生歧義。 - 映射的實(shí)體類不要使用final關(guān)鍵字修飾
hibernate 有延遲加載機(jī)制,這個(gè)機(jī)制會(huì)產(chǎn)生代理對(duì)象,產(chǎn)生代理對(duì)象是通過字節(jié)碼的增強(qiáng)技術(shù)來完成的,其實(shí)就是產(chǎn)生了當(dāng)前類的子類對(duì)象實(shí)現(xiàn)的,而是用 final 關(guān)鍵字修飾就無法產(chǎn)生子類。
2.2 持久化類的三種狀態(tài)
- 瞬時(shí)態(tài)(臨時(shí)態(tài))(自由態(tài))
瞬時(shí)態(tài)是對(duì)象只是 new 了出來,在內(nèi)存開辟了空間,但還沒有和 session 關(guān)聯(lián),也即是還沒有使用 session 操作內(nèi)存中的對(duì)象,這時(shí)候在數(shù)據(jù)庫里面是沒有記錄的。 - 持久態(tài)
new 出來的實(shí)體化類對(duì)象經(jīng)過 session 的操作,被加入到 session 的緩存中,并且與這個(gè)對(duì)象關(guān)聯(lián)的 session 也沒有關(guān)閉,這個(gè)時(shí)候就是持久態(tài),在數(shù)據(jù)庫中存在對(duì)應(yīng)的記錄,每條記錄對(duì)應(yīng)唯一的持久化對(duì)象,注意持久化對(duì)象是在還未提交事務(wù)錢就已經(jīng)是持久態(tài)了。 - 托管態(tài)(游離態(tài))(離線態(tài))
某個(gè)持久態(tài)的實(shí)例在和 session 對(duì)象關(guān)聯(lián)后,session 被關(guān)閉時(shí),這個(gè)對(duì)象就變成了托管態(tài),這個(gè)對(duì)象屬性值發(fā)生改變時(shí),hibernate 就無法檢測(cè)到,因?yàn)檫@個(gè)實(shí)例對(duì)象已經(jīng)失去了和 session 的關(guān)聯(lián)。
關(guān)于這三種狀態(tài)的理解,可以結(jié)合下面的 curd 操作和一級(jí)緩存來理解。
3.curd 操作
實(shí)體類的代碼
package cc.wenshixin.entity;
public class Notice {
private int id; // 公告序號(hào)
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測(cè)試工具測(cè)試的代碼。
3.1 增加操作
增加操作讓持久化類從瞬時(shí)態(tài)變?yōu)槌志脩B(tài)。
@Test
public void testSave()
{
//得到sessionFactory對(duì)象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得session對(duì)象
Session session = sessionFactory.openSession();
//開啟事務(wù)
Transaction tx = session.beginTransaction();
/*執(zhí)行curd操作*/
Notice notice = new Notice("實(shí)驗(yàn)室開放", "同學(xué)們課外可自由選擇實(shí)驗(yàn)", "admin", "2017-10-1");
session.save(notice);
//執(zhí)行事務(wù)
tx.commit();
//關(guān)閉session和sessionFactory
session.close();
sessionFactory.close();
}
3.2 查詢操作
hibernate 的刪改操作都是基于查詢操作實(shí)現(xiàn)的。
@Test
public void testGet()
{
//得到sessionFactory對(duì)象
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è)置主鍵對(duì)應(yīng)屬性值的方式。
@Test
public void testDelete()
{
//得到sessionFactory對(duì)象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對(duì)象
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)的對(duì)象,再對(duì)這個(gè)對(duì)象進(jìn)行操作。
@Test
public void testUpdate()
{
//得到sessionFactory對(duì)象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對(duì)象
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()方法是更具持久化對(duì)象的狀態(tài)來做增加或者更新操作的,對(duì)象如果是瞬時(shí)態(tài),那么執(zhí)行事務(wù)就做增加操作,如果對(duì)象是托管態(tài),那么執(zhí)行事務(wù)就做更新操作,但此時(shí)要注意,更新操作要把持久化類的所有屬性都設(shè)置值,否則沒有設(shè)置屬性值的字段為null,下面的代碼就會(huì)產(chǎn)生這種情況,所以不推薦使用托管態(tài)修改數(shù)據(jù)表種的記錄。
@Test
public void testSaveOrUpdate()
{
//得到sessionFactory對(duì)象
SessionFactory sessionFactory = HibernateUtility.getSessionFactory();
//得到session對(duì)象
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)化
- 瞬時(shí)態(tài)轉(zhuǎn)其他狀態(tài)
瞬時(shí)態(tài)轉(zhuǎn)持久態(tài):執(zhí)行 session 對(duì)象的 save()方法或者 saveOrUpdate()方法
瞬時(shí)態(tài)轉(zhuǎn)托管態(tài):為瞬時(shí)態(tài)對(duì)象設(shè)置持久化標(biāo)識(shí),也即是調(diào)用 setId()方法
Notice notice = new Notice(); //瞬時(shí)態(tài)
notice。setId(2); //托管態(tài)
- 持久態(tài)轉(zhuǎn)其他狀態(tài)
持久化對(duì)象可以通過 session 對(duì)象執(zhí)行 get()和 load()方法,或者 Query 查詢(后面會(huì)說到)從數(shù)據(jù)庫種獲得。
持久態(tài)轉(zhuǎn)瞬時(shí)態(tài):執(zhí)行session的delete()方法
持久態(tài)轉(zhuǎn)托管態(tài):執(zhí)行 session 的close()、clear() 或者 evict() 方法,evict()方法用于清除一級(jí)緩沖中的某一個(gè)對(duì)象,close()方法是用來關(guān)閉 session 對(duì)象,清除整個(gè)一級(jí)緩存,clear()方法用于清除一級(jí)緩存中的所有對(duì)象。 - 托管態(tài)轉(zhuǎn)氣態(tài)狀態(tài)
托管態(tài)對(duì)象是無法直接得到的,是由其他狀態(tài)對(duì)象轉(zhuǎn)化而來的,而托管態(tài)和瞬時(shí)態(tài)的區(qū)別就是 OID 有沒有值。
托管態(tài)轉(zhuǎn)持久態(tài):執(zhí)行 session 的 update()、saveOrUpdate()或者lock()方法
托管態(tài)轉(zhuǎn)瞬時(shí)態(tài):將托管態(tài)的持久化的 OID標(biāo)識(shí)設(shè)置為 null,也即是將作為主鍵的屬性值設(shè)置為 null
注意:由于持久化態(tài)對(duì)象的值改變,其實(shí)不用調(diào)用 update()方法或者 saveOrUpdate()方法,在執(zhí)行完事務(wù)后就可以自動(dòng)更新數(shù)據(jù)庫的(在一級(jí)緩存中會(huì)解釋自動(dòng)更新),但是還是建議把方法加上,便于閱讀代碼。
4.一級(jí)緩存
4.1 什么是一級(jí)緩存
首先我們要明白什么是緩存,數(shù)據(jù)庫本身其實(shí)就是一個(gè)文件系統(tǒng),并且我們知道使用流的方式操作文件效率不高,所以我們把數(shù)據(jù)放到內(nèi)存里面,這樣就可以直接讀取內(nèi)存里面的數(shù)據(jù),提高讀取的效率。
hibernate 框架提供了很多的優(yōu)化方式,一級(jí)緩沖就是優(yōu)化方式之一。hibernate 還有二級(jí)緩存,但現(xiàn)在已經(jīng)不適用了,使用 redis技術(shù)來代替了。
hibernate 的一級(jí)緩存就是指 session 緩存,session 緩沖就是一塊內(nèi)存空間,用來存放相互管理的 java 對(duì)象,在使用 hibernate 查詢對(duì)象時(shí),先根據(jù)對(duì)象的 OID(唯一標(biāo)識(shí)符)去一級(jí)緩存中查找,如果找到就直接從一級(jí)緩存中取出使用,不用再去數(shù)據(jù)庫查詢了,這樣就提高了查詢效率,如果一級(jí)緩存中沒有,就要去數(shù)據(jù)庫中查詢,然后把查到的數(shù)據(jù)信息放到一級(jí)緩存中。hibernate 的一級(jí)緩存的作用就是減少對(duì)數(shù)據(jù)庫的訪問。
4.2 一級(jí)緩存的特點(diǎn)
- 1.hibernate 的一級(jí)緩存默認(rèn)時(shí)打開的。
- 2.hibernate 的一級(jí)緩存使用范圍就是 session 范圍,是從 session 創(chuàng)建到 session 關(guān)閉。
- 3.hibernate 的一級(jí)緩存,存儲(chǔ)數(shù)據(jù)必須是持久化數(shù)據(jù)。
4.3 驗(yàn)證一級(jí)緩存的存在
Notice notice1 = session.get(Notice.class, 1);
System.out.println(notice1);
Notice notice2 = session.get(Notice.class, 1);
System.out.println(notice2);
//比較的是對(duì)象的指向的地址是否一樣
System.out.println(notice1==notice2);
連續(xù)執(zhí)行查詢操作,觀察控制臺(tái)的輸出,發(fā)現(xiàn)只出現(xiàn)了一次查詢的 sql 語句,這就說明第二次的查詢不是在數(shù)據(jù)庫中查詢得到的,而是直接從 hibernate 的一級(jí)緩存中取的,并且比較兩個(gè)對(duì)象引用的地址也是true。
4.4 解釋持久化類自動(dòng)更新
在前面我們說持久化類改變屬性值后,不需使用 update()方法就可以自動(dòng)更新數(shù)據(jù)庫里面的記錄,我們需要指導(dǎo) hibernate 一級(jí)緩存的內(nèi)部結(jié)構(gòu)。在執(zhí)行完查詢操作后,把查詢到的數(shù)據(jù)放到緩沖區(qū),并且復(fù)制一份數(shù)據(jù)到快照區(qū),直接通過 set 方法改變持久化對(duì)象的屬性值,也會(huì)改變緩沖區(qū)里面的內(nèi)容,在提交事務(wù)時(shí)比較緩沖區(qū)和快照區(qū)里面的數(shù)據(jù)是否一致,如果不一致,就更新數(shù)據(jù)庫中的記錄,并更新快照區(qū)中的數(shù)據(jù)。快照區(qū)的作用就是確保一級(jí)緩存中的數(shù)據(jù)和數(shù)據(jù)庫中的數(shù)據(jù)一致。
5.事務(wù)操作
hibernate 是 jdbc 的輕量級(jí)封裝,hibernate 的事務(wù)處理就是數(shù)據(jù)庫的事務(wù)處理。
5.1 什么是事務(wù)
在數(shù)據(jù)庫操作上,一項(xiàng)事務(wù)是由一條或多條操作數(shù)據(jù)庫的 sql 語句組成的一個(gè)不可分割的工作單元。只有當(dāng)事務(wù)中的所有操作都正常完成,整個(gè)事務(wù)才會(huì)被提交到數(shù)據(jù)庫中。如果事務(wù)中由一項(xiàng)操作沒有完成,則整個(gè)事務(wù)就會(huì)被回滾。事務(wù)簡(jiǎn)單理解起來就是,一組邏輯上的操作,組成這組操作的各個(gè)單元,要么一起成功,要么一起失敗,具有統(tǒng)一性。
5.2 事務(wù)的四個(gè)特性詳解
事務(wù)有很嚴(yán)格的定義,需要同時(shí)滿足下面的四個(gè)特性,這四個(gè)特性通常稱之為 ACID 特性。
- 原子型(Atomic):表示將事務(wù)中所做的操作捆綁成一個(gè)不可分割的單元,即對(duì)事務(wù)所進(jìn)行的數(shù)據(jù)修改等操作,要么全部執(zhí)行,要么全都不執(zhí)行。
- 一致性(Consistency):表示事務(wù)完成時(shí),必須使所有的數(shù)據(jù)都保持一致狀態(tài)。
- 隔離性(Isolation):指一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾,即一個(gè)事務(wù)內(nèi)部的操作以及使用的數(shù)據(jù)對(duì)并發(fā)的其他事務(wù)都是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾。
- 持久性(Durability):持久性也稱永久性,指一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫中的數(shù)據(jù)改變就應(yīng)該是永久性的。提交后其他事務(wù)對(duì)其他操作或故障不會(huì)對(duì)它有任何影響。
5.3 事務(wù)的并發(fā)問題
在實(shí)際應(yīng)用中,數(shù)據(jù)庫是要被多個(gè)用戶共同訪問的,在多個(gè)事務(wù)同時(shí)使用相同的數(shù)據(jù)時(shí),可能會(huì)發(fā)生并發(fā)的問題。
- 臟讀:一個(gè)事務(wù)讀取到了另一個(gè)事務(wù)未提交的數(shù)據(jù)。
- 不可重復(fù)度:一個(gè)事務(wù)讀到了另一個(gè)事務(wù)已經(jīng)提交的 update 的數(shù)據(jù),導(dǎo)致在同一個(gè)事務(wù)中的查詢結(jié)果不一致。
- 虛讀/幻讀:一個(gè)事務(wù)讀到了另一個(gè)事務(wù)已經(jīng)提交的 insert 的數(shù)據(jù),導(dǎo)致在同一個(gè)事務(wù)中的多次查詢結(jié)果不一致。
5.4 事務(wù)的隔離級(jí)別
為了避免上面所說的事務(wù)并發(fā)問題發(fā)生,所以在標(biāo)準(zhǔn)的 SQL 規(guī)范中,定義了四個(gè)事務(wù)隔離級(jí)別,不同的隔離級(jí)別對(duì)事務(wù)的處理是不同的。
- 讀未提交(Read Uncommitted, 1級(jí)):一個(gè)事務(wù)在執(zhí)行過程中,即可以訪問其事務(wù)未提交的新插入的數(shù)據(jù),又可以訪問未提交的修改數(shù)據(jù)。如果一個(gè)事務(wù)已經(jīng)開始寫數(shù)據(jù),而另一個(gè)事務(wù)則不允許同時(shí)進(jìn)行寫操作,但允許其他事務(wù)讀此行數(shù)據(jù),此隔離級(jí)別可防止丟失更新。
- 已提交讀(Read Commited,2級(jí)):一個(gè)事務(wù)在執(zhí)行過程中,既可以訪問其他事務(wù)成功提交的新插入的數(shù)據(jù),又可以訪問成功修改的數(shù)據(jù)。讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會(huì)禁止其他事務(wù)訪問該行。此隔離級(jí)別可有效防止臟讀。
- 可重復(fù)讀(Repeated Read,4級(jí)):一個(gè)事務(wù)在執(zhí)行過程中,可以訪問其他事務(wù)成功提交的新插入的數(shù)據(jù),但不可以訪問成功修改的數(shù)據(jù)。讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫事務(wù)(但允許讀事務(wù)),寫事務(wù)則禁止任何其他事務(wù),此隔離級(jí)別可有效的防止不可重復(fù)讀和臟讀。
- 序列化/串行化(Serializable,8級(jí)):提供嚴(yán)格的事務(wù)隔離,它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行,但不能并發(fā)執(zhí)行。此隔離級(jí)別可有效的防止臟讀,不可重復(fù)讀和幻讀。
事務(wù)的隔離級(jí)別是由數(shù)據(jù)庫提供的,但并不是所有數(shù)據(jù)庫都支持四種隔離級(jí)別的。在使用數(shù)據(jù)庫時(shí),隔離級(jí)別越高,安全性越高,性能越低。在實(shí)際的開發(fā)中,不會(huì)選擇最高或者最低的隔離級(jí)別,使用數(shù)據(jù)庫默認(rèn)的即可。
5.5 hibernate 事務(wù)規(guī)范代碼
在 hibernate 中,可以通過代碼來操作管理事務(wù),如通過 Transaction tx = session.beginTransaction();
開啟一個(gè)事務(wù),持久化操作后,通過 tx.commit();
提交事務(wù),如果事務(wù)出現(xiàn)異常,要通過 tx.rollback();
操作來撤銷事務(wù)(回滾事務(wù))。
除了在代碼中對(duì)事務(wù)開啟,提交和回滾操作外,還可以在 hibernate 的配置文件中對(duì)事務(wù)進(jìn)行配置。在配置文件中,可以設(shè)置事務(wù)的隔離級(jí)別。其具體的配置方法是在 hibernate.cfg.xml 文件中的 property
標(biāo)簽中進(jìn)行的。配置方法:<property name="hibernate.connection.isolation">4</property>
,并且我們?cè)谶M(jìn)行正真的事務(wù)管理時(shí),需要考慮到事務(wù)的應(yīng)用場(chǎng)景,事務(wù)的控制不應(yīng)該放在 DAO 層,而應(yīng)該放在 Service 層調(diào)用多個(gè) DAO 實(shí)現(xiàn)一個(gè)業(yè)務(wù)邏輯的操作。其實(shí)最主要的是如何保證在 Service 中開啟事務(wù)時(shí)使用的 Session 對(duì)象和 DAO 中多個(gè)操作使用的是同一個(gè) Session 對(duì)象。
下面有兩種解決辦法。
- 可以在業(yè)務(wù)層獲取到 Session,并將 Session 作為參數(shù)傳遞給 DAO。
- 可以使用 ThreadLocal 將業(yè)務(wù)層獲取的 Session 綁定到當(dāng)前線程,然后在 DAO 中獲取 Session 時(shí)都從當(dāng)前的線程中獲取。
第二種方式時(shí)最優(yōu)的方案,而且具體的實(shí)現(xiàn),hibernate 已經(jīng)在內(nèi)部完成了,我們只需要配置一下。hibernate5 種提供了三種管理 Session 對(duì)象的方法。
- Session 對(duì)象的生命周期與本地線程綁定
- Session 對(duì)象的生命周期與 JTA(Java Transaction API,Java事務(wù)API,是一個(gè)Java企業(yè)版的應(yīng)用程序接口)事務(wù)綁定
- hibernate 委托程序管理 Session 對(duì)象的生命周期
在 hibernate 的配置文件中,hibernate.current_session_context_class 屬性用于指定 Session 管理方式,可選值有:1. tread,Session 對(duì)象的生命周期與本地線程綁定;2. jta,Session 對(duì)象的生命周期與 JTA 事務(wù)綁定;managed,hibernate 委托程序來管理 Session 對(duì)象的生命周期。在這里我們選擇 tread 值,在 hibernate.cfg.xml 中進(jìn)行配置:<property name="hibernate.current_session_context_class">thread</property>
,并且在 hibernate 中提供了 getCurrentSession()方法來創(chuàng)建一個(gè) Session 和本地線程 TreadLocal 綁定的方法。
/*提供于本地線程綁定的session方法*/
public static Session getSession()
{
return sessionFactory.getCurrentSession();
}
hibernate 提供的這個(gè)與本地線程綁定的 Session 可以不用關(guān)閉,當(dāng)線程執(zhí)行結(jié)束后,就會(huì)自動(dòng)關(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
}
}
//下面的代碼只是上面代碼的對(duì)比
@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的簡(jiǎn)單介紹
在前面,我們只進(jìn)行了簡(jiǎn)單的 curd 操作,對(duì)于查詢操作,hibernate 還有幾種不同的 API 可以選擇使用,在這里先簡(jiǎn)單介紹一下,在后面還會(huì)詳細(xì)敘述。
6.1 Query 對(duì)象
使用 query 對(duì)象,不需要寫 sql 語句,但要寫簡(jiǎn)單的 hql(hibernate query language,hibernate 的查詢語言) 語句。
hql 和 sql 語句的區(qū)別:
- hql 語句是直接使用實(shí)體類和屬性來做查詢
- sql 語句是要操作數(shù)據(jù)表和字段
hql語句的寫法:from 實(shí)體類的名稱
。
Query 對(duì)象的使用:
- 創(chuàng)建 query 對(duì)象
- 調(diào)用 query 對(duì)象里面的方法得到結(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, "實(shí)驗(yàn)室開放");
List<Notice> list = query.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
6.2 Criteria 對(duì)象
使用 criteria 對(duì)象,不需要寫語句,直接調(diào)用方法來實(shí)現(xiàn)。
criteria 對(duì)象的使用:
- 創(chuàng)建 criteria 對(duì)象
- 調(diào)用對(duì)象里面的方法得到結(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", "實(shí)驗(yàn)室開放"));
List<Notice> list = criteria.list();
for(Notice notice : list)
{
System.out.println(notice);
}
}
6.3 SQLQuery 對(duì)象
從名字就可以看出是和 sql 有關(guān)的,直接寫 sql 語句,底層 hibernate 調(diào)用的是 sql 語句實(shí)現(xiàn)的。
SQLQuery 對(duì)象
- 創(chuàng)建 SQLQuery 對(duì)象
- 調(diào)用對(duì)象的方法得到結(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));
}
}