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)


相关推荐

  • SpringWS创建webservice服务端及客户端

    SpringWS创建webservice服务端及客户端一、服务端下图主要目录结构,这个是完整的,下面,我们就来构建1.1、创建springinitializr,下一步1.2选中springweb,下一步,最后创建1.3导入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web- services&

  • Python netcdf_python处理nc文件

    Python netcdf_python处理nc文件  NetCDF(networkCommonDataForm)网络通用数据格式是一种面向数组型并适于网络共享的数据的描述和编码标准。目前,NetCDF广泛应用于大气科学、水文、海洋学、环境模拟、地球物理等诸多领域。用户可以借助多种方式方便地管理和操作NetCDF数据集。  文件的后缀是.nc  这里采用python的一个专门用来处理.nc文件的库–netCDF4该库的安装直接:pipinstallnetCDF4这个库玩起来稍微比Pandas复杂一些。下面以全球降水量数据为例进行

    2022年10月22日
  • 正则过滤内网地址和网段不一致_ip地址不是局域网网段的ip

    正则过滤内网地址和网段不一致_ip地址不是局域网网段的ip1.问题描述我要过滤出ABC类内网地址和CIDR格式的内网IP段主要是以下段1.A类地址:10.0.0.0~10.255.255.2552.B类地址:172.16.0.0~172.31.255.2553.C类地址:192.168.0.0~192.168.255.2552.解决问题正则表达式:^(10\.\d{1,3}\.\d{1,3}\.((0\/([89]|1[0-9]|2\d|3[012]))|(\d{1,3})))|(172\.(1[6789]|2\\d|3[01])\.\d{1,3

  • linux 如何配置IP地址

    linux 如何配置IP地址linux如何配置IP地址首先需要先进入里面,命令如下然后在配置操作:然后在保存退出即可:

  • 小米6最好用的系统版本[通俗易懂]

    小米6最好用的系统版本[通俗易懂]小米6最好用的系统版本小米6最好用的系统稳定版10.4.3首先说一下为什么这个版本的系统我认为最好用,因为自己是米粉,也比较喜欢用最新的系统,去年用小米6收到了10.4.2版本的系统更新,体验之后感觉真的很nice,安卓9流畅度提升非常高,包括软件的启动速度,各项反应,但是有一些小瑕疵,比如断流,软件闪退,系统掉帧,然后过了一段时间小米推送了10.4.3稳定版,修复了这三个问题,体验至今为止,没有其他任何问题该版本优点总结如下第一,该版本基于miui10,系统简单易用,基本上算是miui的一个小成的

  • python下载ts视频文件「建议收藏」

    python下载ts视频文件「建议收藏」importrequestsfrommultiprocessingimportPooldefmission(url,n):headers={"User-Agent":"Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/69.0.3497.100Safa…

发表回复

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

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