Hibernate攔截器(Interceptor)與事件監聽器(Listener)
攔截器(Intercept):與Struts2的攔截器機制基本一樣,都是一個操作穿過一層層攔截器,每穿過一個攔截器就會觸發相應攔截器的事件做預處理或善后處理。
監聽器(Listener):其實功能與攔截器是相似的,但它實現原理不同,它是為每一個事件注冊一個或多個監聽器,一旦事件發生,則事件源通知所有監聽該事件的監聽器,然后監聽器處理通知(觀察者模式)。
攔截器
Hibernate為我們提供了實現攔截器的接口org.hibernate.Interceptor,它里面提供了許多攔截事件。通常不需要實現這個接口,因為我們實現自己的攔截器不可能每一個事件都是必須的。所以Hibernate為我們提供了org.hibernate.Interceptor接口的一個空實現類org.hibernate.EmptyIntercept,通常情況下我們只需繼承這個空實現類,Override需要的事件方法即可。
攔截器的工作原理簡易示意圖:
設置攔截器后,相應的操作都會先穿過一層層相應的攔截器,讓攔截器執行預處理或善后處理。
攔截器使用實例:
創建攔截器:
public class AutoUpdateTimeInterceptor extends EmptyInterceptor
{
private static final long serialVersionUID = -8597658125309889388L;
/*
* entity - POJO對象
* id - POJO對象的主鍵
* state - POJO對象的每一個屬性所組成的集合(除了ID)
* propertyNames - POJO對象的每一個屬性名字組成的集合(除了ID)
* types - POJO對象的每一個屬性類型所對應的Hibernate類型組成的集合(除了ID)
*/
@Override
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types)
{
if (entity instanceof People)
{
for (int index=0;index<propertyNames.length;index++)
{
/*找到名為"checkinTime"的屬性*/
if ("checkinTime".equals(propertyNames[index]))
{
/*使用攔截器將People對象的"checkinTime"屬性賦上值*/
state[index] = new Timestamp(new Date().getTime());
return true;
}
}
}
return false;
}
}
場景類
public class Client
{
public static void main(String[] args)
{
/*為Session添加攔截器*/
Session session = HibernateUtil.getSessionFactory().openSession(new AutoUpdateTimeInterceptor());
Transaction tx = null;
try
{
tx = session.beginTransaction();
People people = new People();
people.setName("zhangsan");
session.save(people);
tx.commit();
}
catch (Exception e)
{
if(tx!=null)
{
tx.rollback();
}
e.printStackTrace();
}
finally
{
session.close();
}
}
}
場景類中並沒有顯示的設置了people對象的"checkinTime"的屬性值,啟動該場景類代碼,現在來查看數據庫信息:
可以看到checkin_time這列屬性依然被賦值了,說明該賦值操作是攔截器幫助我們完成的。使用攔截器的時候需要注意攔截器的返回值,我以前一直以為攔截器的返回值會控制一個操作是否可以繼續,通過實驗發現,即使返回false操作也會繼續執行的,只是返回false的話,攔截器的所有設置都是無效的,不會反應到數據庫中。
返回false:
public class AutoUpdateTimeInterceptor extends EmptyInterceptor
{
private static final long serialVersionUID = -8597658125309889388L;
/*
* entity - POJO對象
* id - POJO對象的主鍵
* state - POJO對象的每一個屬性所組成的集合(除了ID)
* propertyNames - POJO對象的每一個屬性名字組成的集合(除了ID)
* types - POJO對象的每一個屬性類型所對應的Hibernate類型組成的集合(除了ID)
*/
@Override
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types)
{
if (entity instanceof People)
{
for (int index=0;index<propertyNames.length;index++)
{
/*找到名為"checkinTime"的屬性*/
if ("checkinTime".equals(propertyNames[index]))
{
/*使用攔截器將People對象的"checkinTime"屬性賦上值*/
state[index] = new Timestamp(new Date().getTime());
// return true;
}
}
}
return false;
}
}
查看插入的數據:
可以看到數據依然保存到數據庫中了,但攔截器的操作並沒有反映到數據庫中,攔截器的操作是無效的。
但是比較奇怪的是Console輸出的SQL語句:
Hibernate:
insert
into
people
(name, checkin_time, id)
values
(?, ?, ?)
Hibernate:
update
people
set
name=?,
checkin_time=?
where
id=?
居然多了一條Update語句,我使用了p6spy顯示了這兩條SQL語句的綁定值:
1327304507491|0|0|statement|insert into people (name, checkin_time, id) values (?, ?, ?)|insert into people (name, checkin_time, id) values ('zhangsan', '2012-01-23 15:41:47.45', '402881e53509837f0135098380370001')
1327304507491|0|0|statement|update people set name=?, checkin_time=? where id=?|update people set name='zhangsan', checkin_time='' where id='402881e53509837f0135098380370001'
可以看到,攔截器的操作會直接反映到數據庫中的,但是如果返回值是false的話,Hibernate會產生一條Update SQL語句將攔截器的操作結果取消了。
最后,Hibernate的攔截器有兩種設置方式,一種是使用sessionFactory.openSession(Interceptor interceptor),這樣的攔截器只會針對該session有效,又叫做局部攔截器。另一種是使用Configuration的setInterceptor(Interceptor interceptor)方法設置,這樣的攔截器對每一個session都有效,又稱之為全局攔截器。
事件監聽器
基本上,Session接口的每個方法都有相對應的事件。比如 LoadEvent,FlushEvent,等等(查閱XML配置文件的DTD,以及org.hibernate.event包來獲得所有已定義的事件的列表)。當某個方法被調用時,Hibernate Session會生成一個相對應的事件並激活所有配置好的事件監聽器。系統預設的監聽器實現的處理過程就是被監聽的方法要做的(被監聽的方法所做的其實僅僅是激活監聽器, “實際”的工作是由監聽器完成的)。不過,你可以自由地選擇實現一個自己定制的監聽器(比如,實現並注冊用來處理處理LoadEvent的LoadEventListener接口), 來負責處理所有的調用Session的load()方法的請求。
在定義自己的事件監聽器時,其實不需要實現XXXListener接口,Hibernate為了方便我們定義事件監聽器,已經為每個事件監聽器接口實提供了一個默認的實現。在org.hibernate.event.def包下面可以找到Hibernate為我們提供的默認實現,我們只需要繼承這些默認實現,在其基礎上添加我們自定義的功能即可。
事件監聽器的簡單示意圖:
當某個方法被調用時,Hibernate Session會生成一個相對應的事件並激活所有配置好的事件監聽器。
事件監聽器使用:
創建事件監聽器:
public class SaveOrUpdateListener extends DefaultSaveOrUpdateEventListener
{
private static final long serialVersionUID = 7496518453770213930L;
/*監聽保存或更新事件*/
@Override
public void onSaveOrUpdate(SaveOrUpdateEvent event)
{
People people = null;
if(event.getObject() instanceof People)
{
people = (People)event.getObject();
}
people.setCheckinTime(new Timestamp(new Date().getTime()));
System.out.println("invoke!");
/*一定要調用父類提供的功能,不然就和繼承接口一樣了*/
super.onSaveOrUpdate(event);
}
}
通過hibernate.cfg.xml配置文件將事件監聽器配置到Hibernate中:
第一種配置方式:
<event type="save-update">
<listener class="com.suxiaolei.hibernate.listener.SaveOrUpdateListener"/>
</event>
第二種配置方式:
<listener class="com.suxiaolei.hibernate.listener.SaveOrUpdateListener" type="save-update"/>
兩種配置方式產生的效果都是一樣的。只是一個以"事件"為主,一個以"監聽器"為主。type是指定監聽事件的類型,class指定監聽器的實現類,一個事件可以有多個監聽器。type有許多取值,下表列出了所有type的值:
上面列表每一個選項對應着一個特定的事件。
場景類:
public class Client
{
public static void main(String[] args)
{
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
People people = new People();
people.setName("lisi");
session.saveOrUpdate(people);
tx.commit();
}
catch (Exception e)
{
if(tx!=null)
{
tx.rollback();
}
e.printStackTrace();
}
finally
{
session.close();
}
}
}
people對象依然沒有設置checkinTime屬性,運行程序后,查看數據庫:
可以看到,checkin_time字段的依然賦值了,說明我們配置的事件監聽器生效了。
使用事件監聽器我發現需要注意父類行為的順序,例如:
public void onSaveOrUpdate(SaveOrUpdateEvent event)
{
People people = null;
if(event.getObject() instanceof People)
{
people = (People)event.getObject();
}
people.setCheckinTime(new Timestamp(new Date().getTime()));
System.out.println("invoke!");
/*一定要調用父類提供的功能,不然就和繼承接口一樣了*/
super.onSaveOrUpdate(event);
}
public void onSaveOrUpdate(SaveOrUpdateEvent event)
{
/*一定要調用父類提供的功能,不然就和繼承接口一樣了*/
super.onSaveOrUpdate(event);
People people = null;
if(event.getObject() instanceof People)
{
people = (People)event.getObject();
}
people.setCheckinTime(new Timestamp(new Date().getTime()));
System.out.println("invoke!");
}
例如上面的順序,雖然最后產生的效果一致,但是第二種順序會多產生一條update語句,有可能會產生性能問題,所以在使用事件監聽器的時候需要多加注意。