Hibernate的事件機制

4.8 事 件 機 制

通常,Hibernate執行持久化過程中,應用程序無法參與其中。所有的數據持久化操作,對用戶都是透明的,用戶無法插入自己的動作。

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

Hibernate的事件框架由兩個部分組成:

?? ● 攔截器機制,對于特定動作攔截,回調應用中的特定動作。

?? ● 事件系統,重寫Hibernate的事件監聽器。

4.8.1 攔截器

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

通過Interceptor接口,可以在數據進入數據庫之間,對數據進行最后的檢查,如果數據不符合要求,可以修改數據,從而避免非法數據進入數據庫。當然,通常無須這樣做,只是在某些特殊的場合下,才考慮使用攔截器完成檢查功能。

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

(1)定義實現Interceptor接口的攔截器類;

(2)通過Session啟用攔截器,或者通過Configuration啟用全局攔截器。

下面是一個攔截器的示例代碼,該攔截器沒有進行任何實際的操作,僅僅打印出標志代碼:

public class MyInterceptor extends EmptyInterceptor

{

??? //更新的次數

??? private int updates;

??? //插入的次數

??? private int creates;

??? //刪除數據時,將調用onDelete方法

??? public void onDelete(Object entity,Serializable id,Object[]

??? state,String[] propertyNames, Type[] types)

??? {

??????? //do nothing

??? }

??? //同步Session和數據庫中的數據

??? public boolean onFlushDirty(Object entity, Serializable id, Object[]

??? currentState, Object[] previousState, String[] propertyNames, Type[]

??????????????????????? ??? types)

??? {

??????? //每同步一次,修改的累加器加1

??????? updates++;

??????? for ( int i=0; i < propertyNames.length; i++ )

??????? {

??????????? if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )

??????????? {

??????????????? currentState[i] = new Date();

??????????????? return true;

??????????? }

??????? }

??????? return false;

??????? }

??? //加載持久化實例時,調用該方法

??? public boolean onLoad(Object entity,Serializable id,Object[]

??? state,String[] propertyNames,Type[] types)

??? {

??????? System.out.println("========================");

??????? for ( int i=0; i < propertyNames.length; i++ )

??????? {

??????????? if ( "name".equals( propertyNames[i] ) )

??????????? {

??????????????? System.out.println(state[i]);

??????????????? state[i] = "aaa";

??????????????? return true;

??????????? }

??????? }

??????? return false;

??? }

??? //保存持久化實例時,調用該方法

??? public boolean onSave(Object entity,Serializable id,Object[]

??? state,String[] propertyNames,Type[] types)

??? {

??????? creates++;

??????? for ( int i=0; i<propertyNames.length; i++ )

??????? {

??????????? if ( "createTimestamp".equals( propertyNames[i] ) )

??????????? {

??????????????? state[i] = new Date();

??? ??????????? return true;

??????????? }

??????? }

??????? return false;

??? }

??? //提交刷新

??? public void postFlush(Iterator entities)

??? {

??????? System.out.println("創建的次數: " + creates + ", 更新的次數: " +

??? updates);

??? }

??? public void preFlush(Iterator entities)

??? {

??????? updates=0;

??????? creates=0;

??? }

??? //事務提交前,觸發該方法

??? public void beforeTransactionCompletion(Transaction tx)

??? {

??????? System.out.println("事務即將結束");

??? }

??? //事務提交后,觸發該方法

??? public void afterTransactionCompletion(Transaction tx)

??? {

??????? System.out.println("事務已經結束");

??? }

}

在上面的攔截器實現類中,實現了很多方法,這些方法都是在Hibernate執行特定動作時自動調用。

完成了攔截器的定義,下面是關于攔截器的使用。攔截器的使用有兩種方法:

?? ● 通過SessionFactory的openSession(Interceptor in)方法打開一個帶局部攔截器的Session。

?? ● 通過Configuration的setInterceptor(Interceptor in)方法設置全局攔截器。

下面是使用局部攔截器的示例代碼:

public class HibernateUtil

{

??? //靜態類屬性 SessionFactory

??? public static final SessionFactory sessionFactory;

??? //靜態初始化塊,完成靜態屬性的初始化

??? static

??? {

??????? try

??????? {

??????????? //采用默認的hibernate.cfg.xml來啟動一個Configuration的實例

??????????? Configuration configuration=new Configuration().configure();

??????????? //由Configuration的實例來創建一個SessionFactory實例

??????????? sessionFactory = configuration.buildSessionFactory();

??????? }

??????? catch (Throwable ex)

??????? {

??????????? System.err.println("初始化sessionFactory失敗." + ex);

??????????? throw new ExceptionInInitializerError(ex);

??????? }

??? }

??? //ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,因此不再需要

??? 對線程同步???

??? public static final ThreadLocal session = new ThreadLocal();

??? //不加攔截器的打開Session方法

??? public static Session currentSession() throws HibernateException

??? {

??????? Session s = (Session) session.get();

??????? //如果該線程還沒有Session,則創建一個新的Session

??????? if (s == null)

??????? {

??????????? s = sessionFactory.openSession();

??????????? //將獲得的Session變量存儲在ThreadLocal變量的Session里

??????????? session.set(s);

??????? }

??????? return s;

??? }

??? //加攔截器的打開Session方法

??? public static Session currentSession(Interceptor it) throws

??? HibernateException

??? {

??????? Session s = (Session) session.get();

??????? //如果該線程還沒有Session,則創建一個新的Session

??????? if (s == null)

??????? {

??????????? //以攔截器創建Session對象

??????? ??? s = sessionFactory.openSession(it);

??????????? //將獲得的Session變量存儲在ThreadLocal變量的Session里

??????????? session.set(s);

??????????? }

??????? return s;

??? }

??? //關閉Session對象

??? public static void closeSession() throws HibernateException

??? {

??????? Session s = (Session) session.get();

??????? if (s != null)

??? ??????? s.close();

??????? session.set(null);

??? }

}

上面的Hibernate工具類提供了兩個currentSession方法,分別用于不使用攔截器獲取Session對象和使用攔截器獲取Session對象。

下面是主程序使用攔截器的代碼片段:

private void testUser()

{

??? //以攔截器開始Session

??? Session session = HibernateUtil.currentSession(new MyInterceptor());

??? //開始事務

??? Transaction tx = session.beginTransaction();

??? //執行下面的代碼時,可以看到系統回調onSave等方法

??? /*

??? User u = new User();

??? u.setName("Yeeku Lee");

??? u.setAge(28);

??? u.setNationality("中國");

??? session.persist(u);

??? u.setAge(29);

??? u.setAge(30);

??? session.persist(u);

??? */

??? //執行下面的代碼時,可以看到系統回調onLoad等方法

??? Object o = session.load(User.class , new Integer(1));

??? System.out.println(o);

??? User u = (User)o;

??? System.out.println(u.getName());

??? //提交事務時,可以看到系統回調事務相關方法

??? tx.commit();

??? HibernateUtil.closeSession();

}

4.8.2 事件系統

Hibernate 3的事件系統是功能更強大的事件框架,事件系統可以替代攔截器,也可以作為攔截器的補充來使用。

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

系統默認監聽器實現的處理過程,完成了所有的數據持久化操作,包括插入和修改等操作。如果用戶定義了自己的監聽器,則意味著用戶必須完成對象的持久化操作。

例如,可以在系統中實現并注冊LoadEventListener監聽器,該監聽器負責處理所有調用Session的load()方法的請求。

監聽器是單態模式對象,即所有同類型的事件處理共享同一個監聽器實例,因此監聽器不應該保存任何狀態,即不應該使用成員變量。

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

(1)實現自己的事件監聽器類;

(2)注冊自定義事件監聽器,代替系統默認的事件監聽器。

實現用戶的自定義監聽器有如下3個方法:

?? ● 實現對應的監聽器接口,這是不可思議的,實現接口必須實現接口內的所有方法,關鍵是必須實現Hibernate對應的持久化操作,即數據庫訪問,這意味著程序員完全取代了Hibernate的底層操作。

?? ● 繼承事件適配器,可以選擇性地實現需要關注的方法,但依然試圖取代Hibernate完成數據庫的訪問,這也不太現實。

?? ● 繼承系統默認的事件監聽器,擴展特定方法。

實際上,前兩種方法很少使用。因為Hibernate的持久化操作也是通過這些監聽器實現的,如果用戶取代了這些監聽器,則應該自己實現所有的持久化操作,這意味著用戶放棄了Hibernate的持久化操作,而改為自己完成Hibernate的核心操作。

通常推薦采用第三種方法實現自己的事件監聽器。Hibernate默認的事件監聽器都被聲明成non-final,從而方便用戶繼承。

下面是用戶自定義監聽器的示例:

//自定義LoadListener,繼承默認的DefaultLoadEventListener實現類

public class MyLoadListener extends DefaultLoadEventListener

{

??? //在LoadEventListener接口僅僅定義了這個方法

??? public Object onLoad(LoadEvent event, LoadEventListener.LoadType

??? loadType)throws HibernateException

??? {

??????? //先調用父類的onLoad方法,從而完成默認的持久化操作

??????? Object o = super.onLoad(event, loadType);

??????? //加入用戶的自定義處理

??????? System.out.println("自定義的load事件");

??????? System.out.println(event.getEntityClassName() + "==========" +

??????? event.getEntityId());

??????? return o;

??? }

}

下面還有一個MySaveListener,用于監聽SaveEvent事件:

//自定義SavaListener,繼承默認的DefaultSaveEventListener實現類

public class MySaveListener extends DefaultSaveEventListener

{

??? //該方法完成實際的數據插入動作

??? protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event)

??? {

??????? //先執行用戶自定義的操作

??????? System.out.println(event.getObject());

??????? //調用父類的默認持久化操作

??????? return super.performSaveOrUpdate(event);

??? }

}

注意:擴展用戶自定義監聽器時,別忘了在方法中調用父類的對應方法。

注冊用戶自定義監聽器也有兩種方法:

?? ● 編程式,通過使用Configuration對象編程注冊。

?? ● 聲明式,在Hibernate的XML格式配置文件中進行聲明,使用Properties格式的配置文件將無法配置自定義監聽器。

下面的示例代碼,通過編程方式使用自定義監聽器:

public class HibernateUtil2

{

??? //靜態類屬性 SessionFactory

??? public static final SessionFactory sessionFactory;

??? //靜態初始化塊,完成靜態屬性的初始化

??? static

??? {

??????? try

??????? {

??????????? Configuration cfg = new Configuration();

??????????? //注冊loadEventListener監聽器

??????????? cfg.getSessionEventListenerConfig().setLoadEventListener

??????????? ( new MyLoadListener() );

??????????? //注冊saveListener監聽器

??????????? cfg.getSessionEventListenerConfig().setSaveEventListener

??????????? (new MySaveListener() );

??????????? //由Configuration實例來創建一個SessionFactory實例

??????????? sessionFactory = cfg.configure().buildSessionFactory();

??????? }

??????? catch (Throwable ex)

??????? {

??????????? System.err.println("初始化sessionFactory失敗." + ex);

??????????? throw new ExceptionInInitializerError(ex);

??????? }

??? }

??? //ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,因此不再需要

??? 對線程同步

??? public static final ThreadLocal session = new ThreadLocal();

??? //不加攔截器的打開Session方法

??? public static Session currentSession() throws HibernateException

??? {

??????? Session s = (Session) session.get();

??????? //如果該線程還沒有Session,則創建一個新的Session

??????? if (s == null)

??????? {

??????????? s = sessionFactory.openSession();

??? ??????? //將獲得的Session變量存儲在ThreadLocal變量的Session里

??????????? session.set(s);

??????? }

??????? return s;

??? }

??? //關閉Session對象

??? public static void closeSession() throws HibernateException

??? {

??????? Session s = (Session) session.get();

??????? if (s != null)

??????????? s.close();

??????? session.set(null);

??? }

}

如果不想修改代碼,也可以在配置文件中使用事件監聽器,注冊事件監聽器的Hibernate配置文件代碼如下:

<?xml version='1.0' encoding='GBK'?>

<!-- Hibernate配置文件的文件頭,包含DTD等信息 -->

<!DOCTYPE hibernate-configuration PUBLIC

??????? "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

??????? "http://hibernate.sourceforge.net/hibernate-configuration-3.0.

??????? dtd">

<!-- Hibernate配置文件的根元素 -->

<hibernate-configuration>

??? <session-factory>

??????? <!—設置數據庫驅動 -->

??????? <property name="connection.driver_class">com.mysql.jdbc.Driver

??????? </property>

??????? <!-- 數據庫服務的url -->

??? ??? <property name="connection.url">jdbc:mysql://localhost/hibernate

??????? </property>

??????? <!-- 數據庫服務的用戶名 -->

??????? <property name="connection.username">root</property>

??????? <!-- 數據庫服務的密碼 -->

??????? <property name="connection.password">32147</property>

??????? <!-- JDBC connection pool (use the built-in) -->

??????? <property name="connection.pool_size">5</property>

??????? <!-- 設置數據庫方言 -->

??????? <property name="dialect">org.hibernate.dialect.MySQLDialect

??????? </property>

??????? <!-- 顯示Hibernate生成的SQL語句 -->

??????? <property name="show_sql">true</property>

??????? <!-- 配置應用啟動時,是否啟動自動建表 -->

??????? <property name="hbm2ddl.auto">update</property>

??????? <!-- 列出所有的持久化映射文件 -->

??????? <mapping resource="User.hbm.xml"/>

??????? <!-- 注冊事件監聽器 -->

??? ??? <listener type="load" class="lee.MyLoadListener"/>

??? ??? <listener type="save" class="lee.MySaveListener"/>

??? </session-factory>

</hibernate-configuration>

使用配置文件注冊事件監聽器雖然方便,但也有不利之處,通過配置文件注冊的監聽器不能共享實例。如果多個<listener/>元素中使用了相同的類,則每一個引用都將產生一個新的攔截器實例。如果需要在多個事件之間共享監聽器的實例,則必須使用編程方式注冊事件監聽器。

注意:雖然監聽器類實現了特定監聽器的接口,在注冊的時候還要明確指出注冊的事件。這是因為一個類可能實現多個監聽器的接口,注冊時明確指定要監聽的事件,可以使得啟用或者禁用某個事件監聽的配置工作更簡單。

轉載于:https://www.cnblogs.com/jadmin/archive/2009/07/19/2206094.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276434.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276434.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276434.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

快速使用Vue3最新的15個常用API

之前我寫了一篇博客介紹了Vue3的新特性&#xff0c;簡單了解了一下Vue3都有哪些特色&#xff0c;并且在文末帶大家稍微體驗了一下Vue3中 Compsition API 的簡單使用上一篇文章地址&#xff1a;緊跟尤大的腳步提前體驗Vue3新特性&#xff0c;你不會還沒了解過Vue3吧因為這個月的…

超級馬里奧代碼_任天堂的源碼泄露,揭示超級馬里奧的前世之生

被黑客盯上的任天堂任天堂遭到了史上最大規模的黑客攻擊&#xff0c;Wii 完整源碼、設計以及《寶可夢》多部作品的信息遭到泄露&#xff0c;而此次泄露事件的后續影響似乎也爆發了出來。《馬里奧賽車》和《超級馬里奧世界2》(耀西島)的早期原型視頻&#xff0c;以及《超級馬里奧…

行者寂寞

公元2009年7月20日。閏五月廿八。炎日&#xff0c;汗如雨。晨行。病臥于京西客站。是夜&#xff0c;不能寐。 公元2009年7月21日。閏五月廿九。戲于清華&#xff0c;游于星巴克。汗如雨。是夜&#xff0c;困于京國際機場5小時。 公元2009年7月22日。六月初一。晨時抵寧。大雨。…

Azure PowerShell (1) PowerShell整理

《Windows Azure Platform 系列文章目錄》 把之前Azure ASM的PowerShell都整理好了。 https://github.com/leizhang1984/AzureChinaPowerShell

漫畫 | 前端發展史的江湖恩怨情仇

時間總是過得很快&#xff0c; 似乎快得讓人忘記了昨天&#xff0c;前端WEB領域的發展更是如此&#xff0c;轉眼間已是近30年&#xff0c;時光荏苒&#xff0c;初心不變&#xff0c;在一代又一代前端人的努力下&#xff0c;前端已經是互聯網不可或缺的一部分。然而很多前端打工…

jquery1.9 下檢測瀏覽器類型和版本

原文鏈接&#xff1a;http://blog.csdn.net/lyc_2011_acm/article/details/8749177 Jquery1.9版本中$.browser已被剔除&#xff1a; 判斷瀏覽器類型&#xff1a; $.browser.mozilla /firefox/.test(navigator.userAgent.toLowerCase()); $.browser.webkit /webkit/.test(nav…

python可迭代對象 迭代器生成器_Python可迭代對象、迭代器和生成器

8.1 可迭代對象(Iterable)大部分對象都是可迭代&#xff0c;只要實現了__iter__方法的對象就是可迭代的。__iter__方法會返回迭代器(iterator)本身&#xff0c;例如&#xff1a;>>> lst [1,2,3]>>> lst.__iter__()Python提供一些語句和關鍵字用于訪問可迭代…

Windows Mobile下使用CppUnitLite輸出測試結果

背景 TDD測試驅動開發是當前流行的開發方法及模式。遵循TDD的方法對開發程序庫(Library)特別有用&#xff0c;因為Library就是為第三方提供一定功能接口的實現&#xff0c;使用TDD的方法可以預先為定義的接口提供測試案例&#xff0c;保證實現代碼能通過測試&#xff0c;保證Li…

sql注意事項2點

①對Null的判斷,如果要用<>與null判斷,則都會得到否定結果②insert into時,要把字段顯示指出,不然會因字段位置變化出錯③-一個數時,如果有可能存在Null,則結果會被置為Null解決方法,select出來的結果,最好加isnull判斷轉載于:https://www.cnblogs.com/lishenglyx/archiv…

PHP IE中下載附件問題

重點&#xff1a; 1、在IE中下載附件之前要清空緩存。 2、中文文件名要用urlencode編碼。 Header("Pragma: "); //不加的話&#xff0c;IE中會提示目標主機無法訪問 Header("Cache-Control: "); //不加的話&#xff0c;IE中會提示目標…

10 個你可能還不知道 VS Code 使用技巧

經常幫一些同學 One-on-One 地解決問題&#xff0c;在看部分同學使用 VS Code 的時候&#xff0c;有些蹩腳&#xff0c;實際上一些有用的技巧能夠提高我們的日常工作效率。NO.1一、重構代碼VS Code 提供了一些快速重構代碼的操作&#xff0c;例如&#xff1a;將一整段代碼提取為…

使用MAP文件快速定位程序崩潰代碼行(轉)

作為程序員&#xff0c;平時最擔心見到的事情就是程序發生了崩潰&#xff0c;無論是指針越界還是非法操作&#xff0c;都將給我們的應用系統造成巨大的損失。但在一個大型系統的測試過程中&#xff0c;初期出現程序崩潰似乎成了不可避免的事。其實測試中出現程序崩潰并不可怕&a…

構建安全的Xml Web Service系列之如何察看SoapMessage

上一篇文章地址&#xff1a;構建安全的Xml Web Service系列一之初探使用Soap頭 (5-22 12:53) 要分析Xml Web Service的安全性&#xff0c;首先要解決的問題是我們能了解和清楚Soap消息的格式和內容&#xff0c;如果獲得不了SoapMessage&#xff0c;分析如何能構建安全Xml w…

前端高效開發必備的 js 庫梳理

之前有很多人問學好前端需要學習哪些 js 庫, 主流框架應該學 vue 還是 react ? 針對這些問題, 筆者來說說自己的看法和學習總結.首先我覺得在學習任何知識之前必須要有一個明確的學習目標, 知道自己為什么要學它, 而不是看網上說的一股腦的給你灌輸各種知識, 讓你學習各種庫, …

交叉報表crosstab隱藏列名顯示_SAP軟件 報表查詢之 輸出格式設置

SAP不僅是功能強大、邏輯嚴謹的ERP軟件&#xff0c;還提供了強大的報表查詢功能。SAP的ALV報表展示功能是SAP的一大特點&#xff0c;實現了類似于EXCEL的功能。使用好ALV報表功能可以方便用戶從SAP中取到想要的數據&#xff0c;尤其是財務用戶。大家在使用SAP報表時&#xff0c…

CSS HACK 區別 IE6、IE7、IE8、Firefox兼容性

轉載鏈接&#xff1a;http://developer.51cto.com/art/201009/226787_1.htm 本文向大家描述一下如何使用CSS HACK區別IE6、IE7、IE8、Firefox兼容性問題&#xff0c;針對不同的瀏覽器寫不同的CSS code的過程&#xff0c;就叫CSS hack,也叫寫CSS hack&#xff0c;相信你對本文介…

Flex sdk4 布局與更新

在flex中,主要使用LayoutManager來驅動組件的度量和布局策略, LayoutManager實現一個單例,在Application類構造函數中創建: 1 public functionApplication()2 {3 UIComponentGlobals.layoutManager ILayoutManager(4 Singleton.getInstance("mx.managers::ILayoutManager&…

linux PROC文件系統詳解

/proc文件系統下的多種文件提供的系統信息不是針對某個特定進程的&#xff0c;而是能夠在整個系統范圍的上下文中使用。可以使用的文件隨系統配置的變化而變化。命令procinfo能夠顯示基于其中某些文件的多種系統信息。以下詳細描述/proc下的文件。----------------------------…

seo每日一貼_白楊SEO:我看ZAC的外貿SEO應該怎樣做?(策略篇)

前言&#xff1a;這是白楊SEO公眾號更新第64篇。本該寫寫頭條SEO啥的&#xff0c;最近在師徒培訓講站內SEO時有旁聽同學提到后面講講谷歌SEO怎么樣&#xff0c;因為谷歌全世界搜索市場占有率&#xff0c;所以外貿SEO最主要還是做谷歌SEO。以白楊特意又去了前輩ZAC的SEO每日一貼…

Can't connect to local MySQL server through socket '/tmp/mysql.sock'

轉載鏈接&#xff1a;http://blog.csdn.net/ixidof/article/details/5958904 摘要&#xff1a;解決不能通過mysql .sock連接MySQL問題 這個問題主要提示是&#xff0c;不能通過 /tmp/mysql .sock連到服務器&#xff0c;而php標準配置正是用過/tmp/mysql .sock&#xff0c;但是…