修改了mybatis的xml中的sql不重启服务器如何动态加载更新

目录

一、背景

二、注意

三、代码

四、使用示例

五、其他参考博客


一、背景

开发一个报表功能,好几百行sql,每次修改完想自测下都要重启服务器,启动一次服务器就要3分钟,重启10次就要半小时,耗不起时间呀。于是在网上找半天,没发现能直接用的, 最后还是乖乖用了自己的业余时间,参考了网上内容写了个合适自己的类。

二、注意

1.本类在mybatis-plus-boot-starter 3.4.0, mybatis3.5.5下有效,其他版本没试过

2.部分idea版本修改了xml中的sql后,并不会直接写入到硬盘中,而是保留在内存中,需要手动ctrl+s或者切换到其他窗口才能触发写入新内容到硬盘,所以使用本类时要确认你修改的sql确实已经保存进硬盘里了

3.xml所在文件夹的绝对位置,需要你修改下,再使用本类
 

三、代码

用一个类就能实现开发阶段sql热更新

这个类可以配置启动一个线程,每10秒重新加载最近有修改过的sql

也可以调用一下接口重新修改过的sql

代码如下:

package com.gree;import com.baomidou.mybatisplus.core.MybatisMapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;/*** 本类用于热部署mybatis xml中修改的sql* 注意:*  1.本类在mybatis-plus-boot-starter 3.4.0 和 mybatis3.5.5 下有效,其他版本没试过*  2.部分idea版本修改了xml中的sql后,并不会直接写入到硬盘中,而是保留在内存中,需要手动ctrl+s或者切换到其他窗口才能触发写入新内容到硬盘,所以使用本类时要确认你修改的sql确实已经保存进硬盘里了*  3.xml所在文件夹的绝对位置,需要你修改下,再使用本类*/
@RestController
public class MybatisMapperRefresh {//xml所在文件夹的绝对位置(这里改成你的位置)private String mapperPath = "D:\\wjh\\Mome\\openGitCode\\mybatisRefreshDemo\\src\\main\\resources\\mapper";//是否需要启动一个线程,每隔一段时间就刷新下private boolean needStartThread = false;//刷新间隔时间(秒)private int sleepSeconds = 10;//项目启动时间(或加载本class的时间)private long startTime = new Date().getTime();//上次执行更新xml的时间private long lasteUpdateTime = 0;private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);private SqlSessionFactory sqlSessionFactory;private Configuration configuration;/*** 构造函数,由spring调用生成bean* @param sqlSessionFactory*/public MybatisMapperRefresh(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;this.configuration = sqlSessionFactory.getConfiguration();if (needStartThread) {this.startThread();}}/*** 调用这个接口刷新你的sql,接口会返回刷新了哪些xml* @return*/@RequestMapping("/sql/refresh")public List<String> refreshMapper() {List<String> refreshedList = new ArrayList<>();try {refreshedList = refreshDir();} catch (Exception e) {e.printStackTrace();}return refreshedList;}/*** 启动一个线程,每间隔一段时间就更新下xml中的sql*/public void startThread() {new Thread(new Runnable() {@Overridepublic void run() {while (true) {logger.warn("线程循环中!");try {refreshDir();} catch (Exception e) {e.printStackTrace();}try {Thread.sleep(sleepSeconds * 1000);} catch (Exception e) {e.printStackTrace();}}}}, "mybatis-plus MapperRefresh").start();}/*** 刷新指定目录下所有xml文件** @throws Exception*/private List<String> refreshDir() throws Exception {List<String> refreshedList = new ArrayList<>();try {//获取指定目录下,修改时间大于上次刷新时间,并且修改时间大于项目启动时间的xmlList<File> fileList = FileUtil.getAllFiles(mapperPath);ArrayList<File> needUpdateFiles = new ArrayList<>();for (File file : fileList) {long lastModified = file.lastModified();if (file.isFile() && startTime <= lastModified && lasteUpdateTime <= lastModified) {needUpdateFiles.add(file);continue;}}//逐个xml刷新if (needUpdateFiles.size() != 0) {lasteUpdateTime = new Date().getTime();}for (File file : needUpdateFiles) {Resource refresh = refresh(new FileSystemResource(file));if(refresh != null){refreshedList.add(refresh.getFile().getAbsolutePath());}}} catch (Exception e) {e.printStackTrace();}//返回已刷新的文件return refreshedList;}/*** 刷新mapper*/private Resource refresh(Resource resource) throws Exception {//打印一下流,看看有没有获取到更新的内容/*InputStream inputStream = resource.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String line;while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}bufferedReader.close();*/boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;try {//清理loadedResources//loadedResources:用于注册所有 Mapper XML 配置文件路径Field loadedResourcesField = isSupper? configuration.getClass().getSuperclass().getDeclaredField("loadedResources"): configuration.getClass().getDeclaredField("loadedResources");loadedResourcesField.setAccessible(true);Set<String> loadedResourcesSet = ((Set<String>) loadedResourcesField.get(configuration));loadedResourcesSet.remove(resource.toString());//分析需要刷新的xml文件XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),new XMLMapperEntityResolver());//得到xml中的mapper节点XNode xNode = xPathParser.evalNode("/mapper");String xNodeNamespace = xNode.getStringAttribute("namespace");//清理mapperRegistry中的knownMappers//mapperRegistry:用于注册 Mapper 接口信息,建立 Mapper 接口的 Class 对象和 MapperProxyFactory 对象之间的关系,其中 MapperProxyFactory 对象用于创建 Mapper 动态代理对象Field knownMappersField = MybatisMapperRegistry.class.getDeclaredField("knownMappers");knownMappersField.setAccessible(true);Map knownMappers = (Map) knownMappersField.get(configuration.getMapperRegistry());knownMappers.remove(Resources.classForName(xNodeNamespace));//清理caches//caches:用于注册 Mapper 中配置的所有缓存信息,其中 Key 为 Cache 的 id,也就是 Mapper 的命名空间,Value 为 Cache 对象configuration.getCacheNames().remove(xNodeNamespace);//其他清理操作cleanParameterMap(xNode.evalNodes("/mapper/parameterMap"), xNodeNamespace);cleanResultMap(xNode.evalNodes("/mapper/resultMap"), xNodeNamespace);cleanKeyGenerators(xNode.evalNodes("insert|update|select|delete"), xNodeNamespace);cleanMappedStatements(xNode.evalNodes("insert|update|select|delete"), xNodeNamespace);cleanSqlElement(xNode.evalNodes("/mapper/sql"), xNodeNamespace);//重新加载xml文件XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),configuration, resource.toString(),configuration.getSqlFragments());xmlMapperBuilder.parse();logger.warn("重新加载成功: " + resource );return resource;} catch (IOException e) {logger.error("重新加载失败 :" ,e);} finally {ErrorContext.instance().reset();}return null;}/*** 清理parameterMap* parameterMap用于注册 Mapper 中通过 标签注册的参数映射信息。Key 为 ParameterMap 的 id,由 Mapper 命名空间和 标签的 id 属性构成,Value 为解析 标签后得到的 ParameterMap 对象** @param list* @param namespace*/private void cleanParameterMap(List<XNode> list, String namespace) {for (XNode parameterMapNode : list) {String id = parameterMapNode.getStringAttribute("id");configuration.getParameterMaps().remove(namespace + "." + id);}}/*** 清理resultMap* resultMap用于注册 Mapper 配置文件中通过 标签配置的 ResultMap 信息,ResultMap 用于建立 Java 实体属性与数据库字段之间的映射关系,其中 Key 为 ResultMap 的 id,该 id 是由 Mapper 命名空间和 标签的 id 属性组成的,Value 为解析 标签后得到的 ResultMap 对象** @param list* @param namespace*/private void cleanResultMap(List<XNode> list, String namespace) {for (XNode resultMapNode : list) {String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());configuration.getResultMapNames().remove(id);configuration.getResultMapNames().remove(namespace + "." + id);clearResultMap(resultMapNode, namespace);}}/*** 清理ResultMap* ResultMap用于注册 Mapper 配置文件中通过 标签配置的 ResultMap 信息,ResultMap 用于建立 Java 实体属性与数据库字段之间的映射关系,其中 Key 为 ResultMap 的 id,该 id 是由 Mapper 命名空间和 标签的 id 属性组成的,Value 为解析 标签后得到的 ResultMap 对象*/private void clearResultMap(XNode xNode, String namespace) {for (XNode resultChild : xNode.getChildren()) {if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())|| "case".equals(resultChild.getName())) {if (resultChild.getStringAttribute("select") == null) {configuration.getResultMapNames().remove(resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));configuration.getResultMapNames().remove(namespace + "."+ resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {clearResultMap(resultChild, namespace);}}}}}/*** 清理keyGenerators* keyGenerators:用于注册 KeyGenerator,KeyGenerator 是 MyBatis 的主键生成器,MyBatis 提供了三种KeyGenerator,即 Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、SelectKeyGenerator(通过 select 语句查询自增主键,例如 oracle 的 sequence)** @param list* @param namespace*/private void cleanKeyGenerators(List<XNode> list, String namespace) {for (XNode xNode : list) {String id = xNode.getStringAttribute("id");configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);}}/*** 清理MappedStatements* MappedStatement 对象描述 <insert|selectlupdateldelete> 等标签或者通过 @Select|@Delete|@Update|@Insert 等注解配置的 SQL 信息。MyBatis 将所有的 MappedStatement 对象注册到该属性中,其中 Key 为 Mapper 的 Id, Value 为 MappedStatement 对象** @param list* @param namespace*/private void cleanMappedStatements(List<XNode> list, String namespace) {Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();List<MappedStatement> objects = new ArrayList<>();for (XNode xNode : list) {String id = xNode.getStringAttribute("id");Iterator<MappedStatement> it = mappedStatements.iterator();while (it.hasNext()) {Object object = it.next();if (object instanceof org.apache.ibatis.mapping.MappedStatement) {MappedStatement mappedStatement = (MappedStatement) object;if (mappedStatement.getId().equals(namespace + "." + id)) {objects.add(mappedStatement);}}}}mappedStatements.removeAll(objects);}/*** 清理sql节点缓存* 用于注册 Mapper 中通过 标签配置的 SQL 片段,Key 为 SQL 片段的 id,Value 为 MyBatis 封装的表示 XML 节点的 XNode 对象** @param list* @param namespace*/private void cleanSqlElement(List<XNode> list, String namespace) {for (XNode context : list) {String id = context.getStringAttribute("id");configuration.getSqlFragments().remove(id);configuration.getSqlFragments().remove(namespace + "." + id);}}public static class FileUtil {/*** 列出执行文件夹下的所有文件,包含子目录文件*/public static List<File> getAllFiles(String folderPath) {List<File> fileList = new ArrayList<>();File folder = new File(folderPath);if (!folder.exists() || !folder.isDirectory()) {return fileList;}File[] files = folder.listFiles();for (File file : files) {if (file.isFile()) {fileList.add(file);} else if (file.isDirectory()) {fileList.addAll(getAllFiles(file.getAbsolutePath()));}}return fileList;}}
}

pom.xml文件也贴出来给大家参考

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.greetree</groupId><artifactId>mybatisRefreshDemo</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.0</version><relativePath/> <!-- lookup parent from repository --></parent><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.3.0</version></dependency></dependencies></project>

四、使用示例

1. 将类MybatisMapperRefresh粘贴到可以被spring扫描到的任意目录

2. 修改类中的mapperPath,改成你的xml文件所在目录

3. 启动服务器

4. 修改你的xml文件里的sql

5.ctrl+s保存文件,确保idea将修改内容写入到硬盘,而不是在内存中

6.调用接口http://localhost:{你项目端口号}/sql/refresh 更新sql,接口会返回刷新了哪些xml

7.验证你的sql是否热更新了

五、其他参考博客

IDEA的热部署【MyBatis XML热部署 】_怎么配热部署实现更新ibatis的xml文件-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/380683.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Android】Activity的生命周期

Activity的生命周期 1.返回栈 其实Android是使用任务&#xff08;task&#xff09;来管理Activity的&#xff0c;一个任务就是一组存放在栈里的Activity的集合&#xff0c;这个栈也被称作返回栈&#xff08;back stack&#xff09;。栈是一种后进先出的数据结构&#xff0c;在…

自动驾驶-预测概览

通过生成一条路径来预测一个物体的行为&#xff0c;在每一个时间段内&#xff0c;为每一辆汽车重新计算预测他们新生成的路径&#xff0c;这些预测路径为规划阶段做出决策提供了必要信息 预测路径有实时性的要求&#xff0c;预测模块能够学习新的行为。我们可以使用多源的数据…

【Unity实战100例】Unity声音可视化多种显示效果

目录 一、技术背景 二、界面搭建 三、 实现 UIAudioVisualizer 基类 四、实现 AudioSampler 类 五、实现 IAudioSample 接口 六、实现MusicAudioVisualizer 七、实现 MicrophoneAudioManager 类 八、实现 MicrophoneAudioVisualizer 类 九、源码下载 Unity声音可视化四…

系统架构设计师教程 第3章 信息系统基础知识-3.6 办公自动化系统(OAS)-解读

系统架构设计师教程 第3章 信息系统基础知识-3.6 办公自动化系统&#xff08;OAS&#xff09; 3.6.1 办公自动化系统的概念3.6.1.1 办公活动3.6.1.1 办公自动化的概念 3.6.2 办公自动化系统的功能3.6.2.1 事务处理3.6.2.1.1 单机系统3.6.2.1.2 多机系统 3.6.2.2 信息管理3.6.2.…

YOLOV8/V7/V5的PCB缺陷检测:可视化界面+GUI+目标计数+视频目标检测与跟踪

在本文中&#xff0c;我将介绍如何使用PyQt5创建一个YOLOv8V7/V5目标检测的可视化界面&#xff0c;可以根据需求选择YOLOv8V7/V5的权重。 该可视化界面的功能丰富&#xff0c;包含内容&#xff1a; 1.GUI目标计数视频目标检测与跟踪 2.完整的OLO数据格式制作流程以及代码 3…

逆向案例二十三——请求头参数加密,某区块链交易逆向

网址&#xff1a;aHR0cHM6Ly93d3cub2tsaW5rLmNvbS96aC1oYW5zL2J0Yy90eC1saXN0L3BhZ2UvNAo 抓包分析&#xff0c;发现请求头有X-Apikey参数加密&#xff0c;其他表单和返回内容没有加密。 直接搜索关键字&#xff0c;X-Apikey&#xff0c;找到疑似加密位置&#xff0c;注意这里…

Spring Boot 中使用 Resilience4j 实现弹性微服务的简单了解

1. 引言 在微服务架构中&#xff0c;服务的弹性是非常重要的。Resilience4j 是一个轻量级的容错库&#xff0c;专为函数式编程设计&#xff0c;提供了断路器、重试、舱壁、限流器和限时器等功能。 这里不做过多演示&#xff0c;只是查看一下官方案例并换成maven构建相关展示&…

系统架构设计师教程(清华第二版) 第3章 信息系统基础知识-3.3 管理信息系统(MIS)-解读

系统架构设计师教程 第3章 信息系统基础知识-3.3 管理信息系统(MIS) 3.3.1 管理信息系统的概念3.3.1.1 部件组成3.3.1.2 结构分类3.3.1.2.1 开环结构3.3.1.2.2 闭环结构3.3.1.3 金字塔结构3.3.2 管理信息系统的功能3.3.3 管理信息系统的组成3.3.3.1 销售市场子系统3.3.3.2…

《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-02-信息系统架构

文章目录 1. 概述1.1 信息系统架构&#xff08;ISA&#xff09;1.2 架构风格 2. 信息系统架构分类2.1 信息系统物理结构2.1.1 集中式结构2.1.2 分布式结构 2.2 信息系统的逻辑结构1&#xff09;横向综合2&#xff09;纵向综合3&#xff09;纵横综合 3. 信息系统架构的一般原理4…

Android14之调试广播实例(二百二十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

windows实现自动化按键

1.选择目标窗口 获取窗口句柄 void KeyPresser::selectWindow() {SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);selectedWindowLabel->setText("请点击目标窗口..."); }void CALLBACK …

word的进阶

Word的基本操作 这里主要用到的软件是WPS软件。 一、创建一个文档 第一种&#xff1a;快捷键&#xff1a;ctrln第二种&#xff1a;通过界面鼠标点击 二、设置文档背景 更换过的背景如下&#xff1a; 三、章节、目录导航的设置 四、插入目录页 五、对历史文档进行管理 六、…

收银系统源码-千呼新零售收银视频介绍

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

基于 asp.net家庭财务管理系统设计与实现

博主介绍&#xff1a;专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用感兴趣的可以先…

XLua原理(一)

项目中活动都是用xlua开发的&#xff0c;项目周更热修也是用xlua的hotfix特性来做的。现研究底层原理&#xff0c;对于项目性能有个更好的把控。 本文认为看到该文章的人已具备使用xlua开发的能力&#xff0c;只研究介绍下xlua的底层实现原理。 一.lua和c#交互原理 概括&…

Github报错:Kex_exchange_identification: Connection closed by remote host

文章目录 1. 背景介绍2. 排查和解决方案 1. 背景介绍 Github提交或者拉取代码时&#xff0c;报错如下&#xff1a; Kex_exchange_identification: Connection closed by remote host fatal: Could not read from remote repository.Please make sure you have the correct ac…

【Qt】常用控件 Q widget的enabled属性,geometry属性

Qt是一个实现图形化程序的程序。为了便于我们开发&#xff0c;Qt为我们提供了许多“控件”。我们需要熟悉并掌握这些控件的使用。 一.什么是控件 控件是构成⼀个图形化界⾯的基本要素. 示例一&#xff1a; 像上述⽰例一中的,按钮,列表视图,树形视图,单⾏输⼊框,多⾏输⼊框,滚动…

OPC UA边缘计算耦合器BL205工业通信的最佳解决方案

OPC UA耦合器BL205是钡铼技术基于下一代工业互联网技术推出的分布式、可插拔、结构紧凑、可编程的IO系统&#xff0c;可直接接入SCADA、MES、MOM、ERP等IT系统&#xff0c;无缝链接OT与IT层&#xff0c;是工业互联网、工业4.0、智能制造、数字化转型解决方案中IO系统最佳方案。…

硅谷裸机云多IP服务器怎么样?

硅谷裸机云多IP服务器是一种在硅谷地区提供的、具有多个IP地址的裸机云服务器。这种服务器结合了裸机服务器的高性能和云服务器的灵活性&#xff0c;同时提供了多个IP地址&#xff0c;为用户的各种需求提供了支持。以下是关于硅谷裸机云多IP服务器的一些详细信息&#xff0c;ra…

智能硬件——0-1开发流程

文章目录 流程图1. 市场分析具体分析 2. 团队组建2. 团队组建早期团队配置建议配置一&#xff1a;基础型团队 (4人)配置二&#xff1a;扩展型团队 (6人)配置三&#xff1a;全面型团队 (7人) 3. 产品需求分析4. ID设计&#xff08;Industrial Design, 工业设计&#xff09;5. 结…