idea - 刷新 mybatis xml

idea - 刷新 mybatis xml

在开发 mybatis 时,修改 xml 不能立刻生效, 在网上找了一下,恰好项目是用mybatis-plus ,修改了一下,可以实现刷新

主要就是加载到修改的xml 文件,然后重新解析xml到内存

  • 通过定时任务找到更新的 xml
  • 调用 XMLMapperBuilder 重新解析

MapperRefresh


@Slf4j
public class MapperRefresh implements java.lang.Runnable {

    private static String filename = "mybatis-refresh.properties";
    private static Properties prop = new Properties();

    private static boolean enabled;         // 是否启用Mapper刷新线程功能
    private Set<String> location;         // Mapper实际资源路径

    private Resource[] mapperLocations;     // Mapper资源路径
    private Configuration configuration;        // MyBatis配置对象

    private volatile Long beforeTime = 0L;           // 上一次刷新时间
    private static int delaySeconds;        // 延迟刷新秒数
    private static int sleepSeconds;        // 休眠时间

    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2, r -> {
        Thread t = new Thread(r, "Mybatis Xml Mapper Thread");
        return t;
    });

    static {

        URL url = MapperRefresh.class.getClassLoader().getResource(filename);
        InputStream is;
        try {
            is = url.openStream();
            if (is == null) {
                log.warn("applicationConfig.properties not found.");
            } else {
                prop.load(is);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        String value = getPropString("enabled");
        System.out.println(value);
        enabled = "true".equalsIgnoreCase(value);

        delaySeconds = getPropInt("delaySeconds");
        sleepSeconds = getPropInt("sleepSeconds");

        delaySeconds = delaySeconds == 0 ? 50 : delaySeconds;
        sleepSeconds = sleepSeconds == 0 ? 3 : sleepSeconds;

        log.debug("[enabled] " + enabled);
        log.debug("[delaySeconds] " + delaySeconds);
        log.debug("[sleepSeconds] " + sleepSeconds);
    }

    public MapperRefresh(Resource[] mapperLocations, Configuration configuration) {
        this.mapperLocations = mapperLocations;
        this.configuration = configuration;
        initMapperLocation();
    }

    @Override
    public void run() {
        beforeTime = System.currentTimeMillis();
        if (enabled) {
            scheduledThreadPoolExecutor.scheduleWithFixedDelay(new MapperRefreshTask(), delaySeconds, sleepSeconds,
                    TimeUnit.SECONDS);
        }
    }

    private void initMapperLocation() {
        //在 classes 下 找到 mapper 路径
        if (location == null) {
            location = Sets.newHashSet();
            for (Resource mapperLocation : mapperLocations) {
                String s = mapperLocation.toString().replaceAll("\\\\", "/");
                s = s.substring("file [".length(), s.lastIndexOf("/"));
                if (!location.contains(s)) {
                    log.debug("mybatis xml location paths : {}", s);
                    location.add(s);
                }
            }
        }
    }


    private class MapperRefreshTask implements Runnable {
        @Override
        public void run() {
            try {
                for (String s : location) {
                    refresh(s, beforeTime);
                }
            } catch (Exception e1) {
                log.error("Mapper xml refresh error: ", e1);
            }
        }
    }

    /**
     * 执行刷新
     *
     * @param filePath   刷新目录
     * @param beforeTime 上次刷新时间
     * @throws NestedIOException     解析异常
     * @throws FileNotFoundException 文件未找到
     * @author ThinkGem
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    private void refresh(String filePath, Long beforeTime) throws Exception {

        // 本次刷新时间
        Long refrehTime = System.currentTimeMillis();

        // 获取需要刷新的Mapper文件列表
        List<File> fileList = this.getRefreshFile(new File(filePath), beforeTime);
        if (fileList.size() > 0) {
            log.debug("Refresh file: " + fileList.size());
        }
        for (int i = 0; i < fileList.size(); i++) {
            InputStream inputStream = new FileInputStream(fileList.get(i));
            String resource = fileList.get(i).getAbsolutePath();
            try {

                // 清理原有资源,更新为自己的StrictMap方便,增量重新加载
                String[] mapFieldNames = new String[]{"mappedStatements", "caches", "resultMaps", "parameterMaps",
                        "keyGenerators", "sqlFragments"};
                for (String fieldName : mapFieldNames) {
                    Field field = configuration.getClass().getDeclaredField(fieldName);
                    field.setAccessible(true);
                    Map map = ((Map) field.get(configuration));
                    if (!(map instanceof StrictMap)) {
                        Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");
                        for (Object key : map.keySet()) {
                            try {
                                newMap.put(key, map.get(key));
                            } catch (IllegalArgumentException ex) {
                                newMap.put(key, ex.getMessage());
                            }
                        }
                        field.set(configuration, newMap);
                    }
                }

                // 清理已加载的资源标识,方便让它重新加载。
                boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
                Field loadedResourcesField = isSupper ? configuration.getClass().getSuperclass().getDeclaredField(
                        "loadedResources") : configuration.getClass().getDeclaredField("loadedResources");
                loadedResourcesField.setAccessible(true);
                Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
                if (resource.lastIndexOf(".xml") != -1) {
                    int i1 = resource.lastIndexOf("/");
                    int i2 = resource.lastIndexOf(".xml");
                    String substring = resource.substring(i1 + 1, i2);
                    Field mappedStatements = configuration.getClass().getDeclaredField("mappedStatements");
                    mappedStatements.setAccessible(true);
                    Map<String, MappedStatement> map =
                            (Map<String, MappedStatement>) mappedStatements.get(configuration);
                    Iterator<String> iterator = map.keySet().iterator();
                    //删除 mappedStatements 中的 缓存
                    while (iterator.hasNext()) {
                        String next = iterator.next();
                        if (next.contains(substring)) {
                            iterator.remove();
                        }
                    }

                    loadedResourcesSet.remove("file [".concat(resource).concat("]"));
                    loadedResourcesSet.remove(resource);
                }

                //重新编译加载资源文件。
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, resource,
                        configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + resource + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }
            log.info("Refresh file: {}", fileList.get(i).getAbsolutePath());
        }
        // 如果刷新了文件,则修改刷新时间,否则不修改
        if (fileList.size() > 0) {
            this.beforeTime = refrehTime;
        }
    }

    /**
     * 获取需要刷新的文件列表
     *
     * @param dir        目录
     * @param beforeTime 上次刷新时间
     * @return 刷新文件列表
     */
    private List<File> getRefreshFile(File dir, Long beforeTime) {
        List<File> fileList = new ArrayList<File>();

        File[] files = dir.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                if (file.isDirectory()) {
                    fileList.addAll(this.getRefreshFile(file, beforeTime));
                } else if (file.isFile()) {
                    if (this.checkFile(file, beforeTime)) {
                        fileList.add(file);
                    }
                } else {
                    log.error("Error file. {}", file.getName());
                }
            }
        }
        return fileList;
    }

    /**
     * 判断文件是否需要刷新
     *
     * @param file       文件
     * @param beforeTime 上次刷新时间
     * @return 需要刷新返回true,否则返回false
     */
    private boolean checkFile(File file, Long beforeTime) {
        if (file.lastModified() > beforeTime) {
            return true;
        }
        return false;
    }

    /**
     * 获取整数属性
     *
     * @param key
     * @return
     */
    private static int getPropInt(String key) {
        int i = 0;
        try {
            i = Integer.parseInt(getPropString(key));
        } catch (Exception e) {
        }
        return i;
    }

    /**
     * 获取字符串属性
     *
     * @param key
     * @return
     */
    private static String getPropString(String key) {
        return prop == null ? null : prop.getProperty(key);
    }

    /**
     * 重写 org.apache.ibatis.session.Configuration.StrictMap 类
     * 来自 MyBatis3.4.0版本,修改 put 方法,允许反复 put更新。
     */
    public static class StrictMap<V> extends HashMap<String, V> {

        private static final long serialVersionUID = -4950446264854982944L;
        private String name;

        public StrictMap(String name, int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor);
            this.name = name;
        }

        public StrictMap(String name, int initialCapacity) {
            super(initialCapacity);
            this.name = name;
        }

        public StrictMap(String name) {
            super();
            this.name = name;
        }

        public StrictMap(String name, Map<String, ? extends V> m) {
            super(m);
            this.name = name;
        }

        @SuppressWarnings("unchecked")
        public V put(String key, V value) {
            remove(key);
            if (containsKey(key)) {
                throw new IllegalArgumentException(name + " already contains value for " + key);
            }
            if (key.contains(".")) {
                final String shortKey = getShortName(key);
                if (super.get(shortKey) == null) {
                    super.put(shortKey, value);
                } else {
                    super.put(shortKey, (V) new Ambiguity(shortKey));
                }
            }
            return super.put(key, value);
        }

        public V get(Object key) {
            V value = super.get(key);
            if (value == null) {
                throw new IllegalArgumentException(name + " does not contain value for " + key);
            }
            if (value instanceof Ambiguity) {
                throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " "
                        + "(try using the full name including the namespace, or rename one of the entries)");
            }
            return value;
        }

        private String getShortName(String key) {
            final String[] keyparts = key.split("\\.");
            return keyparts[keyparts.length - 1];
        }

        protected static class Ambiguity {
            private String subject;

            public Ambiguity(String subject) {
                this.subject = subject;
            }

            public String getSubject() {
                return subject;
            }
        }
    }
}

初始化这个类

@MapperScan(basePackages = "com.x.z.**.mapper")
@SpringBootApplication
public class BootMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootMybatisApplication.class, args);
    }

    @Autowired
    private SqlSession sqlSession;

    @PostConstruct
    public void postConstruct() throws IOException {
        Resource[] resources =
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml");
        new MapperRefresh(resources, sqlSession.getConfiguration()).run();
    }
}

创建 mybatis-refresh.properties

## 开启刷新
enabled=true
## 刷新间隔 秒
sleepSeconds=3
## 延迟间隔 秒
delaySeconds=3

启动

修改 xml 后

2022-07-03 10:15:27.548  INFO 35474 --- [Mapper Thread] com.x.z.mybatis.frame.MapperRefresh      : Refresh file: /home/mybatis-refresh/boot-mybatis/target/classes/mapper/UserModelMapper.xml


good luck!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Idea插件MyBatis的功能是用于简化在写MyBatis项目时的开发过程。通过安装Free MyBatis plugin插件,可以提供以下几个方面的帮助: 1. 自动生成Mapper.xml文件和DAO接口: 这个插件可以自动根据数据库表生成Mapper.xml文件和对应的DAO接口,省去了手动编写的麻烦。 2. 代码跳转和定位: 使用这个插件,可以实现在DAO接口和Mapper.xml之间的快速跳转,方便开发人员在代码中进行定位和查看。 3. 自动生成MyBatis代码: 在使用MyBatis Generator或MyBatis Tools时,这个插件可以帮助自动生成MyBatis相关的代码,减少手动编写的工作量。 总之,Idea插件MyBatis提供了一些便捷的功能,可以加速MyBatis项目的开发过程,提高开发效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [mybatis入门](https://blog.csdn.net/weixin_44222931/article/details/103511806)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [IDEA中常用的插件](https://blog.csdn.net/u010158540/article/details/79800758)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值