分享一个修改了 xml 文件再也不用重启的项目 mybatis-xmlrealod - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
wayn111
V2EX    程序员

分享一个修改了 xml 文件再也不用重启的项目 mybatis-xmlrealod

  •  1
     
  •   wayn111
    wayn111 2023-03-26 21:57:55 +08:00 1687 次点击
    这是一个创建于 930 天前的主题,其中的信息可能已经有所发展或是发生改变。

    自我 18 年使用 Mybaits 以来,开发环境中如果修改了 xml 文件后,只有重启项目才能生效,如果小项目重启还好,但是对于一个重启需要十几分钟的大型项目来说,这就非常耗时了。开发人员因为修改了 xml 文件少量内容,比如添加一个逗号、查询增加一个字段或者修改一个 bug 等,就需要重启整个项目,这就非常痛苦了。

    所以在这里给大家推荐一个实现了 Mybatis xml 文件热加载的项目,mybatis-xmlreload-spring-boot-starter。它能够帮助我们在Spring Boot + Mybatis的开发环境中修改 xml 后,不需要重启项目就能让修改过后 xml 文件立即生效,实现热加载功能。这里给出项目地址:

    ps:mybatis-xmlreload-spring-boot-starter目前 3.0.3.m1 版本实现了 xml 文件修改已有内容,比如修改 sql 语句、添加查询字段、添加查询条件等,可以实现热加载功能。但是对于 xml 文件添加 insert|update|delete|select 标签等内容后,是无法实现热加载的。众所周知,在 Idea 环境进行 Java 开发,在方法内修改方法内容是可以热加载的。但是添加新方法、添加方法参数,修改方法参数,修改方法返回值等都是无法直接热加载的。

    一、mybatis-xmlreload-spring-boot-starter 使用

    mybatis-xmlreload-spring-boot-starter原理:

    • 修改 xml 文件的加载逻辑。在普通的 mybatis-spring 项目中,默认只会加载项目编译过后的 xml 文件,也就是 target 目录下的 xml 文件。但是在mybatis-xmlreload-spring-boot-starter中,修改了这一点,它会加载项目 resources 目录下的 xml 文件,这样用户对于 resources 目录下 xml 文件的修改操作是可以立即触发热加载的。
    • 通过 io.methvin.directory-watcher 项目来监听 xml 文件的修改操作,它底层是通过 java.nio 的WatchService 来实现,当我们监听了整个 resources 目录后,xml 文件的修改会立马触发 MODIFY 事件。
    • 通过 mybatis-spring 项目原生的 xmlMapperBuilder.parse() 方法重新加载解析修改过后的 xml 文件来保证项目对于 Mybatis 的兼容性处理。

    二、技术原理

    mybatis-xmlreload-spring-boot-starter代码结构如下: 核心代码在MybatisXmlReload类中,执行逻辑:

    1. 通过项目初始化时传入 MybatisXmlReloadProperties prop, List<SqlSessionFactory> sqlSessionFactories 参数,获取mybatis-xmlreload-spring-boot-starter的配置信息,以及项目中的数据源配置
    /** * 是否启动以及 xml 路径的配置类 */ private MybatisXmlReloadProperties prop; /** * 获取项目中初始化完成的 SqlSessionFactory 列表,对多数据源进行处理 */ private List<SqlSessionFactory> sqlSessionFactories; public MybatisXmlReload(MybatisXmlReloadProperties prop, List<SqlSessionFactory> sqlSessionFactories) { this.prop = prop; this.sqlSessiOnFactories= sqlSessionFactories; } 
    1. 解析配置文件指定的 xml 路径,获取 xml 文件在 target 目录下的位置
    // 解析项目所有 xml 路径,获取 xml 文件在 target 目录中的位置 List<Resource> mapperLocatiOnsTmp= Stream.of( Optional.of(prop.getMapperLocations()) .orElse(new String[0])) .flatMap(location -> Stream.of(getResources(patternResolver, location))) .toList(); 
    1. 根据 xml 文件在 target 目录下的位置,进行路径替换找到 xml 文件所在 resources 目录下的位置
    // 根据 xml 文件在 target 目录下的位置,进行路径替换找到该 xml 文件在 resources 目录下的位置 for (Resource mapperLocation : mapperLocationsTmp) { mapperLocations.add(mapperLocation); String absolutePath = mapperLocation.getFile().getAbsolutePath(); File tmpFile = new File(absolutePath.replace(CLASS_PATH_TARGET, MAVEN_RESOURCES)); if (tmpFile.exists()) { locationPatternSet.add(Path.of(tmpFile.getParent())); FileSystemResource fileSystemResource = new FileSystemResource(tmpFile); mapperLocations.add(fileSystemResource); } } 
    1. 对 resources 目录的 xml 文件的修改操作进行监听
    // 对 resources 目录的 xml 文件修改进行监听 List<Path> rootPaths = new ArrayList<>(); rootPaths.addAll(locationPatternSet); DirectoryWatcher watcher = DirectoryWatcher.builder() .paths(rootPaths) // or use paths(directoriesToWatch) .listener(event -> { switch (event.eventType()) { case CREATE: /* file created */ break; case MODIFY: /* file modified */ Path modifyPath = event.path(); String absolutePath = modifyPath.toFile().getAbsolutePath(); logger.info("mybatis xml file has changed:" + modifyPath); // 执行热加载逻辑... break; case DELETE: /* file deleted */ break; } }) .build(); ThreadFactory threadFactory = r -> { Thread thread = new Thread(r); thread.setName("xml-reload"); thread.setDaemon(true); return thread; }; watcher.watchAsync(new ScheduledThreadPoolExecutor(1, threadFactory)); 
    1. 对多个数据源进行遍历,判断修改过的 xml 文件属于那个数据源
    // 对多个数据源进行遍历,判断修改过的 xml 文件属于那个数据源 for (SqlSessionFactory sqlSessionFactory : sqlSessionFactories) { ... } 
    1. 根据 Configuration 对象获取对应的标签属性
    // 根据 Configuration 对象获取对应的标签属性 Configuration targetCOnfiguration= sqlSessionFactory.getConfiguration(); Class<?> tClass = targetConfiguration.getClass(), aClass = targetConfiguration.getClass(); if (targetConfiguration.getClass().getSimpleName() .equals("MybatisConfiguration")) { aClass = Configuration.class; } Set<String> loadedResources = (Set<String>) getFieldValue( targetConfiguration, aClass, "loadedResources"); loadedResources.clear(); Map<String, ResultMap> resultMaps = (Map<String, ResultMap>) getFieldValue( targetConfiguration, tClass, "resultMaps"); Map<String, XNode> sqlFragmentsMaps = (Map<String, XNode>) getFieldValue( targetConfiguration, tClass, "sqlFragments"); Map<String, MappedStatement> mappedStatementMaps = (Map<String, MappedStatement>) getFieldValue( targetConfiguration, tClass, "mappedStatements"); 
    1. 遍历 resources 目录下 xml 文件列表
    // 遍历 resources 目录下 xml 文件列表 for (Resource mapperLocation : mapperLocations) { ... } 
    1. 判断是否是被修改过的 xml 文件,否则跳过
    // 判断是否是被修改过的 xml 文件,否则跳过 if (!absolutePath.equals(mapperLocation.getFile().getAbsolutePath())) { continue; } 
    1. 解析 xml 文件,获取修改后的 xml 文件标签对应的 resultMaps|sqlFragmentsMaps|mappedStatementMaps 的属性并执行替换逻辑,并且兼容 mybatis-plus 的替换逻辑
    // 重新解析 xml 文件,替换 Configuration 对象的相对应属性 XPathParser parser = new XPathParser(mapperLocation.getInputStream(), true, targetConfiguration.getVariables(), new XMLMapperEntityResolver()); XNode mapperXnode = parser.evalNode("/mapper"); String namespace = mapperXnode.getStringAttribute("namespace"); List<XNode> resultMapNodes = mapperXnode.evalNodes("/mapper/resultMap"); for (XNode xNode : resultMapNodes) { String id = xNode.getStringAttribute("id", xNode.getValueBasedIdentifier()); resultMaps.remove(namespace + "." + id); } List<XNode> sqlNodes = mapperXnode.evalNodes("/mapper/sql"); for (XNode sqlNode : sqlNodes) { String id = sqlNode.getStringAttribute("id", sqlNode.getValueBasedIdentifier()); sqlFragmentsMaps.remove(namespace + "." + id); } List<XNode> msNodes = mapperXnode.evalNodes("select|inert|update|delete"); for (XNode msNode : msNodes) { String id = msNode.getStringAttribute("id", msNode.getValueBasedIdentifier()); mappedStatementMaps.remove(namespace + "." + id); } 
    1. 重新加载和解析被修改的 xml 文件
    // 9. 重新加载和解析被修改的 xml 文件 try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder( mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { logger.error(e.getMessage(), e); } 

    三、安装方式

    • Spring Boot3.0 中,mybatis-xmlreload-spring-boot-starter在 Maven 项目提供坐标地址如下:
    <dependency> <groupId>com.wayn</groupId> <artifactId>mybatis-xmlreload-spring-boot-starter</artifactId> <version>3.0.3.m1</version> </dependency> 
    • Spring Boot2.0 Maven 项目提供坐标地址如下:
    <dependency> <groupId>com.wayn</groupId> <artifactId>mybatis-xmlreload-spring-boot-starter</artifactId> <version>2.0.1.m1</version> </dependency> 

    四、使用配置

    mybatis-xmlreload-spring-boot-starter 目前只有两个配置属性。mybatis-xml-reload.enabled 默认是 false , 也就是不启用 xml 文件的热加载功能,想要开启的话通过在项目配置文件中设置mybatis-xml-reload.enabled为 true 。还有一个配置属性是 mybatis-xml-reload.mapper-locations,执行热加载的 xml 文件路径,这个属性需要手动填写,跟项目中的 mybatis.mapper-locations 保持一直即可。具体配置如下:

    # mybatis xml 文件热加载配置 mybatis-xml-reload: # 是否开启 xml 热更新,true 开启,false 不开启,默认为 false enabled: true # xml 文件路径,可以填写多个,逗号分隔。 # eg: `classpath*:mapper/**/*Mapper.xml,classpath*:other/**/*Mapper.xml` mapper-locations: classpath:mapper/*Mapper.xml 

    五、最后

    欢迎大家使用mybatis-xmlreload-spring-boot-starter,这个项目我开源的的,使用中遇到问题可以提交 issue 。提交的问题我都会一一查看并回复。再附项目地址:

    最后再说一句,感兴趣的朋友可以点赞加关注,你的支持将是我更新动力。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2514 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 01:50 PVG 09:50 LAX 18:50 JFK 21:50
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86