Hibernate事務以及一級緩存02
1.1 什麽是事務(Transaction)(面試重點)
是並發控制的單元,是用戶定義的一個操作序列。這些操作要麽都做,要麽都不做,是一個不可分割的工作單位。通過事務,sql 能將邏輯相關的一組操作綁定在一起,以便服務器 保持數據的完整性。事務通常是以begin transaction開始,以commit或rollback結束。Commint表示提交,即提交事務的所有操作。具體地說就是將事務中所有對數據的更新寫回到磁盤上的物理數據庫中去,事務正常結束。Rollback表示回滾,即在事務運行的過程中發生了某種故障,事務不能繼續進行,系統將事務中對數據庫的所有已完成的操作全部撤消,滾回到事務開始的狀態。
設想網上購物的一次交易,其付款過程至少包括以下幾步數據庫操作:
1)更新客戶所購商品的庫存信息
2)保存客戶付款信息--可能包括與銀行系統的交互
3)生成訂單並且保存到數據庫中
4)更新用戶相關信息,例如購物數量等等
正常的情況下,這些操作將順利進行,最終交易成功,與交易相關的所有數據庫信息也成功地更新。但是,如果在這一系列過程中任何一個環節出了差錯,例如在更新商品庫存信息時發生異常、該顧客銀行帳戶存款不足等,都將導致交易失敗。一旦交易失敗,數據庫中所有信息都必須保持交易前的狀態不變,比如最後一步更新用戶信息時失敗而導致交易失敗,那麽必須保證這筆失敗的交易不影響數據庫的狀態--庫存信息沒有被更新、用戶也沒有付款,訂單也沒有生成。否則,數據庫的信息將會一片混亂而不可預測。
數據庫事務正是用來保證這種情況下交易的平穩性和可預測性的技術
1.2 為什麽要使用事務?
-
為了提高性能
-
為了保持業務流程的完整性
-
使用分布式事務
1.3 事務的特性
ACID
-
原子性(atomicity)
事務是數據庫的邏輯工作單位,而且是必須是原子工作單位,對於其數據修改,要麽全部執行,要麽全部不執行。
-
一致性(consistency)
事務在完成時,必須是所有的數據都保持一致狀態。在相關數據庫中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。
-
隔離性(isolation)
一個事務的執行不能被其他事務所影響。企業級的數據庫每一秒鐘都可能應付成千上萬的並發訪問,因而帶來了並發控制的問題。由數據庫理論可知,由於並發訪問,在不可預料的時刻可能引發如下幾個可以預料的問題:
-
持久性(durability)
一個事務一旦提交,事物的操作便永久性的保存在DB中。即使此時再執行回滾操作也不能撤消所做的更改
1.4 事務的並發問題
-
臟讀(Dirty Read)
一個事務讀取到了另一個事務未提交的數據操作結果。這是相當危險的,因為很可能所有的操作都被回滾。
-
不可重復讀(虛讀)(NonRepeatable Read)
一個事務對同一行數據重復讀取兩次,但是卻得到了不同的結果。例如事務T1讀取某一數據後,事務T2對其做了修改,當事務T1再次讀該數據時得到與前一次不同的值。
-
幻讀(Phantom Read)
事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據或者缺少了第一次查詢中出現的數據,這是因為在兩次查詢過程中有另外一個事務插入數據造成的
1.5 事務的隔離級別
-
1- 讀未提交
Read uncommitted:最低級別,以上情況均無法保證。
-
2- 讀已提交
Read committed:可避免臟讀情況發生。(Oracle默認)
-
4- 可重復讀
Repeatable read:可避免臟讀、不可重復讀情況的發生。不可以避免虛讀。(MySQl默認)
-
8- 串行化讀
Serializable:事務只能一個一個執行,避免了臟讀、不可重復讀、幻讀。執行效率慢,使用時慎重.
在Hibernate.cfg.xml中進行配置
<!-- 修復 hibernate 的隔離級別 --> <property name="hibernate.connection.isolation">4</property>
可以配置四個值:
1: read uncommited
2: read commited
4: repeatable read
8: serializeable
3. 使用ThreadLocal管理Session(重點,記得會使用getCurrentSession)
3.1 事務管理案例
註意:下面的測試無法保存數據,因為使用 openSession()方法拿到的都是獨立的session對象,事物中提交的session並不是dao中操作的session.
JavaDao代碼:
1 public class CustomerDao { 2 public void save(Customer cust){ 3 Session session = HibernateUtil.openSession();//每次都拿到新的session 4 session.save(cust); 5 //不能關閉session 6 //session.close(); 7 } 8 }View Code
JavaService代碼:
1 public class CustomerService { 2 private CustomerDao dao = new CustomerDao();; 3 4 public void save(Customer c1,Customer c2){ 5 Session session = HibernateUtil.openSession(); 6 //開啟事務 7 Transaction tx = session.beginTransaction(); 8 try { 9 dao.save(c1); 10 dao.save(c2); 11 tx.commit(); 12 } catch (Exception e) { 13 e.printStackTrace(); 14 tx.rollback(); 15 } 16 } 17 }View Code
測試:
1 /** 2 * 事務測試 3 */ 4 @Test 5 public void test2(){ 6 Customer c1 = new Customer(); 7 c1.setName("張三"); 8 9 Customer c2 = new Customer(); 10 c2.setName("李四"); 11 12 CustomerService service = new CustomerService(); 13 service.save(c1, c2); 14 }View Code
3.2 解決方案
3.2.1 修改session的獲取方式
將dao層和service層中需要用到session的地方使用getCurrentSession()
Session session = HibernateUtil.getCurrentSession();
3.2.2 在hibernate.cfg.xml中配置
<!-- 讓session被TheadLocal管理 --> <property name="current_session_context_class">thread</property>
1.使用getCurrentSession時,增刪改查操作都需要事務支持
2.getCurrentSession創建的session會和綁定到當前線程,而openSession不會。
3.getCurrentSession創建的Session會在事務回滾或事物提交後自動關閉,而openSession必須手動關閉
1. 什麽是更新數據丟失?
例如:兩個事務同時對某一條記錄做修改,就會引發丟失更新的問題。
A事務和B事務同時獲取到一條數據,同時再做修改
如果A事務修改完成後,提交了事務
B事務修改完成後,不管是提交還是回滾,如果不做處理,都會對數據產生影響
兩個同時更新! 第一次更新別第二次更新的覆蓋了!!
2. 更新數據丟失解決方案
-
悲觀鎖:
采用的是數據庫提供的一種鎖機制,如果采用做了這種機制,在SQL語句的後面添加 for update 子句
當A事務在操作該條記錄時,會把該條記錄鎖起來,其他事務是不能操作這條記錄的。
只有當A事務提交後,鎖釋放了,其他事務才能操作該條記錄
實現代碼:
session.get(Customer.class, 1,LockMode.UPGRADE); //操作數第三個參數添加鎖
-
樂觀鎖:
采用版本號的機制來解決的。會給表結構添加一個字段version=0,默認值是0
當A事務在操作完該條記錄,提交事務時,會先檢查版本號,如果發生版本號的值相同時,才可以提交事務。同時會更新版本號version=1
? 當B事務操作完該條記錄時,提交事務時,會先檢查版本號,如果發現版本不同時,程序會出現錯誤。
1.在對應的JavaBean中添加一個屬性,名稱可以是任意的。 例如:private Integer version; 提供get和set方法 2.在映射的配置文件中,提供<version name="version"/>標簽即可。 對比version 如果版本不是最新的 !那麽操作不成功! <!-- 就是實體實體類中version --> <version name="version"></version>
持久化類:是指其實例需要被 Hibernate 持久化到數據庫中的類。持久化類符合JavaBean的規範,包含一些屬性,以及與之對應的 getXXX()
和 setXXX()
方法。
2. 持久化類編寫規則
-
get/set方法必須符合特定的命名規則,get 和set 後面緊跟屬性的名字,並且屬性名的首字母為大寫。
-
name
屬性的 get 方法為getName()
,如果寫成 getname() 或 getNAME() 會導致 Hibernate 運行時拋出以下異常:net.sf.hibernate.PropertyNotFoundException:Could not find a getter for porperty name in class mypack XXX -
如果屬性為
boolean
類型,那麽 get 方法名即可以用get
作為前綴,也可以用is
作為前綴。 -
持久化類必須有一個主鍵屬性,用來唯一標識類的每一個對象。這個主鍵屬性被稱為對象標示符(OID,Object Identifier)。
-
Hibernate要求持久化類必須提供一個不帶參的默認構造方法,在程序運行時,Hibernate 運用Java反射機制,調用java.Lang.raflect.Constructor.newInstance()方法來構造持久化類的實例。
-
使用非final類。在運行時生成代理是 Hibernate 的一個重要功能。如果持久化類沒有實現任何接口的話,Hibernate使用CGLIB生成代理,該代理對象時持久化類子類的實例。如果使用了final類,將無法生成CGLIB代理。還有一個可選的策略,讓 Hibernate 持久化類實現一個所有方法都聲明為public的接口,此時將使用JDK的動態代理。同時應該避免在非final類中聲明public final的方法。如果非要使用一個有public final的類,你必須通過設置lazy=”false“來明確地禁用代理
3. 自然和代理主鍵
持久化類中必須包含一個主鍵屬性,主鍵通常分為兩種,自然和代理!
-
自然主鍵:對象本身的一個屬性.創建一個人員表,每個人都有一個身份證號.(唯一的)使用身份證號作為表的主鍵.自然主鍵.(開發中不會使用這種方式)
-
代理主鍵:不是對象本身的一個屬性.創建一個人員表,為每個人員單獨創建一個字段.用這個字段作為主鍵.代理主鍵.(開發中推薦使用這種方式)
4. 主鍵生成策略(重點)
hibernate框架可以有效的幫助我們生成數據主鍵,可以是自增長,也可以是UUID等模式!
修改生成策略位置:
<!-- 配置主鍵id name javaBean的屬性 column 表結構的屬性 如果相同可以去掉 column --> <!-- 主鍵生成策略,修改class值即代表修改主鍵生成策略 --> <id name="cust_id" column="cust_id"> <generator class="native"/> </id>
具體策略值:
-
Hibernate中提供的一種增長機制.
先進行查詢 :select max(id) from user;
再進行插入 :獲得最大值+1作為新的記錄的主鍵.
問題:不能在集群環境下或者有並發訪問的情況下使用.
-
identity:適用於short,int,long作為主鍵。但是這個必須使用在有自動增長數據庫中.采用的是數據庫底層的自動增長機制.底層使用的是數據庫的自動增長(auto_increment).像Oracle數據庫沒有自動增長.
所以此值mysql支持!
-
sequence:適用於short,int,long作為主鍵.底層使用的是序列的增長方式.Oracle數據庫底層沒有自動增長,想自動增長需要使用序列.
此值Oracle支持!
-
uuid:適用於char,varchar類型的作為主鍵.
使用隨機的字符串作為主鍵.
-
native:本地策略.根據底層的數據庫不同,自動選擇適用於該種數據庫的生成策略.(short,int,long)
如果底層使用的MySQL數據庫:相當於identity.
如果底層使用Oracle數據庫:相當於sequence.
-
assigned:主鍵的生成不用Hibernate管理了.必須手動設置主鍵.
-
持久化對象的幾種狀態
-
持久化對象狀態轉換
1.1 創建項目
1.2 導入jar包
1.3 復制上個項目實體(客戶),映射,配置和工具類等!
額外添加一個用戶表,和實體類!
-
建表語句
1 CREATE TABLE `user`( 2 id integer primary key auto_increment, 3 name varchar(10) not null, 4 age integer, 5 version integer 6 )View Code
- 創建實體類
1 public class User { 2 private Integer id; 3 private String name; 4 private Integer age; 5 private Integer version; 6 //getter setter toString 7 }View Code
- 位置: 實體類相同文件夾 命名:User.hbm.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC 3 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 5 <hibernate-mapping> 6 <!-- 配置類和表的映射 catalog="" 數據庫名稱--> 7 <class name="com.itqf.bean.User" table="user" > 8 9 <!-- 配置主鍵id 10 name javaBean的屬性 11 column 表結構的屬性 12 如果相同可以去掉 column 13 --> 14 <id name="id" column="id"> 15 <!-- 主鍵生成策略 遞增 --> 16 <generator class="native"/> 17 </id> 18 <!-- 就是實體實體類中version --> 19 <version name="version"></version> 20 21 <!-- 其他的屬性 --> 22 <property name="name" column="name" length="30"/> 23 <property name="age" column="age"/> 24 </class> 25 </hibernate-mapping>View Code
- 修改核心配置文件,添加User的映射文件
1 <!-- 映射的 com開始--> 2 <mapping resource="/bean/Customer.hbm.xml"/> 3 <mapping resource="/bean/User.hbm.xml"/>View Code
2. 持久化對象介紹
3. 持久化對象的三種狀態(重點)
Hibernate為了管理持久化對象:將持久化對象分成了三個狀態
-
瞬時態:Transient Object
沒有持久化標識OID, 沒有被納入到Session對象的管理.
-
持久態:Persistent Object
有持久化標識OID,已經被納入到Session對象的管理.
-
脫管態(遊離態):Detached Object
有持久化標識OID,沒有被納入到Session對象的管理.
持久化對象中,持久態最為重要,因為持久太對象具有自動更新功能!
展示持久化對象狀態:
1 @Test 2 public void testStatus(){ 3 4 Session session = HibernateUtil.getSession(); 5 6 Transaction beginTransaction = session.beginTransaction(); 7 //持久化對象 8 User user = new User(); 9 user.setName("王老五"); 10 user.setAge(36); 11 12 //----------- 以上是瞬時態 沒有session管理沒有 oid------------------ 13 //返回值就是生成的id 14 Serializable id = session.save(user); 15 System.out.println(id); 16 beginTransaction.commit(); 17 18 //------------ 以上是持久態,有session管理,有oid----------- 19 session.close(); 20 21 22 System.out.println(user.getId()); 23 System.out.println(user.getName()); 24 //------------- 以上托管態, 有oid 但是沒有session管理!----------- 25 }View Code
測試自動更新功能:
1 @Test 2 public void testAuto(){ 3 4 Session session = HibernateUtil.getSession(); 5 6 Transaction beginTransaction = session.beginTransaction(); 7 8 User user = session.get(User.class, "8a8a200c5d7db0f7015d7db0fe280000"); 9 user.setName("修改的name"); 10 //看後臺輸出會發現,不用調用update方法,也會觸發sql語句修改用戶的name屬性! 11 beginTransaction.commit(); 12 session.close(); 13 }View Code
自動更新功能,其實是借助session的一級緩存!
-
瞬時態 -- 沒有持久化標識OID, 沒有被納入到Session對象的管理
獲得瞬時態的對象
User user = new User()
-
瞬時態對象轉換持久態
-
save()/saveOrUpdate();
-
-
瞬時態對象轉換成脫管態
-
user.setId(1)
-
-
-
持久態-- 有持久化標識OID,已經被納入到Session對象的管理
獲得持久態的對象
-
持久態轉換成瞬時態對象
-
delete(); --- 比較有爭議的,進入特殊的狀態(刪除態:Hibernate中不建議使用的)
-
-
持久態對象轉成脫管態對象
-
session的close()/evict()/clear();
-
-
-
脫管態-- 有持久化標識OID,沒有被納入到Session對象的管理
獲得托管態對象:不建議直接獲得脫管態的對象.
User user = new User();
user.setId(1);
脫管態對象轉換成持久態對象
update();/saveOrUpdate()/lock();
脫管態對象轉換成瞬時態對象
user.setId(null);
1. 一級緩存介紹
Hibernate的一級緩存是指Session(屬於事務範圍的緩存,由Hibernate管理,無需幹預),它是一塊內存空間,用來存放從數據庫查詢出的java對象,有了一級緩存,應用程序可以減少訪問數據庫的次數,提高了性能。
在使用Hibernate查詢對象的時候,首先會使用對象屬性的OID值(對應表中的主鍵)在Hibernate的一級緩存進行查找,如果找到,則取出返回,不會再查詢數據庫,如果沒有找到,再到數據庫中進行查詢操作。然後將查詢結果存放到Session一級緩存中。
- 一級緩存演示
1 public class CacheLevelOneTest { 2 3 /** 4 * 使用代碼來證明Hibernate的一級緩存是存在的! 5 */ 6 @Test 7 public void testCache(){ 8 9 Session session = HibernateUtil.openSession(); 10 Transaction tx = session.beginTransaction(); 11 12 //第1次查詢 13 Customer c1 = session.get(Customer.class, 1L); 14 System.out.println(c1); 15 16 //第2次查詢 17 Customer c2 = session.get(Customer.class,1L); 18 System.out.println(c2); 19 //第二次查詢不觸發sql語句,直接獲取緩存中的結果! 20 tx.commit(); 21 session.close(); 22 } 23 }View Code
當執行 commit() 時,Hibernate同時會執行 flush() 方法,hibernate會清理session的一級緩存(flush),也就是將堆內存中的數據與快照中的數據進行對比,如果不一致,則會執行同步(update)操作,若相同,則不執行update。
1、快照是數據的副本
2、快照屬於一級緩存
3、快照是在堆內存中的
1 /** 2 * 說明持久態對象可以直接更新數據庫的數據! 3 */ 4 @Test 5 public void testAutoUpdate(){ 6 7 Session session = HibernateUtil.openSession(); 8 Transaction tx = session.beginTransaction(); 9 10 //獲取到一個持久態對象 11 Customer cust = session.get(Customer.class, 1L); 12 //修改cust的數據 13 cust.setName("湯姆"); 14 15 //沒有 必要執行update語句,因為現在持久態對象已經能夠更新數據庫的數據啦! 16 //session.update(cust); 17 18 tx.commit(); 19 session.close(); 20 21 }View Code
把對象移出一級緩存的方法:
session.evict(object) : 把一個對象移出一級緩存
測試:以下測試數據不會被更新
1 /** 2 * 一級緩存的管理 3 */ 4 @Test 5 public void testEvictAndClear(){ 6 Session session = HibernateUtil.openSession(); 7 Transaction tx = session.beginTransaction(); 8 Customer cust = session.get(Customer.class, 1L); //cust是持久態對象,在一級緩存 9 cust.setName("老王"); 10 11 //把cust對象移出一級緩存 12 session.evict(cust); 13 14 //清空一級緩存 15 //session.clear(); 16 17 tx.commit(); 18 session.close(); 19 }View Code
Hibernate事務以及一級緩存02