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); MapsqlSessionFactoryBeans = 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 mapperFiles = new HashMap (); private Set loadedResources; private Map knownMappers; private Collection cacheNames; private Collection mappedStatementNames; private Collection parameterMapNames; private Collection resultMapNames; private Collection sqlFragmentNames; private Collection 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 resources = this.getChangedXml(); if (resources != null && resources.size() > 0) { this.reloadXML(resources); } } public void reloadXML(List 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 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 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 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 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 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 getChangedXml(){ List resources = new ArrayList (); 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