在springboot项目中用过mybatis一些框架之后都会遇到一个可能会影响效率的点: 项目越来越大,启动一次好几分钟,而dev阶段,mybatis-xxxx.xml的复杂sql一次性又不能写对,这个时候就不能不改一次xml,重新启动一次,然后再测试,效率极低;
项目是不可能因为这个点而频繁重复启动的,增量变动直接idea走热更新逻辑即可 但是mybatis的xml文件虽然会在变动后热更新到target包中,但是默认是不会刷新的 这个时候可以使用如下的默认配置类,来实现定时定时刷新最新的xml,以期达到快速测试的目的;
注意不要将相关配置扔到prod上
项目中随便找个地方放入如下的bean,启动之后自然就会定时刷新xml文件了:
MyBatisMapperHotRefreshHandler.java
package com.xxxx.initialize.impl;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import com.xxx.xxx.order.xxx.web.configuration.DataSourceConfiguration;
import com.xxx.framework.xxxxx.Foundation;
import com.google.common.collect.Maps;
@Component
public class MyBatisMapperHotRefreshHandler {
private static final LoggerService LOG =
LoggerServiceFactory.getLoggerService(MyBatisMapperHotRefreshHandler.class);
private static final String LOG_TITLE = "MyBatisMapperHotRefreshHandler";
@Autowired
private SqlSessionFactory sqlSessionFactory;
private Resource[] mappersResources;
private final Map<String, Long> fileChangedMapping = Maps.newHashMap();
@PostConstruct
public void init() {
try {
// 非fat环境不执行
if (!Foundation.server().getEnv().isFAT()) {
return;
}
firstLoad();
LOG.info(LOG_TITLE, "will set hot reload mapper.");
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
scheduledThreadPoolExecutor.setMaximumPoolSize(10); // unused
scheduledThreadPoolExecutor.scheduleAtFixedRate(this::handleHotReloadMapper, 60, 10, TimeUnit.SECONDS);
} catch (Exception ex) {
LOG.error(LOG_TITLE, "try set hot reload mapper failed.");
}
}
private void firstLoad() throws IOException {
sqlSessionFactory.getConfiguration().setLogImpl(StdOutImpl.class);
this.mappersResources = getMapperResources();
for (Resource resource : mappersResources) {
fileChangedMapping.put(resource.getFilename(), getFileFrame(resource));
}
}
private long getFileFrame(Resource resource) throws IOException {
return resource.contentLength() + resource.lastModified();
}
private void refreshSqlSessionFactoryBean() throws Exception {
Configuration configuration = sqlSessionFactory.getConfiguration();
removeConfig(configuration);
for (Resource mappersResource : mappersResources) {
(new XMLMapperBuilder(mappersResource.getInputStream(), configuration, mappersResource.toString(),
configuration.getSqlFragments())).parse();
}
}
private Resource[] getMapperResources() throws IOException {
return new PathMatchingResourcePatternResolver().getResources(
PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + DataSourceConfiguration.MAPPER_LOCATION);
}
private void handleHotReloadMapper() {
try {
if (!isChanged()) {
return;
}
refreshSqlSessionFactoryBean();
mappersResources = getMapperResources();
} catch (Exception e) {
LOG.info(LOG_TITLE, e);
}
}
private boolean isChanged() throws IOException {
boolean changedFlag = false;
for (Resource mappersResource : mappersResources) {
String filename = mappersResource.getFilename();
boolean addFileFlag = !fileChangedMapping.containsKey(filename);
long currentFileFrame = getFileFrame(mappersResource);
boolean modifyFileFlag = fileChangedMapping.containsKey(filename)
&& currentFileFrame != fileChangedMapping.getOrDefault(filename, 0L);
fileChangedMapping.put(mappersResource.getFilename(), currentFileFrame);
if (addFileFlag || modifyFileFlag) {
LOG.info(LOG_TITLE, "{} changed.", filename);
changedFlag = true;
}
}
return changedFlag;
}
/**
* 清空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();
}
}
代码略丑,但可用,可自行调整