實現ibatis手動控制加載sqlmap文件,終於不用重啓應用了

大學畢業之後到公司,就是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》 來啓動,一個很方便的開發環境就做好了。

等過幾天有空,會把這個空的框架搭起來,方便大家使用。

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