大学毕业之后到公司,就是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就会报错的原因。
好了!代码看清楚了,接下来就看我们怎么来修改代码了。
主要就是SqlmapClientFactoryBean、SqlMapExecutorDelegate、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》 来启动,一个很方便的开发环境就做好了。
等过几天有空,会把这个空的框架搭起来,方便大家使用。
本文介绍了一种方法,通过修改SqlMapClientFactoryBean、SqlMapExecutorDelegate和SqlMapClientImpl,实现手动刷新ibatis的sqlmap配置,从而避免每次修改后都需要重启应用。作者提供了实现该功能的四个文件下载链接,并建议结合之前的《java webapp嵌入jetty》文章,创建一个便捷的开发环境。
196

被折叠的 条评论
为什么被折叠?



