Hibernate深入淺出(十一)Hibernate回調與攔截機制(LifeCycle、Validateable、Interceptor)

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更加清晰合理的實現模型。由於實現方法大同小異,這裏不再贅述,大家可自行評判。

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