现在的位置: 首页 > 综合 > 正文

实现ibatis手动控制加载sqlmap文件,终于不用重启应用了

2013年11月21日 ⁄ 综合 ⁄ 共 4969字 ⁄ 字号 评论关闭

大学毕业之后到公司,就是velocity+springMVC+srping+ibatis,所以一直在用ibatis做持久层,其他的几个框架也都是稍有了解。

好了屁话少说进入正题:之前有写一篇文章 《java webapp嵌入jetty》 为的就是能快速开发,直接在eclipse做debug很是方便。但是呢,用了ibatis,在sqlmap中写了sql,如果每次修改了sqlmap,那么就要每次都重启应用才行,使用起来很是蛋疼,如果项目小,也就是分分钟的事,如果工程足够大,那么重启一次就够受了!

于是就在考虑,能否每次手动来控制ibatis重新加载已经修改好的sqlmap呢?这就可以不用重启了。

答案肯定是可以的~ 毕竟在spring做bean初始化的时候就会加载ibatis的sqlmap,所以只要我们找到对应的代码,然后做出一些调整就可以实现重新加载了。

OK,看代码~~

首先SqlmapClientFactoryBean是spring给ibatis做的适配,那么我们就要从这个类看起。

可以看到ibatis的sqlmapClient是通过configParser返回的。

在new SqlMapConfigParser()的时候,由于以下的引用关系。

SqlMapConfigParser--XmlParserState--SqlMapConfiguration---SqlMapExecutorDelegate

SqlMapConfigParser将上面的类初始化,并且在SqlMapConfiguration初始化的时候,将new的SqlMapExecutorDelegate赋值给SqlMapClientImpl

而SqlMapExecutorDelegate才是真正的执行代理类,并且所有的sqlmap解析都被它保存着。

解析过程不详细表述了,感兴趣的同学可以看源码。

最终通过SqlMapExecutorDelegate.addMappedStatement方法,把解析出来的sqlmapstatement保存到map中sqlmap的id为key值

代码如上,可以看到每次添加会验证id是否重复。这也就是为啥一个sql中如果有两个相同id就会报错的原因。


好了!代码看清楚了,接下来就看我们怎么来修改代码了。

主要就是SqlmapClientFactoryBeanSqlMapExecutorDelegate、SqlMapClientImpl三个类

我们想重新刷新,那么就要操作SqlMapExecutorDelegate中的map,然后ibatis和spring的接口只有SqlMapClientImpl这个类,并且spring的适配是SqlmapClientFactoryBean

于是乎,思路就来了,只要我们重写SqlmapClientFactoryBean让它返回我们重写过的SqlMapClientImpl,在SqlMapClientImpl添加刷新的方法,然后通过SqlMapClientImpl来调用我们代理的SqlMapExecutorDelegate就可以实现重新加载了~

代码如下:

SqlmapClientFactoryBean

在添加返回前,声明出我们重写的对sqlmapClientImpl的代理

package com.h2o3.right.dal.platform;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Properties;

import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser;
import com.ibatis.sqlmap.engine.builder.xml.XmlParserState;
import com.ibatis.sqlmap.engine.config.SqlMapConfiguration;
import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;

/**
 * sqlMapClientImpl的代理类。
 * 
 * <pre>
 * 		这个类是所有sql操作的入口,并且存放了SqlMapExecutorDelegate这个sql执行器的代理类。
 * 		并且这个类是在spring中的入口。
 * 		
 * 		所以代理这个类,将自己的delegate设置进去,并且反射自己的delegate到SqlMapConfiguration中
 * 		保证重新加载sqlmap的时候,操作的是自己的delegate,这样就不会触发原生delegate中的重复判断。
 * </pre>
 * 
 * @author yuezhen
 * 
 */
public class H2o3SqlMapClientImpl extends SqlMapClientImpl {

	/**
	 * Delegate for SQL execution
	 */
	public H2o3SqlMapExecutorDelegate h2o3Delegate;

	/**
	 * sqlmap的路径
	 */
	private Resource[] configLocations;

	/**
	 * config转换器
	 */
	private SqlMapConfigParser configParser;

	/**
	 * sqlmapclient配置的properties,spring传入
	 */
	private Properties properties;

	/**
	 * 构造方法。
	 * 
	 * @param client
	 * @param configLocations
	 * @param configParser
	 * @param properties
	 */
	public H2o3SqlMapClientImpl(SqlMapClient client,
			Resource[] configLocations, SqlMapConfigParser configParser,
			Properties properties) {
		super(new H2o3SqlMapExecutorDelegate(((ExtendedSqlMapClient) client)
				.getDelegate()));
		this.h2o3Delegate = (H2o3SqlMapExecutorDelegate) this.delegate;
		this.configLocations = configLocations;
		this.configParser = configParser;
		this.properties = properties;
		relfectDelegate();
	}

	/**
	 * 重新刷新。
	 * 
	 * @throws IOException
	 */
	public void fresh() throws IOException {

		// 调用configParser来重新加载
		for (Resource configLocation : configLocations) {
			InputStream is = configLocation.getInputStream();
			try {
				configParser.parse(is, properties);
			} catch (RuntimeException ex) {
				throw new NestedIOException("Failed to parse config resource: "
						+ configLocation, ex.getCause());
			}
		}
	}

	/**
	 * 反射将自己的delegate,反射到SqlMapConfiguration中。
	 */
	public void relfectDelegate() {
		try {
			Field stateField = this.configParser.getClass().getDeclaredField(
					"state");
			stateField.setAccessible(true);
			XmlParserState state = (XmlParserState) stateField
					.get(this.configParser);
			Field configFiled = state.getClass().getDeclaredField("config");
			configFiled.setAccessible(true);
			SqlMapConfiguration configField = (SqlMapConfiguration) configFiled
					.get(state);
			Field clientField = configField.getClass().getDeclaredField(
					"client");
			clientField.setAccessible(true);
			clientField.set(configField, this);
			Field delegateField = configField.getClass().getDeclaredField(
					"delegate");
			delegateField.setAccessible(true);
			delegateField.set(configField, this.delegate);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public H2o3SqlMapExecutorDelegate getMydelegate() {
		return h2o3Delegate;
	}
}

代理中做保存自己所写的SqlMapExecutorDelegate代理,并且把自己的SqlMapExecutorDelegate通过反射的方式放到SqlMapConfiguration中,以保证从新加载的时候用的是我们的SqlMapExecutorDelegate。

并且实现刷新的方法:将传入的sqlmap路径,在调用一次parser的解析。

SqlMapExecutorDelegate方法中,由于这个类太多不可见,没办法,只能很土的方法,一是写个代理,代理他所有的方法,并且重写addmapping的方法。二是通过反射把所有的属性反射进去。然后重写addmapping。

这里采用第一种方案

将以前的重复判断给去掉。

最后,web层做一个刷新的controller,将sqlmapclient注入进去,然后通过refesh方法来进行刷新即可。

这样,每次修改了sql之后,访问一下refersh.htm就重新加载了sqlmap!!

这里只是实现通过url手动刷新,如果大家感兴趣,还可以设置,没法访问db都去刷新,方法类似,都是修改SqlMapExecutorDelegate这个类的不同调用方法即可。

已经测试通过,ibatis基于org/apache/ibatis/ibatis-sqlmap/2.3.4.726/ibatis-sqlmap-2.3.4.726-sources.jar

附上四个文件下载链接 http://download.csdn.net/detail/lywybo/5613303

H2o3SqlMapClientFactoryBean.java

H2o3SqlMapClientImpl.java

H2o3SqlMapExecutorDelegate.java

RefershController.java

使用的时候将文件放到代码中,然后配置sqlmapclient为H2o3SqlMapClientFactoryBean即可。

然后配合上篇文章 《java webapp嵌入jetty》 来启动,一个很方便的开发环境就做好了。

等过几天有空,会把这个空的框架搭起来,方便大家使用。

抱歉!评论已关闭.