CIM S2Hibernate自定义的Interceptor

Seasar提供了S2Hibernate来封装hibernate。但是在公司的CIM系统中,我们又自定义了一套自己的S2hibernate规范,封装原有的S2hibernate类库。研究了一下它的实现,做点笔记以防忘记。

(注:关于Seasar框架,请查阅 http://dhongwu.iteye.com/admin/blogs/1949725; 关于S2hibernate,请查阅官方文档 http://s2hibernate.seasar.org/ja/s2hibernate.html。很遗憾暂时只有日文版本。)

CIM中S2hibernate的注入声明是在s2hibernate3.dicon中。
  
...
<component name="s2SessionFactory" class="jp.co.worksap.common.s2.S2SessionFactoryImpl" >
...
<component class="jp.co.worksap.common.s2.CwfDaoMetaDataFactoryImpl"/>
<component class="jp.co.worksap.common.s2.HistoricalSaveInterceptor"/>

<component name="readOnly" class="org.seasar.hibernate3.interceptor.ReadOnlySessionInterceptor"/>

<component name="interceptor" class="jp.co.worksap.common.s2.S2HibernateDaoInterceptor"/>

<component name="s2H3Customizer" class="org.seasar.framework.container.customizer.AspectCustomizer">
	<initMethod name="addInterceptorName">
		<arg>"s2hibernate.interceptor"</arg>
	</initMethod>
</component>
...

可以看到,最后得到一个s2H3Customizer,再把这个customizer添加到customize.dicon的DaoCustomizer中,随着Seasar框架的启动就把定义的AOP加载到Dao类上。(更多关于Seasar框架启动的细节,请查阅 http://dhongwu.iteye.com/admin/blogs/1949725)。

直接的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,请阅读官方文档 http://s2container.seasar.org/2.4/en/tx.html),第二层键值就是每个线程自己维护的Date变量转成形如yyyy/MM/dd后的字符串。

那么实际上对于每一个JTA Transaction, 内部都维护两个session。一个是Date为当天值的session,一个是Date为NULL的session(以下简称NoHist-session)。前者用于针对带有baseDate的查询命令的执行,后者则是针对不含baseDate的查询命令以及其它的增删改命令。再回顾S2HibernateDaoInterceptor的invoke函数,在每一条增删改命令完成以后,都会让相应的NoHist-session flush一遍(似乎这样的效率不高,可以有改进的空间)

猜你喜欢

转载自dhongwu.iteye.com/blog/1955906