天天看點

CIM S2Hibernate自定義的Interceptor

Seasar提供了S2Hibernate來封裝hibernate。但是在公司的CIM系統中,我們又自定義了一套自己的S2hibernate規範,封裝原有的S2hibernate類庫。研究了一下它的實作,做點筆記以防忘記。

(注:關于Seasar架構,請查閱[url]http://dhongwu.iteye.com/admin/blogs/1949725[/url]; 關于S2hibernate,請查閱官方文檔[url]http://s2hibernate.seasar.org/ja/s2hibernate.html[/url]。很遺憾暫時隻有日文版本。)

CIM中S2hibernate的注入聲明是在s2hibernate3.dicon中。

可以看到,最後得到一個s2H3Customizer,再把這個customizer添加到customize.dicon的DaoCustomizer中,随着Seasar架構的啟動就把定義的AOP加載到Dao類上。(更多關于Seasar架構啟動的細節,請查閱 [url]http://dhongwu.iteye.com/admin/blogs/1949725[/url])。

直接的AOP就是類S2HibernateDaoInterceptor。在S2HibernateDaoInterceptor的invoke函數中,有:

public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		if (!MethodUtil.isAbstract(method)) {
			return invocation.proceed();
		}
		Class daoClass = getTargetClass(invocation);
		CwfDaoMetaDataImpl dmd = (CwfDaoMetaDataImpl) hibernateDaoMetaDataFactory_.getDaoMetaData(daoClass);

		Date historyDate = dmd.getFilterBaseDate(invocation);
		if (historyDate != null) {
			holder.set(historyDate);
		} else {
			holder.set(S2SessionFactoryImpl.NULLDATE);
		}
		try{
			HibernateCommand cmd = dmd.getHibernateCommand(method.getName());
			Object ret = cmd.execute(invocation.getArguments());

			if(!dmd.isSelectMethod(invocation.getMethod().getName())){

				if(!s2fac.getSessionNoHist().getFlushMode().equals(FlushMode.MANUAL)){
					s2fac.getSessionNoHist().flush();
				}
			}

			return ret;
		}finally{
			holder.remove();
		}
	}
           

首先判斷目前調用的函數是否為抽象函數(即沒有函數實作的body)。S2hibernate原本的理念就是将Dao中的各種增删查改(以下簡稱CRUD)函數聲明成抽象函數,具體内容通過AOP來實作。 CIM這裡也沿用這一理念。接下來獲得目前Dao的相關metadata。hibernateDaoMetaDataFactory_通過S2hibernate3.dicon入獲得,實際上為CwfDaoMetaDataFactoryImpl類。該getDaoMetaData函數為:

public synchronized HibernateDaoMetaData getDaoMetaData(Class daoClass) {
		String key = daoClass.getName();
		HibernateDaoMetaData dmd = (HibernateDaoMetaData) daoMetaDataCache_
				.get(key);
		if (dmd != null) {
			return dmd;
		}
		dmd = new CwfDaoMetaDataImpl(sessionFac, daoClass);
		daoMetaDataCache_.put(key, dmd);
		return dmd;
	}
           

先檢視是否已經定義過該類的metadata,沒有的話就去建立。在CwfDaoMetaDataImpl的構造函數中,有:

public CwfDaoMetaDataImpl(S2SessionFactory arg0, Class daoClass) {
		super(arg0, daoClass);

		sessionFactory=arg0;

		daoClass_ = daoClass;
		daoBeanDesc_ = BeanDescFactory.getBeanDesc(daoClass_);
		Field beanField = daoBeanDesc_.getField(BEAN_KEY);
		beanClass_ = (Class) FieldUtil.get(beanField, null);

		commandMap.put("listCommand", new CwfCommand(sessionFactory,beanClass_));
		commandMap.put(METHOD_SIMPLE_SEARCH, new SimpleSearchCommand(sessionFactory,beanClass_));
		commandMap.put(METHOD_GET_COUNT, new GetCountCommand(sessionFactory,beanClass_));
		setupHibernateCommand();
	}
           

super(arg0, daoClass)做的就是原先S2Hibernate的本職工作,包括初始化Dao的各種CRUD函數,并添加到commandMap中。然後這裡人工添加了3個函數listCommand, simpleSearch, getCount,這個是為了和cim中的自定義元件sstable相容(關于sstable日後再詳細讨論),提供為sstable服務的接口函數。最後setupHibernateCommand()是檢視Dao中聲明的select函數,檢視他們是否聲明了一個"baseDate"參數。如果聲明了就把相應的sql查詢定義為HistoricalHqlQueryCommand。這也是我們要自定義S2hibernate的最根本目的,就是為了添加這一層封裝。

多說一句,HistoricalHqlQueryCommand繼承自HibernateCommand,這個是S2Hibernate提供的封裝SQL行為的函數,通過調用它的execute就可以執行相應的SQL指令。

重新回到S2HibernateDaoInterceptor的invoke函數,在獲得Dao的相關metadata後,就可以通過metadata中的commandMap獲得目前調用函數名對應的HibernateCommand,調用execute來執行SQL指令。如果目前HibernateCommand是我們上文提到的HistoricalHqlQueryCommand,則它的execute函數如下:

public Object execute(Object[] args) {
		Session session = getSession();

		Query query = session.createQuery(hqlQuery_);

		ArgsMetaData argsMeta = getArgsMeta();

		for (int i = 0; i < argsMeta.getArgsCount(); ++i) {

			Object value = argsMeta.getValue( args, i);
			String name = argsMeta.getArgument(i).getFieldName();
			if (value != null) {
				if (name.equals("firstResult") == true) {
					query.setFirstResult(((Integer) value).intValue());

				} else if (name.equals("maxResults") == true) {
					query.setMaxResults(((Integer) value).intValue());
				} else if (name.equals(CwfDaoMetaDataImpl.HIST_DATE) == true) {
					session.enableFilter("history").setParameter("date", (Date)value);
				} else {
					if( value instanceof List){
						query.setParameterList(name, (List)value);

					}else{
						query.setParameter(name, value);
					}
				}
			}
		}
		List ret = query.list();
		return getReturnObject(getMethod(),ret );
	}
           

重點是這一句

else if (name.equals(CwfDaoMetaDataImpl.HIST_DATE) == true) {
    session.enableFilter("history").setParameter("date", (Date)value);
}
           

HIST_DATE就是我們前文提及的字元串"baseDate",如果Dao的函數ARGS中聲明了"baseDate",則此時Hibernate開啟名為history的過濾器,并把過濾器condition中的date值設為該Dao函數傳入的Date。

多說一句,關于S2HibernateDaoInterceptor的invoke函數中的holder變量,是在s2hibernate3.dicon中注入的一個singleton的HistoryDateHolder對象。雖然是singleton變量,但是它實際上是一個ThreadLocal,是以每個線程有自己獨立的Date對象。它的作用主要就是在session管理上。在HibernateCommand中的execute函數中,getSession()調用s2hibernate3.dicon自定義的S2SessionFactoryImpl中的getSession()。該工廠維護了一個雙層SessionMap,第一層鍵值是線程目前的JTA transaction(關于seasar的JTA transaction AOP,請閱讀官方文檔[url]http://s2container.seasar.org/2.4/en/tx.html[/url]),第二層鍵值就是每個線程自己維護的Date變量轉成形如yyyy/MM/dd後的字元串。

那麼實際上對于每一個JTA Transaction, 内部都維護兩個session。一個是Date為當天值的session,一個是Date為NULL的session(以下簡稱NoHist-session)。前者用于針對帶有baseDate的查詢指令的執行,後者則是針對不含baseDate的查詢指令以及其它的增删改指令。再回顧S2HibernateDaoInterceptor的invoke函數,在每一條增删改指令完成以後,都會讓相應的NoHist-session flush一遍(似乎這樣的效率不高,可以有改進的空間)