MyBatis热加载插件网上也看到过,但都是全部重新加载,这样必然效率不是很高,而且也不支持多数据源,所以还是自己实现吧!
package com.myapp.core;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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.binding.MapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
/**
* mapper.xml增量热加载,修改mapper.xml不需要重启tomcat等容器,正式环境去掉
*/
public class XMLMapperLoader implements DisposableBean, InitializingBean, ApplicationContextAware {
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationContext applicationContext;
private ScheduledExecutorService executorService;
private Long initialDelay = 5L;
private Long period = 5L;
private boolean enabled = true;
public XMLMapperLoader(){
super();
logger.info(">>> XMLMapperLoader initialized...(MyBatis xml文件热部署模块初始完成,生产模式需要移除)");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void afterPropertiesSet() throws Exception {
if(enabled){
Field field = org.mybatis.spring.SqlSessionFactoryBean.class.getDeclaredField("mapperLocations");
field.setAccessible(true);
Map<String,org.mybatis.spring.SqlSessionFactoryBean> sqlSessionFactoryBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, org.mybatis.spring.SqlSessionFactoryBean.class);
if(sqlSessionFactoryBeans != null && sqlSessionFactoryBeans.size() > 0){
executorService = Executors.newScheduledThreadPool(sqlSessionFactoryBeans.size());
for(org.mybatis.spring.SqlSessionFactoryBean sqlSessionFactoryBean : sqlSessionFactoryBeans.values()){
Resource[] mapperLocations = (Resource[]) field.get(sqlSessionFactoryBean);
Scanner scanner = new Scanner(sqlSessionFactoryBean.getObject().getConfiguration(), mapperLocations);
executorService.scheduleAtFixedRate(scanner, initialDelay, period, TimeUnit.SECONDS);
}
}
}
}
public void setInitialDelay(Long initialDelay) {
this.initialDelay = initialDelay;
}
public void setPeriod(Long period) {
this.period = period;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setStrict(boolean strict) {
this.strict = strict;
}
class Scanner implements Runnable {
private Configuration configuration;
private Resource[] mapperLocations;
private HashMap<String, String> mapperFiles = new HashMap<String, String>();
private Set<?> loadedResources;
private Map<?,?> knownMappers;
private Collection<String> cacheNames;
private Collection<String> mappedStatementNames;
private Collection<String> parameterMapNames;
private Collection<String> resultMapNames;
private Collection<String> sqlFragmentNames;
private Collection<String> keyGeneratorNames;
public Scanner(Configuration configuration, Resource[] mapperLocations) {
this.configuration = configuration;
this.mapperLocations = mapperLocations;
loadedResources = getSetField(Configuration.class, configuration, "loadedResources");
knownMappers = getMapField(MapperRegistry.class, configuration.getMapperRegistry(), "knownMappers");
cacheNames = configuration.getCacheNames();
mappedStatementNames = configuration.getMappedStatementNames();
parameterMapNames = configuration.getParameterMapNames();
resultMapNames = configuration.getResultMapNames();
keyGeneratorNames = configuration.getKeyGeneratorNames();
sqlFragmentNames = configuration.getSqlFragments().keySet();
this.scan();
}
@Override
public void run() {
List<Resource> resources = this.getChangedXml();
if (resources != null && resources.size() > 0) {
this.reloadXML(resources);
}
}
public void reloadXML(List<Resource> resources){
for(Resource mapperLocation : resources){
refresh(mapperLocation);
}
}
private void refresh(Resource resource){
try {
XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(), new XMLMapperEntityResolver());
XNode context = xPathParser.evalNode("/mapper");
String namespace = context.getStringAttribute("namespace");
knownMappers.remove(Resources.classForName(namespace));
loadedResources.remove(resource.toString());
cacheNames.remove(namespace);
cleanMappedStatements(context.evalNodes("select|insert|update"), namespace);
cleanParameterMaps(context.evalNodes("/mapper/parameterMap"), namespace);
cleanResultMaps(context.evalNodes("/mapper/resultMap"), namespace);
cleanKeyGenerators(context.evalNodes("insert|update"), namespace);
cleanSqlElements(context.evalNodes("/mapper/sql"), namespace);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(), configuration, resource.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
System.err.println(">>> XML文件改变,重新加载\"namespace:" + namespace + "\"成功!");
} catch (Exception e) {
logger.error("Refresh IOException :"+e.getMessage());
} finally {
ErrorContext.instance().reset();
}
}
private Set<?> getSetField(Class<?> clazz, Object object, String fieldName){
try {
Field setField = clazz.getDeclaredField(fieldName);
setField.setAccessible(true);
return (Set<?>) setField.get(object);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private Map<?,?> getMapField(Class<?> clazz, Object object, String fieldName){
try {
Field setField = clazz.getDeclaredField(fieldName);
setField.setAccessible(true);
return (Map<?,?>) setField.get(object);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* 清理mappedStatements
*
* @param list
* @param namespace
*/
private void cleanMappedStatements(List<XNode> list, String namespace) {
for (XNode node : list) {
String id = node.getStringAttribute("id");
mappedStatementNames.remove(namespace + "." + id );
}
}
/**
* 清理parameterMap
*
* @param list
* @param namespace
*/
private void cleanParameterMaps(List<XNode> list, String namespace) {
for (XNode node : list) {
String id = node.getStringAttribute("id");
parameterMapNames.remove(namespace + "." + id);
}
}
/**
* 清理resultMap
*
* @param list
* @param namespace
*/
private void cleanResultMaps(List<XNode> list, String namespace) {
for (XNode node : list) {
String id = node.getStringAttribute("id", node.getValueBasedIdentifier());
resultMapNames.remove(id);
resultMapNames.remove(namespace + "." + id);
clearResultMaps(node, namespace);
}
}
private void clearResultMaps(XNode xNode, String namespace) {
for (XNode node : xNode.getChildren()) {
if ("association".equals(node.getName()) || "collection".equals(node.getName()) || "case".equals(node.getName())) {
if (node.getStringAttribute("select") == null) {
resultMapNames.remove(node.getStringAttribute("id", node.getValueBasedIdentifier()));
resultMapNames.remove(namespace + "." + node.getStringAttribute("id", node.getValueBasedIdentifier()));
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
clearResultMaps(node, namespace);
}
}
}
}
}
/**
* 清理selectKey
*
* @param list
* @param namespace
*/
private void cleanKeyGenerators(List<XNode> list, String namespace) {
for (XNode node : list) {
String id = node.getStringAttribute("id");
keyGeneratorNames.remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
keyGeneratorNames.remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
}
}
/**
* 清理sql节点缓存
*
* @param list
* @param namespace
*/
private void cleanSqlElements(List<XNode> list, String namespace) {
for (XNode node : list) {
String id = node.getStringAttribute("id");
sqlFragmentNames.remove(id);
sqlFragmentNames.remove(namespace + "." + id);
}
}
public void scan() {
if (!mapperFiles.isEmpty()) {
return;
}
for (Resource mapperLocation : this.mapperLocations) {
String fileKey = getValue(mapperLocation);
mapperFiles.put(mapperLocation.getFilename(), fileKey);
}
}
private String getValue(Resource resource){
try {
String contentLength = String.valueOf((resource.contentLength()));
String lastModified = String.valueOf((resource.lastModified()));
return new StringBuilder(contentLength).append(lastModified).toString();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(),e);
}
}
public List<Resource> getChangedXml(){
List<Resource> resources = new ArrayList<Resource>();
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
String name = mapperLocation.getFilename();
String value = mapperFiles.get(name);
String fileKey = getValue(mapperLocation);
if (!fileKey.equals(value)) {
mapperFiles.put(name, fileKey);
resources.add(mapperLocation);
}
}
return resources;
}
}
public void destroy() throws Exception {
if (executorService != null) {
executorService.shutdownNow();
}
}
}
使用时只需要将该插件纳入到Spring容器中即可。
可选配置参数说明:
- enabled:是否启用该插件,默认值true
- initialDelay:初始延迟多久后开始检测,单位秒,默认5s
- period:间隔多久检测一次xml文件是否变化,单位秒,默认5s