1. 一級緩存
前面學習了一級緩存的主要兩個作用:
提高效率手段1:提高查詢效率
提高效率手段2:減少不必要的修改語句發送
現在開始了解一下Hibernate的事務控制。Hibernate是對JDBC的輕量級封裝,其主要功能是操作數據庫。在操作數據庫過程中,經常會遇到事務處理的問題,那么我們接下來就介紹hibernate中的事務管理。
回歸一下,什么是事務:在數據庫操作中,一項事務(Transaction)是由一條或多條操作數據庫的SQL語句組成的一個不可分割的工作單元。當事務中的所有操作都正常完成時,整個事務才能被提交到數據庫中,如果一項操作沒有完成,則整個事務會被回滾。
其實事務總結起來理解為:邏輯上的一組操作,組成這組操作的各個單元,要么一起成功,要么一起失敗。
事務的四個特性:事務有很嚴格的定義,需要同時滿足四個特性,即原子性、一致性、隔離性、持久性。這四個特性通常稱之為ACID特性,具體如下:
原子性(Atomic):表示該事務中所做的操作捆綁成一個不可分割的單元,即對事務所進行的數據修改等操作,要么全部執行,要么全部不執行。
一致性(Consistency):表示事務完成時,必須使所有的數據都保持一致狀態。
隔離性(Isolation):指一個事務的執行不能被其它事務干擾。即一個事務內部的操作及使用的數據對並發的其他事務時隔離的,並發執行的各個事務之間不能互相干擾。
持久性(Durability):持久性也叫永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。提交后的其他操作或故障不會對其有任何影響。
事務的並發問題:在實際應用過程中,數據庫是要被多個用戶所共同訪問的。在多個事務同時使用相同的數據時,可能會發生並發的問題,具體如下:
1).臟讀:一個事務讀取到另一個事務未提交的數據。
2).不可重復讀:一個事務讀到了另一個事務已經提交的update的數據,導致在同一個事務中的多次查詢結果不一致。
2).虛讀/幻讀:一個事務讀到了另一個事務已經提交的insert的數據,導致在同一個事務中的多次查詢結果不一致。
事務的隔離級別
為了避免事務並發問題的發生,在標准SQL規范中,定義了4個事務隔離級別,不同的隔離級別對事務的處理不同。
1. 讀未提交(Read Uncommitted,1級): 一個事務在執行過程中,既可以訪問其他事務提交的新插入的數據,又可以訪問未提交的修改數據。如果一個事務已經開始寫數據,則另外一個事務則不允許同時進行寫操作,但允許其他事務讀此行數據。此隔離級別可防止丟失更新。
2.已提交讀(Read committed, 2級): 一個事務在執行過程中,既可以訪問其他事務成功提交的新插入的數據,又可以訪問成功修改的數據。讀取數據的事務允許其他事務繼續訪問該行數據,但是未提交的寫事務將會禁止其他事務訪問該行。此隔離級別可有效防止臟讀。
3.可重復讀(Repeatable Read, 4級): 一個事務在執行過程中,可以訪問其他事務成功提交的新插入的數據,但不可以訪問成功修改的數據。讀取數據的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。此隔離級別可有效的防止不可重復讀和臟讀。
4. 序列化/串行化(Serializable, 8級):提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接着一個地執行,但不能並發執行。此隔離級別可有效的防止臟讀、不可重復讀和幻讀。
READ_UNCOMMITTED: 允許你讀取還未提交的改變了的數據。可能導致臟、幻、不可重復讀;
READ_COMMITTED: 允許在並發事務已經提交后讀取。可防止臟讀,但幻讀和不可重復讀仍可發生;
REPEATABLE_READ: 對相同字段的多次讀取是一致的,除非數據被事務本身改變。可防止臟、不可重復讀,但幻讀仍可能發生。
SERIALIZABLE:完全服從ACID的隔離級別,確保不發生臟、幻、不可重復讀。這在所有的隔離級別中是最慢的,它是典型的通過完全鎖定在事務中涉及的數據表來完成的。
事務的隔離級別,是由數據庫提供的,並不是所有數據庫都支持四種隔離級別:
Mysql:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE(默認REPEATABLE_READ)
Oracle: READ_UNCOMMITTED、READ_COMMITTED、SERIALIZABLE(默認 READ_COMMITTED)
在使用數據庫時候,隔離級別越高,安全性越高,性能越低。
實際開發中,不會選擇最高或者最低隔離級別,選擇READ_COMMITTED(oracle默認)、REPEATABLE_READ(mysql默認)
2 Hibernate事務管理
在Hibernate中,可以通過代碼來操作管理事務,如通過
“Transaction tx=session.beginTransactiong();”
開啟一個事務,持久化操作后,通過"tx.commit();" 提交事務;如果事務出現異常,又通過“tx.rollback();"操作來撤銷事務(事務回滾)。
除了在代碼中對事務開啟,提交和回滾操作外,還可以在hibernate的配置文件中對事務進行配置。配置文件中,可以設置事務的隔離級別。其具體的配置方法是在hibernate.cfg.xml文件中的
<session-factory>標簽元素中進行的。配置方法如下所示。
<!--
事務隔離級別
hibernate.connection.isolation = 4
1-- Read uncommitted isolation
2-- Read committed isolation
4-- Repeatable read isolation
8-- Serializable isolation
-->
<property name="hibernate.connection.isolation">4</property>
到這里我們已經設置了事務的隔離級別,那么我們在真正進行事務管理的時候,需要考慮事務的應用場景,也就是說我們的事務控制不應該是在DAO層實現的,應該在Service層實現,並且在Service中調用多個DAO實現一個業務邏輯的操作。具體操作如下顯示:
其實最主要的是如何保證在Service中開啟的事務時使用的Session對象和DAO中多個操作使用的是同一個Session對象。
其實有兩種辦法可以實現:
1. 可以在業務層獲取到Session,並將Session作為參數傳遞給DAO。
2. 可以使用ThreadLocal將業務層獲取的Session綁定到當前線程中,然后再DAO中獲取Session的時候,都從當前線程中獲取。
其實使用第二種方式肯定是最優方案,那么具體的實現已經不用我們來完成了,hibernate的內部已經將這個事情做完了。我們只需要完成一段配置即可。
Hibernate5中自身提供了三種管理Session對象的方法
Session對象的生命周期與本地線程綁定
Session對象的生命周期與JTA事務綁定
Hibernate委托程序管理Session對象的生命周期
在Hibernate的配置文件中,hibernate.current_session_context_class屬性用於指定Session管理方式,可選值包括:
1. thread:Session對象的生命周期與本地線程綁定(推薦)
2. jta:Session對象的生命周期與JTA事務綁定
3. managed:hibernate委托程序來管理Session對象的生命周期。
在hibernate.cfg.xml中進行如下配置:
<!-- 配置session綁定本地線程 -->
<property name="hibernate.current_session_context_class">thread</property>
Hibernate提供sessionFactory.getCurrentSession()創建一個session和ThreadLocal綁定方法。
在HibernateUtils工具類中更改getCurrentSession方法:
public static Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
而且Hibernate中提供的這個與線程綁定的session可以不用關閉,當線程執行結束后,就會自動關閉了。
所以最終的配置文件如下:

1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd" > 3 <hibernate-configuration> 4 <session-factory> 5 <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> 6 <property name="hibernate.connection.url">jdbc:mysql:///day24_db</property> 7 <property name="hibernate.connection.username">root</property> 8 <property name="hibernate.connection.password">toor</property> 9 <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> 10 <property name="hibernate.show_sql">true</property> 11 <property name="hibernate.format_sql">true</property> 12 <property name="hibernate.hbm2ddl.auto">update</property> 13 <!-- 配置C3P0連接池 14 <property name="connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property> 15 在連接池中可用的數據庫連接的最少數目 16 <property name="c3p0.min_size">5</property> 17 在連接池中所有數據庫連接的最大數目 18 <property name="c3p0.max_size">20</property> 19 設定數據庫連接的過期時間,以秒為單位,如果連接池中的某個數據庫連接處於 20 空閑狀態的時間超過了timeout時間,就會從連接池中清除 21 <property name="c3p0.timeout"></property> 22 每3000秒檢查所有連接池中的空閑連接,以秒為單位 23 <property name="c3p0.idle_test_period">3000</property> --> 24 25 <!-- 配置隔離級別 --> 26 <property name="hibernate.connection.isolation">4</property> 27 <!-- 配置session綁定本地線程 --> 28 <property name="hibernate.current_session_context_class">thread</property> 29 <mapping resource="cn/eagle/domain/Customer.hbm.xml" /> 30 </session-factory> 31 </hibernate-configuration>
最終的工具類如下:

1 package cn.eagle.utils; 2 3 import org.hibernate.Session; 4 import org.hibernate.SessionFactory; 5 import org.hibernate.cfg.Configuration; 6 7 public class HibernateUtils { 8 9 private static final Configuration configuration; 10 private static final SessionFactory sessionFactory; 11 12 static { 13 configuration = new Configuration().configure(); 14 sessionFactory = configuration.buildSessionFactory(); 15 } 16 17 public static Session getCurrentSession() { 18 return sessionFactory.getCurrentSession(); 19 } 20 }
到這里我們已經對Hibernate的事務管理有了基本的了解,但是之前我們所做的CRUD的操作其實還沒有查詢多條記錄。那如果我們需要查詢多條記錄要如何完成呢,我們接下去學習一下。