每次修改mybatis的sql脚本后,都要重启,因为mybatis的mapper文件默认只在启动时加载到缓存,改动后不会自动加载,于是研究了下mybatis配置文件的加载,分享如下:
实现思路:使用定时器定时扫描mapper文件的改动,如果有改动则调用mapper文件的加载方法XMLMapperBuilder.parse()。
一.写一个重新加载mapper文件的java类
首先需要构建一个sqlSessionFactory对象,并指定mybatis的Configuration.xml配置文件路径,之后定时扫描并判断mapper文件是否有改动,如果有改动则重新加载,
package zttc.itat.user.utils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* mybatis的mapper文件有改动时,进行重新加载
* @author ycblus
*
*/
public class SqlSessionCache {
private Logger log = Logger.getLogger(SqlSessionCache.class);
private Resource[] mapperLocations;
private String packageSearchPath = "classpath*:zttc/itat/user/mapper/*.xml";
SqlSessionFactory sqlSessionFactory;
Configuration configuration;
private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化
{
String resource = "Configuration.xml";
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) ;
configuration = this.sqlSessionFactory.getConfiguration(); //扫描文件
} catch (IOException e) {
e.printStackTrace();
}
}
public void refreshMapper() throws Exception{
try {
try {
this.scanMapperXml();
} catch (IOException e) {
log.error("packageSearchPath扫描包路径配置错误");
return;
}
Runnable runnable = new Runnable() {
public void run() {
// task to run goes here
try {
// 判断是否有文件发生了变化
if (isChanged()) {
// 清理
this.removeConfig(configuration);
// 重新加载
for (Resource configLocation : mapperLocations) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(),
configuration, configLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
log.info("mapper文件[" + configLocation.getFilename() + "]加载成功");
} catch (IOException e) {
log.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
continue;
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/** * 清空Configuration中几个重要的缓存 * @param configuration * @throws Exception */
private void removeConfig(Configuration configuration) throws Exception {
Class<?> classConfig = configuration.getClass();
clearMap(classConfig, configuration, "mappedStatements");
clearMap(classConfig, configuration, "caches");
clearMap(classConfig, configuration, "resultMaps");
clearMap(classConfig, configuration, "parameterMaps");
clearMap(classConfig, configuration, "keyGenerators");
clearMap(classConfig, configuration, "sqlFragments");
clearSet(classConfig, configuration, "loadedResources");
}
/** * 判断文件是否发生了变化 * @param resource * @return * @throws IOException */
boolean isChanged() throws IOException {
boolean flag = false;
for (Resource resource : mapperLocations) {
String resourceName = resource.getFilename();
boolean addFlag = !fileMapping.isEmpty() && !fileMapping.containsKey(resourceName);// 此为新增标识
// 修改文件:判断文件内容是否有变化
Long compareFrame = fileMapping.get(resourceName);
long lastFrame = resource.contentLength() + resource.lastModified();
boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识
fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
// 新增或是修改时,存储文件
if(addFlag || modifyFlag) {
flag = true;
}
}
return flag;
}
};
ScheduledExecutorService service = Executors
.newSingleThreadScheduledExecutor();
// 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间
service.scheduleAtFixedRate(runnable, 1, 10, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setPackageSearchPath(String packageSearchPath) {
this.packageSearchPath = packageSearchPath;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/** * 扫描xml文件所在的路径 * @throws IOException */
private void scanMapperXml() throws IOException {
this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
}
/** * 清空Configuration中几个重要的缓存 * @param configuration * @throws Exception */
private void removeConfig(Configuration configuration) throws Exception {
Class<?> classConfig = configuration.getClass();
clearMap(classConfig, configuration, "mappedStatements");
clearMap(classConfig, configuration, "caches");
clearMap(classConfig, configuration, "resultMaps");
clearMap(classConfig, configuration, "parameterMaps");
clearMap(classConfig, configuration, "keyGenerators");
clearMap(classConfig, configuration, "sqlFragments");
clearSet(classConfig, configuration, "loadedResources");
}
@SuppressWarnings("rawtypes")
private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
Field field = classConfig.getDeclaredField(fieldName);
field.setAccessible(true);
Map mapConfig = (Map) field.get(configuration);
mapConfig.clear();
}
@SuppressWarnings("rawtypes")
private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
Field field = classConfig.getDeclaredField(fieldName);
field.setAccessible(true);
Set setConfig = (Set) field.get(configuration);
setConfig.clear();
}
public static void main(String[] args) {
HashMap<String, Long> fileMapping = new HashMap<String, Long>();
boolean f = !fileMapping.isEmpty() && !fileMapping.containsKey("Hello.xml");
System.out.println(f);
}
}
二.Configuration.xml配置文件
这个配置文件在这里只作为扫描配置使用,可以看到其他配置都没有,因为我是在spring里面统一配置了
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <mapper resource="zttc/itat/user/mapper/TUserMapper.xml"/> <mapper resource="zttc/itat/user/mapper/TStoreMapper.xml"/> </mappers> </configuration>
三.写一个Servlet,在服务器启动时调用前面写好的定时加载类。
代码如下:
package zttc.itat.user.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.log4j.Logger;
import zttc.itat.user.utils.SqlSessionCache;
/**
* Servlet implementation class MapperReloadServlet
*
* when mybatis files changed,reload them
*/
public class MapperReloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public MapperReloadServlet() {
super();
// TODO Auto-generated constructor stub
}
public void init()throws ServletException
{
Logger logger = Logger.getLogger(this.getClass());
logger.info("The mapper reload timer starting... ");
try {
new SqlSessionCache().refreshMapper();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
此外,需在web.xml中加入这个servlet类,添加代码如下:
<servlet>
<servlet-name>MapperReloadServlet</servlet-name>
<servlet-class>zttc.itat.user.servlet.MapperReloadServlet</servlet-class>
<load-on-startup>7</load-on-startup>
</servlet>
这样每次当mapper文件有改动时,就会重新加载。不过会把所有的mapper文件重新加载一遍,如果需要对指定文件进行加载也是可以的,需要修改下重新加载的类。
另外,服务器应该配置为reloadable="true",否则改动了东西也不会自动部署到服务上去的,但是这样有一个风险,就是reloadable="true"以后,如果服务器主机性能差的话,容易挂掉!。
四.改动mapper文件后的效果图