热加载 MyBatis 中修改过的 Mapper.xml
Posted 诗歌poetry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了热加载 MyBatis 中修改过的 Mapper.xml相关的知识,希望对你有一定的参考价值。
拿来即用的热加载 MyBatis 中修改过的 Mapper.xml
项目中使用的是 MyBatis, 在开发过程中, 每次修改完 SQL 都需要重新启动一遍项目, 非常耗时, 影响开发效率. 所以非常有必要热加载 修改过的 Mapper.xml 文件.
原理并不难 :
- 遍历所有的 xml 文件, 根据文件属性, 找到刚更新的文件
- 删除缓存中修改过的 xml 文件的解析对象
- 重新解析 xml 文件并保存
使用方式: 直接复制下面的类到 项目中, 启动项目即可看到如下输出, 说明启动成功
========= Enabled refresh mybatis mapper =========
修改完 SQL 以后, 更新一下资源
过几秒钟, 控制台即可看到更新的文件.
直接复制下面代码到 Spring 项目中, 即可生效 如果没有使用 Lombok, 直接删掉日志输出或者使用 System.out.println()
输出
package com.util.mybatis;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Mybatis的mapper文件中的sql语句被修改后, 只能重启服务器才能被加载, 非常耗时,所以就写了一个自动加载的类,
* 配置后检查xml文件更改,如果发生变化,重新加载xml里面的内容.
*/
@Slf4j
@Component
public class MapperAutoRefresh implements ApplicationContextAware, SmartInitializingSingleton
/*容器上下文, 通过 Aware 填充*/
private ApplicationContext applicationContext;
/*是否启用 Mapper 刷新线程功能*/
private static boolean enabled = true;
/* Mapper.xml 实际资源路径集合 默认去 MyBatis 中已有的配置*/
private Set<String> locationSet = new HashSet<>();
/* MyBatis 中 xml 文件的路径列表, 用 File[] 包裹, 没法直接使用*/
private Set<String> loadedResourcesSet;
/*MyBatis配置对象*/
private Configuration configuration;
/*上一次刷新时间*/
private Long beforeTime = 0L;
/*延迟刷新秒数*/
private static int delaySeconds = 10;
/*休眠时间*/
private static int sleepSeconds = 30;
/**
* 根据配置的 SqlSessionFactoryBean 的 mapperLocations 属性, 获取所有的 mapper.xml 的资源路径
*
* @see SqlSessionFactoryBean
*/
@SuppressWarnings("unchecked")
public void setLocation()
try
Field loadedResourcesField = Configuration.class.getDeclaredField("loadedResources");
loadedResourcesField.setAccessible(true);
this.loadedResourcesSet = ((HashSet<String>) loadedResourcesField.get(configuration));
for (String locationPath : loadedResourcesSet)
if (locationPath.startsWith("file ["))
String s = locationPath.substring("file [".length(), locationPath.lastIndexOf("]"));
locationSet.add(s);
log.info("Location:" + s);
catch (NoSuchFieldException | IllegalAccessException e)
throw new RuntimeException(e);
/**
* 执行资源刷新任务
*/
public void exeTask()
if (CollectionUtils.isEmpty(locationSet))
return;
beforeTime = System.currentTimeMillis();
if (enabled)
new Thread(runnable).start();
private Runnable runnable = () ->
try
// 暂定时间
TimeUnit.SECONDS.sleep(delaySeconds);
log.info("========= Enabled refresh mybatis mapper =========");
// 开始执行刷新操作
while (true)
for (String path : locationSet)
this.refresh(path, beforeTime);
TimeUnit.SECONDS.sleep(sleepSeconds);
catch (InterruptedException e)
throw new RuntimeException(e);
;
/**
* 刷新资源的操作
*
* @param filePath xml 文件路径
* @param beforeTime 上次刷新事件
*/
public void refresh(String filePath, long beforeTime)
// 本次刷新时间
long refreshTime = System.currentTimeMillis();
File file = new File(filePath);
if (!checkFile(file, beforeTime))
return;
try
InputStream inputStream = new FileInputStream(file);
// 清理原有资源,更新为自己的StrictMap方便增量重新加载
String[] mapFieldNames = new String[]
"mappedStatements", "caches",
"resultMaps", "parameterMaps",
"keyGenerators", "sqlFragments"
;
for (String fieldName : mapFieldNames)
Field field = Configuration.class.getDeclaredField(fieldName);
field.setAccessible(true);
Map map = ((Map) field.get(configuration));
if (!(map instanceof StrictMap))
Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");
for (Object key : map.keySet())
try
newMap.put(key, map.get(key));
catch (IllegalArgumentException ex)
newMap.put(key, ex.getMessage());
field.set(configuration, newMap);
// 清理已加载的资源标识,方便让它重新加载。
this.loadedResourcesSet.remove(filePath);
//重新编译加载资源文件。
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration,
filePath, configuration.getSqlFragments());
xmlMapperBuilder.parse();
catch (FileNotFoundException e)
e.printStackTrace();
catch (NoSuchFieldException | IllegalAccessException e)
throw new RuntimeException(e);
finally
ErrorContext.instance().reset();
if (log.isDebugEnabled())
log.info("Refresh file: " + file.getAbsolutePath());
log.info("Refresh filename: " + file.getName());
this.beforeTime = refreshTime;
/**
* 判断文件是否需要刷新,需要刷新返回true,否则返回false
*
* @param file xml 文件
* @param beforeTime 上次更新事件
* @return 是否需要重新加载
*/
private boolean checkFile(File file, Long beforeTime)
return file.lastModified() > beforeTime;
/**
* 重写 org.apache.ibatis.session.Configuration.StrictMap 类
* 来自 MyBatis3.4.0版本,修改 put 方法,允许反复 put更新。
*
* @see org.apache.ibatis.session.Configuration.StrictMap
*/
public static class StrictMap<V> extends HashMap<String, V>
private static final long serialVersionUID = -4950446264854982944L;
private final String name;
public StrictMap(String name, int initialCapacity, float loadFactor)
super(initialCapacity, loadFactor);
this.name = name;
public StrictMap(String name, int initialCapacity)
super(initialCapacity);
this.name = name;
public StrictMap(String name)
super();
this.name = name;
public StrictMap(String name, Map<String, ? extends V> m)
super(m);
this.name = name;
@SuppressWarnings("unchecked")
@Override
public V put(String key, V value)
// 核心逻辑, 先删除后添加
if (enabled)
remove(key);
if (containsKey(key))
throw new IllegalArgumentException(name + " already contains value for " + key);
if (key.contains("."))
final String shortKey = getShortName(key);
if (super.get(shortKey) == null)
super.put(shortKey, value);
else
super.put(shortKey, (V) new Ambiguity(shortKey));
return super.put(key, value);
@Override
public V get(Object key)
V value = super.get(key);
if (value == null)
throw new IllegalArgumentException(name + " does not contain value for " + key);
if (value instanceof Ambiguity)
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
return value;
private String getShortName(String key)
final String[] keyParts = key.split("\\\\.");
return keyParts[keyParts.length - 1];
protected static class Ambiguity
private String subject;
public Ambiguity(String subject)
this.subject = subject;
public String getSubject()
return subject;
/**
* 单例实例化完成后执行
*/
@Override
public void afterSingletonsInstantiated()
SqlSessionFactory sessionFactory = applicationContext.getBean(SqlSessionFactory.class);
this.configuration = sessionFactory.getConfiguration();
setLocation();
exeTask();
/**
* 赋值 applicationContext
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
this.applicationContext = applicationContext;
以上是关于热加载 MyBatis 中修改过的 Mapper.xml的主要内容,如果未能解决你的问题,请参考以下文章
springboot 整合通用mapper , 热加载 遇到ClassCastException
牛叉了-arthas 热更新 mybatis mapper xml
牛叉了-arthas 热更新 mybatis mapper xml
牛叉了-arthas 热更新 mybatis mapper xml