日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本文博主給大家帶來一篇 MyBatis xml 文件熱加載的實現教程,自博主從事開發工作使用 Mybatis 以來,如果需要修改 xml 文件的內容,通常都需要重啟項目,因為不重啟的話,修改是不生效的,Mybatis 僅僅會在項目初始化的時候將 xml 文件加載進內存。

本著提升開發效率且網上沒有能夠直接使用的輪子初衷,博主自己開發了 「mybatis-xmlreload-spring-boot-starter」 這個項目。它能夠幫助我們在「Spring Boot + Mybatis」的開發環境中修改 xml 后,不需要重啟項目就能讓修改過后 xml 文件立即生效,實現熱加載功能。這里先給出項目地址:

  • https://Github.com/wayn111/mybatis-xmlreload-spring-boot-starter 歡迎大家關注,點個star
  • 博主github:https://github.com/wayn111

一、xml 文件熱加載實現原理

1.1 xml 文件怎么樣解析

「Spring Boot + Mybatis」的常規項目中,通過 org.mybatis.spring.SqlSessionFactoryBean 這個類的 buildSqlSessionFactory() 方法完成對 xml 文件的加載邏輯,這個方法只會在自動配置類 MybatisAutoConfiguration 初始化操作時進行調用。這里把 buildSqlSessionFactory() 方法中 xml 解析核心部分進行展示如下:

 

  1. 通過遍歷 this.mApperLocations 數組(這個對象就是保存了編譯后的所有 xml 文件)完成對所有 xml 文件的解析以及加載進內存。this.mapperLocations 解析邏輯在 MybatisProperties 類的 resolveMapperLocations() 方法中,它會解析 mybatis.mapperLocations 屬性中的 xml 路徑,將編譯后的 xml 文件讀取進 Resource 數組中。路徑解析的核心邏輯在 PathMatchingResourcePatternResolver 類的 getResources(String locationPattern) 方法中。大家有興趣可以自己研讀一下,這里不多做介紹。
  2. 通過 xmlMapperBuilder.parse() 方法解析 xml 文件各個節點,解析方法如下: 簡單來說,這個方法會解析 xml 文件中的 mapper|resultMap|cache|cache-ref|sql|select|insert|update|delete 等標簽。將他們保存在 org.Apache.ibatis.session.Configuration 類的對應屬性中,如下展示: 到這里,我們就知道了 Mybatis 對 xml 文件解析是通過 xmlMapperBuilder.parse() 方法完成并且只會在項目啟動時加載 xml 文件。

1.2 實現思路

通過對上述 xml 解析邏輯進行分析,我們可以通過監聽 xml 文件的修改,當監聽到修改操作時,直接調用 xmlMapperBuilder.parse() 方法,將修改過后的 xml 文件進行重新解析,并替換內存中的對應屬性以此完成熱加載操作。這里也就引出了本文所講的主角: 「mybatis-xmlreload-spring-boot-starter」

二、mybatis-xmlreload-spring-boot-starter 登場

「mybatis-xmlreload-spring-boot-starter」 這個項目完成了博主上述的實現思路,使用技術如下:

  • 修改 xml 文件的加載邏輯。在原用 mybatis-spring 中,只會加載項目編譯過后的 xml 文件,也就是 target 目錄下的 xml 文件。但是在「mybatis-xmlreload-spring-boot-starter」中,我修改了這一點,它會加載項目 resources 目錄下的 xml 文件,這樣對于 xml 文件的修改操作是可以立馬觸發熱加載的。
  • 通過 io.methvin.directory-watcher 來監聽 xml 文件的修改操作,它底層是通過 JAVA.nio 的WatchService 來實現。
  • 兼容 Mybatis-plus3.0,核心代碼兼容了 Mybatis-plus 自定義的 com.baomidou.mybatisplus.core.MybatisConfiguration 類,任然可以使用 xml 文件熱加載功能。

2.1 核心代碼

項目的結構如下:

 

核心代碼在 MybatisXmlReload 類中,代碼展示:

/**
 * mybatis-xml-reload核心xml熱加載邏輯
 */
public class MybatisXmlReload {
    private static final Logger logger = LoggerFactory.getLogger(MybatisXmlReload.class);
    /**
     * 是否啟動以及xml路徑的配置類
     */
    private MybatisXmlReloadProperties prop;
    /**
     * 獲取項目中初始化完成的SqlSessionFactory列表,對多數據源進行處理
     */
    private List<SqlSessionFactory> sqlSessionFactories;
    public MybatisXmlReload(MybatisXmlReloadProperties prop, List<SqlSessionFactory> sqlSessionFactories) {
        this.prop = prop;
        this.sqlSessionFactories = sqlSessionFactories;
    }
    public void xmlReload() throws IOException {
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        String CLASS_PATH_TARGET = File.separator + "target" + File.separator + "classes";
        String MAVEN_RESOURCES = "/src/main/resources";
        // 1. 解析項目所有xml路徑,獲取xml文件在target目錄中的位置
        List<Resource> mapperLocationsTmp = Stream.of(Optional.of(prop.getMapperLocations()).orElse(new String[0]))
                .flatMap(location -> Stream.of(getResources(patternResolver, location))).toList();

        List<Resource> mapperLocations = new ArrayList<>(mapperLocationsTmp.size() * 2);
        Set<Path> locationPatternSet = new HashSet<>();
        // 2. 根據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);
            }
        }
        // 3. 對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);
                            // 4. 對多個數據源進行遍歷,判斷修改過的xml文件屬于那個數據源
                            for (SqlSessionFactory sqlSessionFactory : sqlSessionFactories) {
                                try {
                                    // 5. 獲取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");
                                    // 6. 遍歷xml文件
                                    for (Resource mapperLocation : mapperLocations) {
                                        // 7. 判斷是否是被修改過的xml文件,否則跳過
                                        if (!absolutePath.equals(mapperLocation.getFile().getAbsolutePath())) {
                                            continue;
                                        }
                                        // 8. 重新解析xml文件,替換Configuration對象的相對應屬性
                                        XPathParser parser = new XPathParser(mapperLocation.getInputStream(), true, targetConfiguration.getVariables(), new XMLMapperEntityResolver());
                                        XNode mapperXnode = parser.evalNode("/mapper");
                                        List<XNode> resultMapNodes = mapperXnode.evalNodes("/mapper/resultMap");
                                        String namespace = mapperXnode.getStringAttribute("namespace");
                                        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|insert|update|delete");
                                        for (XNode msNode : msNodes) {
                                            String id = msNode.getStringAttribute("id", msNode.getValueBasedIdentifier());
                                            mappedStatementMaps.remove(namespace + "." + id);
                                        }
                                        try {
                                            // 9. 重新加載和解析被修改的 xml 文件
                                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                    targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                                            xmlMapperBuilder.parse();
                                        } catch (Exception e) {
                                            logger.error(e.getMessage(), e);
                                        }
                                        logger.info("Parsed mapper file: '" + mapperLocation + "'");
                                    }
                                } catch (Exception e) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                            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));
    }

    /**
     * 根據xml路徑獲取對應實際文件
     *
     * @param location 文件位置
     * @return Resource[]
     */
    private Resource[] getResources(PathMatchingResourcePatternResolver patternResolver, String location) {
        try {
            return patternResolver.getResources(location);
        } catch (IOException e) {
            return new Resource[0];
        }
    }

    /**
     * 根據反射獲取 Configuration 對象中屬性
     */
    private static Object getFieldValue(Configuration targetConfiguration, Class<?> aClass,
                                        String filed) throws NoSuchFieldException, IllegalAccessException {
        Field resultMapsField = aClass.getDeclaredField(filed);
        resultMapsField.setAccessible(true);
        return resultMapsField.get(targetConfiguration);
    }
}

代碼執行邏輯:

  1. 解析配置文件指定的 xml 路徑,獲取 xml 文件在 target 目錄下的位置
  2. 根據 xml 文件在 target 目錄下的位置,進行路徑替換找到 xml 文件所在 resources 目錄下的位置
  3. 對 resources 目錄的 xml 文件的修改操作進行監聽
  4. 對多個數據源進行遍歷,判斷修改過的 xml 文件屬于那個數據源
  5. 根據 Configuration 對象獲取對應的標簽屬性
  6. 遍歷 resources 目錄下 xml 文件列表
  7. 判斷是否是被修改過的 xml 文件,否則跳過
  8. 解析被修改的 xml 文件,替換 Configuration 對象中的相對應屬性
  9. 重新加載和解析被修改的 xml 文件

2.2 安裝方式

  • 在 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>

2.3 使用配置

Maven 項目寫入「mybatis-xmlreload-spring-boot-starter」坐標后即可使用本項目功能,默認是不啟用 xml 文件的熱加載功能,想要開啟的話通過在項目配置文件中設置 mybatis-xml-reload.enabled 為 true,并指定 mybatis-xml-reload.mapper-locations 屬性,也就是 xml 文件位置即可啟動。具體配置如下:

# 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 或者加博主私人微信「waynaqua」給你解決。 再附項目地址:

  • https://github.com/wayn111/mybatis-xmlreload-spring-boot-starter

希望這個項目能夠提升大家的日常開發效率,節約重啟次數,喜歡的朋友們可以點贊加關注。

分享到:
標簽:mybatis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定