1. Lifecycle與Validatable
在某些情況下,我們需要對實體對象的CRUD操作進行捕獲並執行相應的處理。在數據庫層,這通常通過觸發器(Triger)實現。
Hibernate 通過Lifecycle、Validatable接口制定了實體對象CRUD過程中的回調(CallBack)方式。
Lifecycle.java
public interface Lifecycle { /**在實體對象Save/Insert操作之前觸發*/ public boolean onSave(Session s) throws CallbackException; /**在Session.update()操作之前觸發*/ public boolean onUpdate(Session s)throws CallbackException; /**在實體對象Delete操作之前觸發*/ public boolean onDelete(Session s)throws CallbackException; /**在實體對象被加載之後觸發*/ public void onLoad(Session s, Serializable id); }
實體類通過實現Lifecycle接口,即可在特定的持久化階段,觸發特定的處理過程:
pulic class TUser implements Serializable,Lifecycle { … public boolean onSave(Session s)throws CallbackException { … return false;//insert操作正常執行 … } public boolean onUpdate(Session s)throws CallbackException{ … if(…)return true;//update操作將被終止 } public boolean onDelete(Session s)throws CallbackException{ … return false;//delete操作正常執行 } public void onLoad(Session s, Serialiable id){ … } }
對於onSave、onUpdate、onDelete方法,如果返回true則意味着需要中止執行對應的操作過程。如果代碼運行期間拋出了CallbackException,對應的操作也會被中止。
注:不要試圖在這些方法中調用Session進行持久化操作,這些方法中Session無法正常使用。如果必須進行持久化操作,需要進行一些特殊處理,具體參見稍後關於Interceptor。
Validatable.java
public interface Validatable{ public void validate() throws ValidationFailure; }
Validatable接口定義了數據驗證實現方式。實體類實現Validatable接口,並在validate對當前待保存的數據進行驗證,以保證數據邏輯合法。
Validatable.validate方法將在實體被持久化之前得到調用以對數據進行驗證。但注意,與Lifecycle的回調方法不同,此方法在實體對象的生命週期內可能被數次調用,因此,此方法應僅用於數據本身的邏輯校驗,而不要試圖在此實現業務邏輯的驗證。
Lifecycle/Validatable接口定義了一種自然的回調機制。但是,這種機制要求實體類必須實現Lifecycle/Validatable接口,Hibernate原生接口的介入,使得我們的實體類移植性大大降低(實際上,此時的實體類已經不再是一個嚴格意義上的POJO)。
由於注意到這點,Hibernate引入了Interceptor,爲持久化事件的捕獲和處理提供了一個非侵略性的實現。
Interceptor
Interceptor接口定義了Hibernate中的通用攔截機制。Session創建時即可以指定加載相應的Interceptor,之後,此Session的持久化操作工作都將首先經由此攔截器捕獲處理。
Interceptor.java
public interface Interceptor{ //對象初始化之前加載,這裏的entity處於剛被創建的狀態(也就是說屬性均未賦值) public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException; //Session.flush方法進行髒數據檢查時,如果發現PO狀態改變,則調用此方法 public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException; //將在對象被保存之前調用,這提供了一個對待保存對象屬性進行修改的機會 public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException; //將在對象被刪除之前調用 public boolean onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException; //Session執行flush方法之前被調用 public void preFlush(Iterator entities) throws CallbackException; //Session執行flush方法之後被調用 public void postFlush(Iterator entities) throws CallbackException; //Session.saveOrUpdate方法時,將調用此方法判斷對象是否尚未保存 public Boolean isUnsaved(Object entity); //Session.flush方法時,將調用此方法判斷對象是否爲髒數據,這提供了髒數據檢查的另一個攔截式實現渠道 public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types); //用於創建實體對象時,如果返回null,則Hibernate將調用實體類的默認構造方法創建實體對象 public Object instantiate(Class clazz, Serializable id) throws CallbackException; }
另外值得注意的是,與Lifecycle相同,Interceptor的方法中不可通過Session實例進行持久化操作。關於Interceptor中的持久化實現參見“Interceptor典型應用”中的內容。
在創建Session實例時,我們可以通過編碼加載Interceptor:
SessionFactory sessionFactory = cofig.buildSessionFactory(); Interceptor it = new MyInterceptor(); session = sessionFactory.openSession(it);
此後,此Session的所有動作均會被此Interceptor捕獲。
假設MyInterceptor的實現如下:
public class MyInterceptor implements Interceptor{ … public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException{ if(entity instanceof TUser){ System.out.println(“User to be saved=>”+((TUser)entity).getName()); } return false; } … }
執行:
SessionFactory sessionFactory = cofig.buildSessionFactory(); Interceptor it = new MyInterceptor(); session = sessionFactory.openSession(it); TUser user = new TUser(); user.setName(“Erica”); Transaction tx= session.beginTransaction(); session.save(user); tx.commit(); session.close();
觀察屏幕日誌:
可以看到,MyInterceptor.onSave方法在Hibernate執行庫表insert操作之前調用。
通過這個例子可以發現,Interceptor實際上覆蓋了Lifecycle接口的功能,且具備更少的侵入性(實體類無需與任何Hibernate原生接口綁定)。
Interceptor典型應用
“數據稽覈”功能是不少關鍵系統的必備功能。
何謂“數據稽覈”?即針對關鍵信息及其變更歷史進行審查,作爲業務跟蹤的基礎依據。
那麼如何實現關鍵信息更新操作的記錄。顯然這並不是個非常困難的技術問題。最常見的方式就是在各個業務邏輯單元中通過編碼進行記錄。
回顧上面關於Interceptor的探討,我們可以發現,對於基於Hibernate的應用而言,通過Interceptor實現數據稽覈功能也許是最自然的一種方式。
以t_user表操作爲例,我們來看看基於Interceptor的實現方式。
Interceptor接口中,與數據更新操作相關的攔截方法有onSave和onFlushDirty。其中onSave方法對應insert操作,onFlushDirty對應update操作。
那麼,我們只要針對這兩個操作實現對應的攔截器方法,並在此方法中對更新歷史進行保存,那麼所有關於t_user表的修改操作都將被記錄在案。
這種實現方式通過集中化的記錄機制,避免了各業務邏輯單元中通過編碼進行操作記錄的可能缺漏,提供了最爲全面的數據跟蹤機制。另一方面,也避免了稽覈邏輯在業務邏輯中分散,最大程度上避免了重複代碼的出現。
假設“歷史記錄”實體機構如下:
public class TAuditLog implements Serializable{ private Integer id; private String user; private String action; private String entityName; private String comment; private Long logtime; … }
對應的Interceptor實現如下:
public class MyInterceptor implements Interceptor{ private Session session; private String userID; private Set insertSet = new HashSet(); private Set updateSet = new HashSet(); public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException{ if(entity instanceof TUser){ insertSet.add(entity); } return false; } public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException{ if(entity instanceof TUser){ updateSet.add(entity); } return false; } public void postFlush(Iterator entities) throws CallbackException{ try{ if(insertSet.size()>0){ AuditDAO.doLog(“insert”, userID, insertSet, session.connection()); } if(updateSet.size()>0){ AutitDAO.doLog(“update”, userID, updateSet, session.connection()); } }catch(HibernateException e){ e.printStackTrace(); } } … }
可以看到,在onSave、onFlushDirty方法中,我們對發生改變的數據對象進行了記錄,並在postFlush方法中將所有這些操作記錄到庫表中。
對應的Session級代碼如下:
SessionFactory sessionFactory = cofig.buildSessionFactory(); MyInterceptor it = new MyInterceptor(); session = sessionFactory.openSession(it); it.setSession(session); it.setUserID(“CurrentUser”); TUser user = new TUser(); user.setName(“Erica”); Transaction tx = session.beginTransaction(); session.save(user); tx.commit(); session.close();
AuditDAO.doLog方法在這裏實現了操作日誌記錄的功能,但我們注意到,這裏並沒有直接利用當前的session,而是轉而利用了當前Session的JDBC Connection引用。session.connection()
之前曾說過Lifecycle和Interceptor中都不能調用當前Session進行操作,Lifecycle和Interceptor接口中定義的方法,都將由當前Session負責調用,而如果在這些方法中又調用了當前Session進行持久化操作,則將導致Session內部狀態的混亂。
很容易得到的一個思路就是在AuditDAO中獲取一個新的Session實例完成持久化操作。顯然這個思路邏輯上完全正確,但是從性能方面考慮,一個操作需要兩個Session完成,也就意味着需要同時獨佔兩個數據庫連接,這對併發量較大的系統來說可能有點奢侈。
既然當前Session實例無法重用。重用當前Session的數據庫連接多少能減少一點性能損耗。
於是有了以下代碼:
public static void doLog(String action, String userID, set modifySet, Connection connection){ Session tempSession = HibernateUtil.getSessionFactory.openSession(connection); try{ Iterator it = modifySet.iterator(); while(it.hasNext()){ TUser user = (TUser)it.next(); TAuditLog auditLog = new TAuditLog(); auditLog.setUserID(userID); auditLog.setAction(action); auditLog.setComment(user.toString()); auditLog.setLogTime(getCurrentTime()); tempSession.save(auditLog); } tempSession.flush(); }catch(Exception ex){ throw new CallbackException(ex); }finally{ try{ tempSession.close(); }catch(HibernateException ex){ throw new CallbackException(ex); } } … }
這樣,通過SessionFactory.openSession(Connection dbconn)方法,依託當前Session的JDBC Connection,我們創建了一個臨時Session實例用於保存操作記錄。
值得注意的是,在AuditDAO.doLog方法裏,我們無需再啓動事務,原因就在於臨時Session中的JDBC Connection是與執行Interceptor的Session所共享的,而在此JDBCConnection上事務已經在外圍邏輯代碼中啓動。
同樣的道理,由於共享JDBCConnection,我們也無需執行close操作。
Interceptor提供了非侵入性的攔截器實現機制。基於這種機制,我們可以以更加靈活的方式實現傳統數據邏輯中的一些特殊需求。在實際開發中,我們可以根據自己的需求進行相應的調整。
在Hibernate3中,提供了基於時間監聽Listener機制的攔截器實現。這種方式可以提供比Interceptor更加清晰合理的實現模型。由於實現方法大同小異,這裏不再贅述,大家可自行評判。