原文转载自:https://blog.csdn.net/lovelong8808/article/details/78738086
转载使用后,根据实际情况有做部分修改,感谢原文博主提供的刷新方法。
注意,目前该方法只支持全量刷新,即刷新指定路径下的所有Mapper.xml文件,不支持仅刷新修改后的部分Mapper.xml。
由于项目已经发布到线上,要是修改一个Mapper.xml文件的话,需要重启整个服务,这个是很耗时间的,而且在一段时间内导致服务不可用,严重影响用户的体验度。所以希望可以有一个机制可以,当修改某个mapper.xml的时候,只要重新加载这个mapper.xml就好了,参考网上的一些资料和demo,加上一些自己的总结,下面的代码是通过测试的,可以供参考和使用。
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* Created with IntelliJ IDEA.
* Description: 动态刷新Mapper.xml
*/
public class RefreshMapperCache {
private Logger log = LoggerFactory.getLogger(RefreshMapperCache.class);
private SqlSessionFactory sqlSessionFactory;
private Resource[] mapperLocations;
private String packageSearchPath;
public void refreshMapper() {
try {
Configuration configuration = this.sqlSessionFactory.getConfiguration();
// step.1 扫描文件
try {
this.scanMapperXml();
} catch (IOException e) {
log.error("packageSearchPath扫描包路径配置错误");
return;
}
// System.out.println("==============刷新前mapper中的内容 start===============");
// //获取xml中的每个语句的名称即 id = "findUserById";
// for (String name : configuration.getMappedStatementNames()) {
// System.out.println(name);
// }
// System.out.println("==============刷新前mapper中的内容 end===============");
// step.2.1 清理
this.removeConfig(configuration);
// step.2.2 重新加载
for (Resource configLocation : mapperLocations) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
System.out.println("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");
} catch (IOException e) {
log.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
continue;
}
}
// System.out.println("--------------------------刷新后mapper中的内容 start--------------------------");
// for (String name : configuration.getMappedStatementNames()) {
// System.out.println(name);
// }
// System.out.println("--------------------------刷新后mapper中的内容 end--------------------------");
} catch (Exception e) {
log.error("****************刷新缓存异常: " + e.getMessage());
e.getMessage();
}
}
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();
}
}
在Spring的配置文件 applicationContext.xml 中配置bean
<bean id="refreshMapperCache" class="com.sharp.vocb.cache.RefreshMapperCache" >
<!-- 扫描的映射mapper.xml的文件路径
这个地方要注意mapper的文件,多数据源情况下,只能扫描自己数据源下的mapper,否则会报异常 -->
<property name="packageSearchPath" value="classpath*:mappers/**/*.xml"></property>
<!-- 配置自己的数据源 -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
注意:
1. 如果是多数据源的情况 , 扫描mapper.xml文件的时候,只能扫该数据源下的mapper.xml文件
2. 多数据源情况下,设置sqlSessionFactory 的时候,要设置为对应的数据源
3. 如果是单数据源的情况,那么就简单了,只需要配置当前数据源及对应的mapper.xml文件即可
调用方式,我是采用的是通过前端触发一个网络请求来调用刷新MapperCache的方法。
@Controller
@RequestMapping("/refresh")
public class RefreshController extends BaseController {
private Logger logger = LoggerFactory.getLogger(RefreshController.class);
@Autowired
private RefreshMapperCache refreshMapperCache;
/**
* 刷新 Mapper.xml
* @param request
* @return
*/
@RequestMapping("/refreshMapperCache")
@ResponseBody
public String refreshMapperCache(HttpServletRequest request){
JSONObject object = new JSONObject();
try {
refreshMapperCache.refreshMapper();
SysEntUser sysEntUser = getCurrentUser(request);
logger.info("== 用户 "+ sysEntUser.getName() + " 执行了刷新MapperCache操作。");
object.put(SystemKeyWord.RETURN_CODE, SystemKeyWord.SUCCESS_CODE);
object.put(SystemKeyWord.RETURN_DESC, "操作成功!");
} catch (Exception e){
logger.error("==刷新MapperCache发生错误! "+e.getMessage());
e.printStackTrace();
object.put(SystemKeyWord.RETURN_CODE, SystemKeyWord.ERROR_CODE);
object.put(SystemKeyWord.RETURN_DESC, "操作失败!");
object.put(SystemKeyWord.ERROR_MESSAGE, e.getMessage());
}
return object.toJSONString();
}
}