浏览代码

Revert: 清理热加载相关.

nieqiurong 6 年之前
父节点
当前提交
db65c28670

+ 3 - 0
mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/spring/boot/starter/GlobalConfig.java

@@ -247,6 +247,9 @@ public class GlobalConfig {
         if (StringUtils.checkValNotNull(this.getFieldStrategy())) {
             globalConfiguration.setFieldStrategy(this.getFieldStrategy());
         }
+        if (StringUtils.checkValNotNull(this.getRefreshMapper())) {
+            globalConfiguration.setRefresh(this.getRefreshMapper());
+        }
         if (StringUtils.checkValNotNull(this.getCapitalMode())) {
             globalConfiguration.setCapitalMode(this.getCapitalMode());
         }

+ 12 - 4
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/MybatisConfiguration.java

@@ -22,6 +22,7 @@ import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.SqlSession;
 
+import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
 import com.baomidou.mybatisplus.toolkit.IdWorker;
 
 /**
@@ -75,12 +76,19 @@ public class MybatisConfiguration extends Configuration {
     @Override
     public void addMappedStatement(MappedStatement ms) {
         logger.debug("addMappedStatement: " + ms.getId());
-        if (this.mappedStatements.containsKey(ms.getId())) {
+        if (GlobalConfigUtils.isRefresh(ms.getConfiguration())) {
             /*
-             * 说明已加载了xml中的节点; 忽略mapper中的SqlProvider数据
+             * 支持是否自动刷新 XML 变更内容,开发环境使用【 注:生产环境勿用!】
              */
-            logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file");
-            return;
+            this.mappedStatements.remove(ms.getId());
+        } else {
+            if (this.mappedStatements.containsKey(ms.getId())) {
+                /*
+                 * 说明已加载了xml中的节点; 忽略mapper中的SqlProvider数据
+                 */
+                logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file");
+                return;
+            }
         }
         super.addMappedStatement(ms);
     }

+ 297 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/spring/MybatisMapperRefresh.java

@@ -0,0 +1,297 @@
+/**
+ * Copyright (c) 2011-2020, hubin (jobob@qq.com).
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.baomidou.mybatisplus.spring;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ibatis.binding.MapperRegistry;
+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.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.core.io.UrlResource;
+import org.springframework.util.ResourceUtils;
+
+import com.baomidou.mybatisplus.entity.GlobalConfiguration;
+import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
+import com.baomidou.mybatisplus.toolkit.SystemClock;
+
+/**
+ * <p>
+ * 切莫用于生产环境(后果自负)<br>
+ * Mybatis 映射文件热加载(发生变动后自动重新加载).<br>
+ * 方便开发时使用,不用每次修改xml文件后都要去重启应用.<br>
+ * </p>
+ *
+ * @author nieqiurong
+ * @Date 2016-08-25
+ */
+public class MybatisMapperRefresh implements Runnable {
+
+    private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);
+    /**
+     * 记录jar包存在的mapper
+     */
+    private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
+    private SqlSessionFactory sqlSessionFactory;
+    private Resource[] mapperLocations;
+    private Long beforeTime = 0L;
+    private Configuration configuration;
+    /**
+     * 是否开启刷新mapper
+     */
+    private boolean enabled;
+    /**
+     * xml文件目录
+     */
+    private Set<String> fileSet;
+    /**
+     * 延迟加载时间
+     */
+    private int delaySeconds = 10;
+    /**
+     * 刷新间隔时间
+     */
+    private int sleepSeconds = 20;
+
+    public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, int delaySeconds,
+                                int sleepSeconds, boolean enabled) {
+        this.mapperLocations = mapperLocations.clone();
+        this.sqlSessionFactory = sqlSessionFactory;
+        this.delaySeconds = delaySeconds;
+        this.enabled = enabled;
+        this.sleepSeconds = sleepSeconds;
+        this.configuration = sqlSessionFactory.getConfiguration();
+        this.run();
+    }
+
+    public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, boolean enabled) {
+        this.mapperLocations = mapperLocations.clone();
+        this.sqlSessionFactory = sqlSessionFactory;
+        this.enabled = enabled;
+        this.configuration = sqlSessionFactory.getConfiguration();
+        this.run();
+    }
+
+    @Override
+    public void run() {
+        final GlobalConfiguration globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
+        /*
+         * 启动 XML 热加载
+		 */
+        if (enabled) {
+            beforeTime = SystemClock.now();
+            final MybatisMapperRefresh runnable = this;
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (fileSet == null) {
+                        fileSet = new HashSet<>();
+                        if (mapperLocations != null) {
+                            for (Resource mapperLocation : mapperLocations) {
+                                try {
+                                    if (ResourceUtils.isJarURL(mapperLocation.getURL())) {
+                                        String key = new UrlResource(ResourceUtils.extractJarFileURL(mapperLocation.getURL()))
+                                            .getFile().getPath();
+                                        fileSet.add(key);
+                                        if (jarMapper.get(key) != null) {
+                                            jarMapper.get(key).add(mapperLocation);
+                                        } else {
+                                            List<Resource> resourcesList = new ArrayList<>();
+                                            resourcesList.add(mapperLocation);
+                                            jarMapper.put(key, resourcesList);
+                                        }
+                                    } else {
+                                        fileSet.add(mapperLocation.getFile().getPath());
+                                    }
+                                } catch (IOException ioException) {
+                                    ioException.printStackTrace();
+                                }
+                            }
+                        }
+                    }
+                    try {
+                        Thread.sleep(delaySeconds * 1000);
+                    } catch (InterruptedException interruptedException) {
+                        interruptedException.printStackTrace();
+                    }
+                    do {
+                        try {
+                            for (String filePath : fileSet) {
+                                File file = new File(filePath);
+                                if (file.isFile() && file.lastModified() > beforeTime) {
+                                    globalConfig.setRefresh(true);
+                                    List<Resource> removeList = jarMapper.get(filePath);
+                                    if (removeList != null && !removeList.isEmpty()) {
+                                        for (Resource resource : removeList) {
+                                            runnable.refresh(resource);
+                                        }
+                                    } else {
+                                        runnable.refresh(new FileSystemResource(file));
+                                    }
+                                }
+                            }
+                            if (globalConfig.isRefresh()) {
+                                beforeTime = SystemClock.now();
+                            }
+                            globalConfig.setRefresh(true);
+                        } catch (Exception exception) {
+                            exception.printStackTrace();
+                        }
+                        try {
+                            Thread.sleep(sleepSeconds * 1000);
+                        } catch (InterruptedException interruptedException) {
+                            interruptedException.printStackTrace();
+                        }
+
+                    } while (true);
+                }
+            }, "mybatis-plus MapperRefresh").start();
+        }
+    }
+
+    /**
+     * 刷新mapper
+     *
+     * @throws Exception
+     */
+    @SuppressWarnings("rawtypes")
+    private void refresh(Resource resource) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
+        this.configuration = sqlSessionFactory.getConfiguration();
+        boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
+        try {
+            Field loadedResourcesField = isSupper ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
+                : configuration.getClass().getDeclaredField("loadedResources");
+            loadedResourcesField.setAccessible(true);
+            Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
+            XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
+                new XMLMapperEntityResolver());
+            XNode context = xPathParser.evalNode("/mapper");
+            String namespace = context.getStringAttribute("namespace");
+            Field field = MapperRegistry.class.getDeclaredField("knownMappers");
+            field.setAccessible(true);
+            Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
+            mapConfig.remove(Resources.classForName(namespace));
+            loadedResourcesSet.remove(resource.toString());
+            configuration.getCacheNames().remove(namespace);
+            cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
+            cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
+            cleanKeyGenerators(context.evalNodes("insert|update"), namespace);
+            cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);
+            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
+                sqlSessionFactory.getConfiguration(),
+                resource.toString(), sqlSessionFactory.getConfiguration().getSqlFragments());
+            xmlMapperBuilder.parse();
+            logger.debug("refresh: '" + resource + "', success!");
+        } catch (IOException e) {
+            logger.error("Refresh IOException :" + e.getMessage());
+        } finally {
+            ErrorContext.instance().reset();
+        }
+    }
+
+    /**
+     * 清理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
+     *
+     * @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);
+        }
+    }
+
+    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);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 清理selectKey
+     *
+     * @param list
+     * @param namespace
+     */
+    private void cleanKeyGenerators(List<XNode> list, String namespace) {
+        for (XNode context : list) {
+            String id = context.getStringAttribute("id");
+            configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
+            configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
+        }
+    }
+
+    /**
+     * 清理sql节点缓存
+     *
+     * @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);
+        }
+    }
+
+}

+ 5 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/spring/MybatisSqlSessionFactoryBean.java

@@ -569,6 +569,11 @@ public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFacto
         // TODO 设置全局参数属性
         globalConfig.signGlobalConfig(sqlSessionFactory);
         if (!isEmpty(this.mapperLocations)) {
+            if (globalConfig.isRefresh()) {
+                //TODO 设置自动刷新配置 减少配置
+                new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
+                    2, true);
+            }
             for (Resource mapperLocation : this.mapperLocations) {
                 if (mapperLocation == null) {
                     continue;

+ 71 - 0
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/MybatisMapperRefreshTest.java

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2011-2020, hubin (jobob@qq.com).
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.baomidou.mybatisplus.test;
+
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import com.baomidou.mybatisplus.plugins.pagination.Pagination;
+import com.baomidou.mybatisplus.spring.MybatisMapperRefresh;
+import com.baomidou.mybatisplus.test.mysql.mapper.UserMapper;
+import com.baomidou.mybatisplus.toolkit.SystemClock;
+
+/**
+ * <p>
+ * 切莫用于生产环境(后果自负)<br>
+ * Mybatis 映射文件热加载(发生变动后自动重新加载).<br>
+ * 方便开发时使用,不用每次修改xml文件后都要去重启应用.<br>
+ * </p>
+ *
+ * @author nieqiurong
+ * @Date 2016-08-25
+ */
+public class MybatisMapperRefreshTest extends CrudTest {
+
+
+    /**
+     * 测试 Mybatis XML 修改自动刷新
+     */
+    public static void main(String[] args) throws Exception {
+        Resource[] resource = new ClassPathResource[]{new ClassPathResource("mysql/UserMapper.xml")};
+        SqlSessionFactory sessionFactory = new CrudTest().sqlSessionFactory();
+        new MybatisMapperRefresh(resource, sessionFactory, 0, 5, true);
+        boolean isReturn = false;
+        SqlSession session = null;
+        while (!isReturn) {
+            try {
+                session = sessionFactory.openSession();
+                UserMapper userMapper = session.getMapper(UserMapper.class);
+                userMapper.selectListRow(new Pagination(1, 10));
+                resource[0].getFile().setLastModified(SystemClock.now());
+                session.commit();
+                session.close();
+                Thread.sleep(5000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                if (session != null) {
+                    session.close();
+                }
+                Thread.sleep(5000);
+            }
+        }
+        System.exit(0);
+    }
+
+}

+ 12 - 0
mybatis-plus-support/src/main/java/com/baomidou/mybatisplus/entity/GlobalConfiguration.java

@@ -90,6 +90,10 @@ public class GlobalConfiguration implements Serializable {
      * 字段验证策略
      */
     private FieldStrategy fieldStrategy = FieldStrategy.NOT_NULL;
+    /**
+     * 是否刷新mapper
+     */
+    private boolean isRefresh = false;
     /**
      * 是否大写命名
      */
@@ -219,6 +223,14 @@ public class GlobalConfiguration implements Serializable {
         this.fieldStrategy = FieldStrategy.getFieldStrategy(fieldStrategy);
     }
 
+    public boolean isRefresh() {
+        return isRefresh;
+    }
+
+    public void setRefresh(boolean refresh) {
+        this.isRefresh = refresh;
+    }
+
     public Set<String> getMapperRegistryCache() {
         return mapperRegistryCache;
     }

+ 4 - 0
mybatis-plus-support/src/main/java/com/baomidou/mybatisplus/toolkit/GlobalConfigUtils.java

@@ -163,6 +163,10 @@ public class GlobalConfigUtils {
         return getGlobalConfig(configuration).getFieldStrategy();
     }
 
+    public static boolean isRefresh(Configuration configuration) {
+        return getGlobalConfig(configuration).isRefresh();
+    }
+
     public static Set<String> getMapperRegistryCache(Configuration configuration) {
         return getGlobalConfig(configuration).getMapperRegistryCache();
     }