Hibernate 5.3 (九)

事務概念

事務是一步或幾步基本操作組成的邏輯執行單元,這些基本操作作爲一個整體執行單元,它們要麼全部執行,要麼全部取消,絕不能僅僅執行一部分。一般而言,每次用戶請求,對應一個業務邏輯方法,一個業務邏輯方法往往具有邏輯上的原子性,應該使用事務。例如一個轉賬操作,對應修改兩個賬戶的餘額,這兩個賬戶的修改要麼同時生效,要麼同時取消一同時生效是轉賬成功,同時取消是轉賬失敗,但不可只修改其中一個賬戶,那將破壞數據庫的完整性。

事務特性(ACID)

  1. 原子性(atomicity): 事務中不可分割最小邏輯執行體
  2. 一致性(consistency):如果系統運行發生中斷,某個事務尚未完成而被迫中斷, 而該未完成的事務對數據庫所做的修改已被寫入數據庫, 此時,數據庫就處於一種不正確的狀態。比如銀行在兩個賬戶之間轉賬:從A賬戶向B賬戶轉入1000元。系統先減少A賬戶的1000元, 然後再爲B賬戶增加1000元。如果全部執行成功,數據庫處於一致性狀態。如果僅執行完A賬戶金額的修改, 而沒有增加B賬戶的金額,則數據庫就處於不一致性狀態。 因此,一致性是通過原子性來保證的。
  3. 隔離性(isolation):事務之間互相不影響。
  4. 持續性(durability):也稱之爲持久性,事務一旦提交,就保存到數據庫。

Session與事務

Tip

線程安全:在多線程使用該對象的時候,底層是通過鎖機制去保證,每次只有一個對象執行。
線程不安全:在多線程使用該對象的時候,多個線程對同時執行,會出現邏輯錯亂。

SessionFactory 對象的創建代價很高,它是線程安全的對象,被設計成可以被所有線程所共享。通常,SessionFactory 會在應用程序啓動時創建,一旦創建 了SessionFactory 將不會輕易關閉, 只有當應用退出時才關閉SessionFactory。

Session對象是輕量級的,它也是線程不安全的。下面是比較的方法去獲取session:

public static final ThreadLocal<Session> session
		= new ThreadLocal<Session>();

	public static Session currentSession()
		throws HibernateException
	{
		Session s = session.get();
		// 如果該線程還沒有Session,則創建一個新的Session
		if (s == null)
		{
			s = sessionFactory.openSession();
			// 將獲得的Session變量存儲在ThreadLocal變量session裏
           // 相當於每一個線程都綁定一個session,你每次線程來拿的時候,都用自己的線程的session,怎麼會衝突,就不需要線程同步
			session.set(s);
		}
		return s;
	}

對於單個業務進程、單個的工作單元而言,Session只被使用一次。創建Session 時,並不會立即打開與數據庫之間的連接,只有需要進行數據庫操作時,Session纔會獲取JDBC連接。因此,打開和關閉Session,並不會對性能造成很大的影響。甚至即使無法確定一個請求是否需要數據訪問,也可以打開Session對象,因爲如果不進行數據庫訪問,Session不會獲取JDBC連接。

Hibernate 建議一次事務請求,對應一個Session。

長事務

事務持續時間很長,在事務中有多個用戶操作。

對於長事務而言:一個比較差的做法就是在用戶與服務器會話(session, 通常就是一次HTTP Session)期間,應用程序保持 Session與數據庫事務打開,並保持數據庫鎖定,以阻止併發修改,從而保證數據庫事務隔離級別和原子操作。這種數據庫鎖定會導致應用程序無法擴展併發用戶的數量。每次HTTP Session對應一次Hibernate Session的模式會導致應用程序無法擴展併發用戶的數量,因此不推薦使用。

Hibernate 處理長事務的三種方式:

  • 自動版本化: Hibernate能夠自動進行樂觀併發控制,如果在用戶處理的過程中持久化實體發生併發修改,
    Hibernate能夠自動檢測到。
  • 脫管對象:如果採用每次用戶請求對應一次Session的模式,那麼,前面載入的實例在用戶處理的過程中,始終與Session脫離,處於脫管狀態。Hibernate允許把脫管對象重新關聯到Session上,並且對修改進行持久化。在這種模式下,自動版本化被用來隔離併發修改。這種模式也被稱爲使用脫管對象的每次請求對應一個Hibernate Session。
  • 長生命週期Session: Session可以在數據庫事務提交之後,斷開和底層的JDBC連接。當新的客戶端請求到來時,它又重新連接上底層的JDBC連接。這種模式被稱爲每個應用程序事務對應一個Session。因爲應用程序事務是相當長(跨越多個用戶請求)的,所以也被稱爲長生命週期Session。

在session 關閉之前,如果集合屬性,,由於懶加載的原因,一定要裝載所有的數據,不然你在後面使用的時候,會報異常。

上下文相關的Session

Hibernate 應用使用Session時都用到了HibermateUtil 工具類,因爲該工具類可以保證將線程不安全的Session綁定限制在當前線程內,也就是實現一種“上下文相關的”Session。

Hibernate 管理上下文的三種策略

  • org.hibernate.context.JTASessionContext:根據JTA來跟蹤和界定上下文相關Session,這和最早的僅支持JTA的方法是完全一樣的。
  • org.hibernate.context.ThreadLocalSessionContext:通過當前正在執行的線程來跟蹤和界定上下文相關Session,也就是和前面的HibernateUtil的Session維護模式相似。
  • org.hibernate.context.ManagedSessionContext:通過當前執行的線程來跟蹤和界定上下文相關Session。但是程序需要使用這個類的靜態方法將Session實例綁定、取消綁定,它並不會自動打開、fush 或者關閉任何Session。

如果使用ThreadLocalSessionContext 策略,Hibermate 的Session會隨着getCurrentSession(方法自動打開,並隨着事務提交自動關閉,非常方便。對於在容器中使用Hibermate 的場景而言,通常會採用第種方式,對於獨立的Hibernate應用而言,通常會採用第二種方式。

我們通常在Hibernate 配置文件中,可以通過hibernate.current_session_context_class去配置該屬性。

事件機制

Hibermate執行持久化過程中,應用程序無法參與其中。

通過事件框架,Hibermate 允許應用程序能響應特定的內部事件,從而允許實現某些通用的功能,或者對Hibernate功能進行擴展。

Hibernate的事件框架組成

  • 攔截器機制:對於特定動作攔截,回調應用中的特定動作。
  • 事件系統:重寫Hibernate的事件監聽器。
攔截器機制

通過實現Interceptor接口,可以從Session中回調應用程序的特定方法,這種回調機制可讓應用程序在持久化對象被保存、更新、刪除或加載之前,檢查並修改其屬性。

使用攔截器按如下步驟進行:

  1. 定義實現Interceptor接口的攔截器類。
  2. 通過Session啓用攔截器,或者通過Configuration 啓用全局攔截器。

在Hibernate 下有Interceptor 接口和EmptyInterceptor 類,EmptyInterceptor 就像是一個適配器類,因爲Interceptor 要實現的方法比較多,沒有必要全寫,所以可以使用EmptyInterceptor 。

public class MyHibernateTestInterceptor extends EmptyInterceptor {
	private int count;
//注意這裏實現的是Hibernate 下 的攔截器
	@Override
	public boolean onSave(Object entity, Serializable id, Object[] state,
			String[] propertyNames, Type[] types) {
		// TODO Auto-generated method stub
		count++;
		return super.onSave(entity, id, state, propertyNames, types);
	}
	@Override
	public void afterTransactionCompletion(Transaction tx) {
		// TODO Auto-generated method stub
		System.out.println(count);
	}
	

}

該攔截器實現簡單,在save 實例的時候,統計save 次數。

攔截器種類
  • 打開一個帶局部攔截器的Session,這個攔截器只是針對該session有效
		   Session ss = sf.withOptions().interceptor(new MyHibernateTestInterceptor()).openSession();//這裏必須要這樣寫,用鏈式調用創建session,否則攔截器無效。而且這種,每次都要創建一個sessionfactory,用同一個也無效。
  • 通過Configuration的setInterceptor(Interceptor in)方法設置全局攔截器。
事件系統

事件系統可以替代攔截器

基本上,Session 接口的每個方法都有對應的事件,比如LoadEvent、 FlushEvent 等。當Session調用某個方法時,Hibernate Session會生成對應的事件,並激活對應的事件監聽器。

使用事件系統按如下步驟進行:

  1. 實現自己的事件監聽器類。
  2. 註冊自定義事件監聽器,代替系統默認的事件監聽器。
public class MyHibernateTestListener extends DefaultSaveEventListener {
	@Override
	public void onSaveOrUpdate(SaveOrUpdateEvent arg0) {
		// TODO Auto-generated method stub
		System.out.println("在save 觸發的事件");
		super.onSaveOrUpdate(arg0);
	}

}

注意:擴展用戶自定義監聽器時,別忘了在方法中調用父類的對應方法, 否則Hibernate 默認的持久化行爲都會失效。 因爲這些持久化行爲本身就是通過事件來完成的。

註冊事件監聽器,(所有的事件監聽器全在這注冊):


    public class RegisterService implements Integrator {

	@Override
	public void disintegrate(SessionFactoryImplementor arg0,
			SessionFactoryServiceRegistry arg1) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void integrate(Metadata arg0, SessionFactoryImplementor arg1,
			SessionFactoryServiceRegistry arg2) {
		// TODO Auto-generated method stub
	    EventListenerRegistry eventListenerRegistry = 
	            arg2.getService(EventListenerRegistry.class);

	      eventListenerRegistry.getEventListenerGroup(EventType.SAVE)
	                     .appendListener(new MyHibernateTestListener());
	                     //EventType 事件枚舉類型,包含所有的事件類型
	      
	}

}

在代碼中應用註冊事件類

BootstrapServiceRegistry bootstrapRegistry =
                 new BootstrapServiceRegistryBuilder()
                 .applyIntegrator(new RegisterService())
                 .build();
		StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapRegistry)
        .configure().build();
		Metadata metadata = new MetadataSources( serviceRegistry ).getMetadataBuilder().build();
		SessionFactory sf = metadata.getSessionFactoryBuilder().build();

還有一種配置事件的方法,在Hibernate配置文件中:

  1. 拓展系統默認xx事件監聽器。
  2. 在hibernate 配置文件中配置。
	    <listener  type="save" class="com.example.test.listener.MyHibernateTestListener"/>//這裏type 自己可以查看hibernate 事件類型,實際上是保存在一個map 中,key 就是這裏type ,value 就是hibernate自己事件類,而我們之間拓展默認的事件,一旦註冊,就會使用我們默認的,所以上面注意的那句話,就是保證原來hibernate 不會丟了,知道不?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章