Mybatis实现*mapper.xml热部署-分子级更新

Mybatis实现*mapper.xml热部署-分子级更新无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里以跳转到教程。需求:项目在开发阶段或是修复bug阶段,会有修改mybatis的mapper.xml的时候,修改一般情况都要重启才能生失效,如果是分布式项目重启有时会耗时很久,都是无尽的等待。如果频繁修改,那么时间都浪费到等待重启的过程。…

大家好,又见面了,我是你们的朋友全栈君。

需求:

项目在开发阶段或是修复bug阶段,会有修改mybatis的mapper.xml的时候,修改一般情况都要重启才能生失效,如果是分布式项目重启有时会耗时很久,都是无尽的等待。如果频繁修改,那么时间都浪费到等待重启的过程。

目标:

实现mybatis的mapper.xml文件修改后热部署,而且只热更新修改了的xml,可以提高重新解析过程的效率。

要求:

尽量满足开闭原则

实现:

import com.yirun.framework.core.utils.PropertiesHolder;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import java.lang.reflect.Field;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
/**
*  mapper.xml热部署,最小单位是一个xml文件
*  @date                    :2018/12/20
*  @author                  :zc.ding@foxmail.com
*/
public class MapperHotDeployPlugin implements InitializingBean, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(MapperHotDeployPlugin.class);
private final static String OPEN = "1";
private volatile SqlSessionFactoryBean sqlSessionFactoryBean;
private volatile Configuration configuration;
@Override
public void afterPropertiesSet() {
String flag = PropertiesHolder.getProperty("mapper.hot.deploy");
logger.info("Mybatis热部署标识mapper.hot.deploy={}", flag);
// 判断是否开启了热部署
if(StringUtils.isNotBlank(flag) && OPEN.equals(flag)){
new WatchThread().start();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("frameworkSqlSessionFactory");
sqlSessionFactoryBean = applicationContext.getBean(SqlSessionFactoryBean.class);
configuration = sqlSessionFactory.getConfiguration();
}
class WatchThread extends Thread{
private final Logger logger = LoggerFactory.getLogger(WatchThread.class);
@Override
public void run() {
startWatch();
}
/**
*  启动监听
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private void startWatch(){
try{
WatchService watcher = FileSystems.getDefault().newWatchService();
getWatchPaths().forEach(p -> {
try {
Paths.get(p).register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
} catch (Exception e) {
logger.error("ERROR: 注册xml监听事件", e);
throw new RuntimeException("ERROR: 注册xml监听事件", e);
}
});
while (true) {
WatchKey watchKey = watcher.take();
Set<String> set = new HashSet<>();
for (WatchEvent<?> event: watchKey.pollEvents()) {
set.add(event.context().toString());
}
// 重新加载xml
reloadXml(set);
boolean valid = watchKey.reset();
if (!valid) {
break;
}
}
}catch(Exception e){
System.out.println("Mybatis的xml监控失败!");
logger.info("Mybatis的xml监控失败!", e);
}
}
/**
*  加载需要监控的文件父路径
*  @return java.util.Set<java.lang.String>
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private Set<String> getWatchPaths(){
Set<String> set = new HashSet<>();
Arrays.stream(getResource()).forEach(r -> {
try{
logger.info("资源路径:{}", r.toString());
set.add(r.getFile().getParentFile().getAbsolutePath());
}catch(Exception e){
logger.info("获取资源路径失败", e);
throw new RuntimeException("获取资源路径失败");
}
});
logger.info("需要监听的xml资源: {}", set);
return set;
}
/**
*  获取配置的mapperLocations
*  @return org.springframework.core.io.Resource[]
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private Resource[] getResource(){
return (Resource[]) getFieldValue(sqlSessionFactoryBean, "mapperLocations");
}
/**
*  删除xml元素的节点缓存
*  @param nameSpace xml中命名空间
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private void clearMap(String nameSpace) {
logger.info("清理Mybatis的namespace={}在mappedStatements、caches、resultMaps、parameterMaps、keyGenerators、sqlFragments中的缓存");
Arrays.asList("mappedStatements", "caches", "resultMaps", "parameterMaps", "keyGenerators", "sqlFragments").forEach(fieldName -> {
Object value = getFieldValue(configuration, fieldName);
if (value instanceof Map) {
Map<?, ?> map = (Map)value;
List<Object> list = map.keySet().stream().filter(o -> o.toString().startsWith(nameSpace + ".")).collect(Collectors.toList());
logger.info("需要清理的元素: {}", list);
list.forEach(k -> map.remove((Object)k));
}
});
}
/**
*  清除文件记录缓存
*  @param resource xml文件路径
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private void clearSet(String resource) {
logger.info("清理mybatis的资源{}在容器中的缓存", resource);
Object value = getFieldValue(configuration, "loadedResources");
if (value instanceof Set) {
Set<?> set = (Set)value;
set.remove(resource);
set.remove("namespace:" + resource);
}
}
/**
*  获取对象指定属性
*  @param obj 对象信息
*  @param fieldName 属性名称
*  @return java.lang.Object
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private Object getFieldValue(Object obj, String fieldName){
logger.info("从{}中加载{}属性", obj, fieldName);
try{
Field field = obj.getClass().getDeclaredField(fieldName);
boolean accessible = field.isAccessible();
field.setAccessible(true);
Object value = field.get(obj);
field.setAccessible(accessible);
return value;
}catch(Exception e){
logger.info("ERROR: 加载对象中[{}]", fieldName, e);
throw new RuntimeException("ERROR: 加载对象中[" + fieldName + "]", e);
}
}
/**
*  重新加载set中xml
*  @param set 修改的xml资源
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private void reloadXml(Set<String> set){
logger.info("需要重新加载的文件列表: {}", set);
List<Resource> list = Arrays.stream(getResource())
.filter(p -> set.contains(p.getFilename()))
.collect(Collectors.toList());
logger.info("需要处理的资源路径:{}", list);
list.forEach(r ->{
try{
clearMap(getNamespace(r));
clearSet(r.toString());
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(r.getInputStream(), configuration,
r.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
}catch(Exception e){
logger.info("ERROR: 重新加载[{}]失败", r.toString(), e);
throw new RuntimeException("ERROR: 重新加载[" + r.toString() + "]失败", e);
}finally {
ErrorContext.instance().reset();
}
});
logger.info("成功热部署文件列表: {}", set);
}
/**
*  获取xml的namespace
*  @param resource xml资源
*  @return java.lang.String
*  @date                    :2018/12/19
*  @author                  :zc.ding@foxmail.com
*/
private String getNamespace(Resource resource){
logger.info("从{}获取namespace", resource.toString());
try{
XPathParser parser = new XPathParser(resource.getInputStream(), true, null, new XMLMapperEntityResolver());
return parser.evalNode("/mapper").getStringAttribute("namespace");
}catch(Exception e){
logger.info("ERROR: 解析xml中namespace失败", e);
throw new RuntimeException("ERROR: 解析xml中namespace失败", e);
}
}
}
}

使用方式:

<bean id="mapperHotDeploy" class="com.xxxxx.plugins.MapperHotDeployPlugin"/>

通过mapper.hot.deploy属性配置启停

待优化:

支持单数据源,使用开发环境

结语:

使用简单、无入侵、满足开闭原则。good luck!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143398.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 第三章 文件的描述符和重定向

    第三章 文件的描述符和重定向

  • MySQL与PostgreSQL比较,哪个更好、我们该选用哪个?

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 作者:祚儿疯 blog.csdn.net/u012414189/article/details/84064146 1…

  • pycharm下载pandas包失败_pycharm下载包很慢

    pycharm下载pandas包失败_pycharm下载包很慢Pycharm使用安装各种包下载速度慢问题快捷键安装各种包python3-mpipinstallnumpy控制台用这段代码,所有包应该都可以下载下载速度慢问题pip下载速度一般几十k,下着下着就超时了,我用这个大佬的方法解决了MAC下的这个问题MAC解决pip3下载速度慢的问题快捷键最后记录一些经常用的MACPycharm快捷键,方便使用option+commend+L代码格式化control+r运行commend+backspace删除光标所在行

  • node.js 安装与环境变量配置

    node.js 安装与环境变量配置1.进入node.js官网或者node.js中文网下载node.js最新版本(windows系统的.msi)2.双击.msi文件进行安装,默认会把node.js和npm加到path当中,进入cmd运行以下命令查看版本。其实,node目录下的node.exe就是一个绿色的可执行文件,拷到哪儿都可以用~(引用别人的话)3.配置全局模块的安装路径到node-global文件夹,…

  • Lucene4 TokenStream「建议收藏」

    Lucene4 TokenStream「建议收藏」packageorg.apache.lucene.analysis;/**LicensedtotheApacheSoftwareFoundation(ASF)underoneormore*contributorlicenseagreements.SeetheNOTICEfiledistributedwith*thisworkfor

  • 小确幸BBS论坛-2-首页

    小确幸BBS论坛-2-首页话说:各位读者朋友,晚上好!这一篇展示首页。难度系数:★★★☆☆建议用时:1天实际用时1天目录工具类首页面index.jsp首页面样式后端代码首页面复杂点:1.SQL语句复杂点,要统计下回复次数;2.搜索。单独搜索简单,但是要实现在不同栏目下的搜索,就相对复杂些,而且不论搜索还是分栏目,分页都要带上;1.工具类DBUtil连接数据

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号