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

来源:互联网 发布:java开发分几种 编辑:程序博客网 时间:2024/05/16 10:04

大学毕业之后到公司,就是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》 来启动,一个很方便的开发环境就做好了。

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

原创粉丝点击