mybatis mapper xml文件热部署

针对网上关于mybatis mapper xml文件的热部署进行一个总结:

第一种方式:只要改动相应的mapper xml文件就全部扫描

第二种方式:只针对修改了的mapper xml文件进行重新加载

下面就针对这两种方式详细介绍

方式一:

创建一个MapperRefresh的java工具类

    package com.thinkgem.jeesite.mybatis.thread; 
      
    import java.io.File;  
    import java.io.FileInputStream;  
    import java.io.FileNotFoundException;  
    import java.io.InputStream;  
    import java.lang.reflect.Field;  
    import java.util.ArrayList;  
    import java.util.HashMap;  
    import java.util.List;  
    import java.util.Map;  
    import java.util.Properties;  
    import java.util.Set;  
      
    import org.apache.commons.lang3.StringUtils;  
    import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
    import org.apache.ibatis.executor.ErrorContext;  
    import org.apache.ibatis.session.Configuration;  
    import org.apache.log4j.Logger;  
    import org.springframework.core.NestedIOException;  
    import org.springframework.core.io.Resource;  
      
    import com.google.common.collect.Sets;  
      
    /** 
     * 刷新MyBatis Mapper XML 线程 
     * @author ThinkGem 
     * @version 2016-5-29 
     */  
    public class MapperRefresh implements java.lang.Runnable {  
      
        public static Logger log = Logger.getLogger(MapperRefresh.class);  
      
        private static String filename = "/mybatis-refresh.properties";  
        private static Properties prop = new Properties();  
      
        private static boolean enabled;         // 是否启用Mapper刷新线程功能  
        private static boolean refresh;         // 刷新启用后,是否启动了刷新线程  
          
        private Set location;         // Mapper实际资源路径  
          
        private Resource[] mapperLocations;     // Mapper资源路径  
        private Configuration configuration;        // MyBatis配置对象  
          
        private Long beforeTime = 0L;           // 上一次刷新时间  
        private static int delaySeconds;        // 延迟刷新秒数  
        private static int sleepSeconds;        // 休眠时间  
        private static String mappingPath;      // xml文件夹匹配字符串,需要根据需要修改  
      
        static {  
              
            try {  
                prop.load(MapperRefresh.class.getResourceAsStream(filename));  
            } catch (Exception e) {  
                e.printStackTrace();  
                System.out.println("Load mybatis-refresh “"+filename+"” file error.");  
            }  
      
            enabled = "true".equalsIgnoreCase(getPropString("enabled"));  
              
            delaySeconds = getPropInt("delaySeconds");  
            sleepSeconds = getPropInt("sleepSeconds");  
            mappingPath = getPropString("mappingPath");  
      
            delaySeconds = delaySeconds == 0 ? 50 : delaySeconds;  
            sleepSeconds = sleepSeconds == 0 ? 3 : sleepSeconds;  
            mappingPath = StringUtils.isBlank(mappingPath) ? "mappings" : mappingPath;  
      
            log.debug("[enabled] " + enabled);  
            log.debug("[delaySeconds] " + delaySeconds);  
            log.debug("[sleepSeconds] " + sleepSeconds);  
            log.debug("[mappingPath] " + mappingPath);  
        }  
      
        public static boolean isRefresh() {  
            return refresh;  
        }  
      
        public MapperRefresh(Resource[] mapperLocations, Configuration configuration) {  
            this.mapperLocations = mapperLocations;  
            this.configuration = configuration;  
        }  
      
        @Override  
        public void run() {  
      
            beforeTime = System.currentTimeMillis();  
      
            log.debug("[location] " + location);  
            log.debug("[configuration] " + configuration);  
      
            if (enabled) {  
                // 启动刷新线程  
                final MapperRefresh runnable = this;  
                new Thread(new java.lang.Runnable() {  
                    @Override  
                    public void run() {  
                          
                        if (location == null){  
                            location = Sets.newHashSet();  
                            log.debug("MapperLocation's length:" + mapperLocations.length);  
                            for (Resource mapperLocation : mapperLocations) {  
                                String s = mapperLocation.toString().replaceAll("\\\\", "/");  
                                s = s.substring("file [".length(), s.lastIndexOf(mappingPath) + mappingPath.length());  
                                if (!location.contains(s)) {  
                                    location.add(s);  
                                    log.debug("Location:" + s);  
                                }  
                            }  
                            log.debug("Locarion's size:" + location.size());  
                        }  
      
                        try {  
                            Thread.sleep(delaySeconds * 1000);  
                        } catch (InterruptedException e2) {  
                            e2.printStackTrace();  
                        }  
                        refresh = true;  
      
                        System.out.println("========= Enabled refresh mybatis mapper =========");  
      
                        while (true) {  
                            try {  
                                for (String s : location) {  
                                    runnable.refresh(s, beforeTime);  
                                }  
                            } catch (Exception e1) {  
                                e1.printStackTrace();  
                            }  
                            try {  
                                Thread.sleep(sleepSeconds * 1000);  
                            } catch (InterruptedException e) {  
                                e.printStackTrace();  
                            }  
      
                        }  
                    }  
                }, "MyBatis-Mapper-Refresh").start();  
            }  
        }  
      
        /** 
         * 执行刷新 
         * @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 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);  
                        }  
                    }  
                      
                    // 清理已加载的资源标识,方便让它重新加载。  
                    Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");  
                    loadedResourcesField.setAccessible(true);  
                    Set loadedResourcesSet = ((Set)loadedResourcesField.get(configuration));  
                    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();  
                }  
                System.out.println("Refresh file: " + mappingPath + StringUtils.substringAfterLast(fileList.get(i).getAbsolutePath(), mappingPath));  
                if (log.isDebugEnabled()) {  
                    log.debug("Refresh file: " + fileList.get(i).getAbsolutePath());  
                    log.debug("Refresh filename: " + fileList.get(i).getName());  
                }  
            }  
            // 如果刷新了文件,则修改刷新时间,否则不修改  
            if (fileList.size() > 0) {  
                this.beforeTime = refrehTime;  
            }  
        }  
          
        /** 
         * 获取需要刷新的文件列表 
         * @param dir 目录 
         * @param beforeTime 上次刷新时间 
         * @return 刷新文件列表 
         */  
        private List getRefreshFile(File dir, Long beforeTime) {  
            List fileList = new ArrayList();  
      
            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 {  
                        System.out.println("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 extends HashMap {  
      
            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 m) {  
                super(m);  
                this.name = name;  
            }  
      
            @SuppressWarnings("unchecked")  
            public V put(String key, V value) {  
                // ThinkGem 如果现在状态为刷新,则刷新(先删除后添加)  
                if (MapperRefresh.isRefresh()) {  
                    remove(key);  
                    MapperRefresh.log.debug("refresh key:" + key.substring(key.lastIndexOf(".") + 1));  
                }  
                // ThinkGem end  
                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;  
                }  
            }  
        }  
    }  

MyBatis有几个不太好的地方,是当实体类别名重名的时候,Mapper XML有错误的时候,系统启动时会一直等待无法正常启动(其实是加载失败后又重新加载,进入了死循环),这里我也顺便重写下SqlSessionFactoryBean.java文件,解决这个问题,在这个文件里也加入启动上面写的线程类:

 

 1、修改实体类重名的时候抛出并打印异常,否则系统会一直递归造成无法启动。

 2、MapperXML有错误的时候抛出并打印异常,否则系统会一直递归造成无法启动。

 3、加入启动MapperRefresh.java线程服务。

    /** 
     *    Copyright 2010-2015 the original author or authors. 
     * 
     *    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 
     * 
     *       http://www.apache.org/licenses/LICENSE-2.0 
     * 
     *    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.thinkgem.jeesite.mybatis.spring;  
      
    import static org.springframework.util.Assert.notNull;  
    import static org.springframework.util.ObjectUtils.isEmpty;  
    import static org.springframework.util.StringUtils.hasLength;  
    import static org.springframework.util.StringUtils.tokenizeToStringArray;  
      
    import java.io.IOException;  
    import java.sql.SQLException;  
    import java.util.Properties;  
      
    import javax.sql.DataSource;  
      
    import org.apache.ibatis.builder.xml.XMLConfigBuilder;  
    import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
    import org.apache.ibatis.executor.ErrorContext;  
    import org.apache.ibatis.logging.Log;  
    import org.apache.ibatis.logging.LogFactory;  
    import org.apache.ibatis.mapping.DatabaseIdProvider;  
    import org.apache.ibatis.mapping.Environment;  
    import org.apache.ibatis.plugin.Interceptor;  
    import org.apache.ibatis.reflection.factory.ObjectFactory;  
    import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;  
    import org.apache.ibatis.session.Configuration;  
    import org.apache.ibatis.session.SqlSessionFactory;  
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
    import org.apache.ibatis.transaction.TransactionFactory;  
    import org.apache.ibatis.type.TypeHandler;  
    import org.mybatis.spring.transaction.SpringManagedTransactionFactory;  
    import org.springframework.beans.factory.FactoryBean;  
    import org.springframework.beans.factory.InitializingBean;  
    import org.springframework.context.ApplicationEvent;  
    import org.springframework.context.ApplicationListener;  
    import org.springframework.context.ConfigurableApplicationContext;  
    import org.springframework.context.event.ContextRefreshedEvent;  
    import org.springframework.core.NestedIOException;  
    import org.springframework.core.io.Resource;  
    import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;  
      
    import com.thinkgem.jeesite.common.mybatis.thread.MapperRefresh;  
      
    /** 
     * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}. 
     * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context; 
     * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection. 
     * 
     * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction 
     * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions 
     * which span multiple databases or when container managed transactions (CMT) are being used. 
     * 
     * @author Putthibong Boonbong 
     * @author Hunter Presnall 
     * @author Eduardo Macarron 
     *  
     * @see #setConfigLocation 
     * @see #setDataSource 
     * @version $Id$ 
     * @modify ThinkGem 2016-5-24 来自 MyBatisSpring1.2.3版本 
     */  
    public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {  
      
      private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);  
      
      private Resource configLocation;  
      
      private Resource[] mapperLocations;  
      
      private DataSource dataSource;  
      
      private TransactionFactory transactionFactory;  
      
      private Properties configurationProperties;  
      
      private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
      
      private SqlSessionFactory sqlSessionFactory;  
      
      //EnvironmentAware requires spring 3.1  
      private String environment = SqlSessionFactoryBean.class.getSimpleName();  
      
      private boolean failFast;  
      
      private Interceptor[] plugins;  
      
      private TypeHandler[] typeHandlers;  
      
      private String typeHandlersPackage;  
      
      private Class[] typeAliases;  
      
      private String typeAliasesPackage;  
      
      private Class typeAliasesSuperType;  
      
      //issue #19. No default provider.  
      private DatabaseIdProvider databaseIdProvider;  
      
      private ObjectFactory objectFactory;  
      
      private ObjectWrapperFactory objectWrapperFactory;  
      
      /** 
       * Sets the ObjectFactory. 
       *  
       * @since 1.1.2 
       * @param objectFactory 
       */  
      public void setObjectFactory(ObjectFactory objectFactory) {  
        this.objectFactory = objectFactory;  
      }  
      
      /** 
       * Sets the ObjectWrapperFactory. 
       *  
       * @since 1.1.2 
       * @param objectWrapperFactory 
       */  
      public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {  
        this.objectWrapperFactory = objectWrapperFactory;  
      }  
      
      /** 
       * Gets the DatabaseIdProvider 
       * 
       * @since 1.1.0 
       * @return 
       */  
      public DatabaseIdProvider getDatabaseIdProvider() {  
        return databaseIdProvider;  
      }  
      
      /** 
       * Sets the DatabaseIdProvider. 
       * As of version 1.2.2 this variable is not initialized by default.  
       * 
       * @since 1.1.0 
       * @param databaseIdProvider 
       */  
      public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {  
        this.databaseIdProvider = databaseIdProvider;  
      }  
      
      /** 
       * Mybatis plugin list. 
       * 
       * @since 1.0.1 
       * 
       * @param plugins list of plugins 
       * 
       */  
      public void setPlugins(Interceptor[] plugins) {  
        this.plugins = plugins;  
      }  
      
      /** 
       * Packages to search for type aliases. 
       * 
       * @since 1.0.1 
       * 
       * @param typeAliasesPackage package to scan for domain objects 
       * 
       */  
      public void setTypeAliasesPackage(String typeAliasesPackage) {  
        this.typeAliasesPackage = typeAliasesPackage;  
      }  
      
      /** 
       * Super class which domain objects have to extend to have a type alias created. 
       * No effect if there is no package to scan configured. 
       * 
       * @since 1.1.2 
       * 
       * @param typeAliasesSuperType super class for domain objects 
       * 
       */  
      public void setTypeAliasesSuperType(Class typeAliasesSuperType) {  
        this.typeAliasesSuperType = typeAliasesSuperType;  
      }  
      
      /** 
       * Packages to search for type handlers. 
       * 
       * @since 1.0.1 
       * 
       * @param typeHandlersPackage package to scan for type handlers 
       * 
       */  
      public void setTypeHandlersPackage(String typeHandlersPackage) {  
        this.typeHandlersPackage = typeHandlersPackage;  
      }  
      
      /** 
       * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes} 
       * 
       * @since 1.0.1 
       * 
       * @param typeHandlers Type handler list 
       */  
      public void setTypeHandlers(TypeHandler[] typeHandlers) {  
        this.typeHandlers = typeHandlers;  
      }  
      
      /** 
       * List of type aliases to register. They can be annotated with {@code Alias} 
       * 
       * @since 1.0.1 
       * 
       * @param typeAliases Type aliases list 
       */  
      public void setTypeAliases(Class[] typeAliases) {  
        this.typeAliases = typeAliases;  
      }  
      
      /** 
       * If true, a final check is done on Configuration to assure that all mapped 
       * statements are fully loaded and there is no one still pending to resolve 
       * includes. Defaults to false. 
       * 
       * @since 1.0.1 
       * 
       * @param failFast enable failFast 
       */  
      public void setFailFast(boolean failFast) {  
        this.failFast = failFast;  
      }  
      
      /** 
       * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is 
       * "WEB-INF/mybatis-configuration.xml". 
       */  
      public void setConfigLocation(Resource configLocation) {  
        this.configLocation = configLocation;  
      }  
      
      /** 
       * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} 
       * configuration at runtime. 
       * 
       * This is an alternative to specifying "" entries in an MyBatis config file. 
       * This property being based on Spring's resource abstraction also allows for specifying 
       * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml". 
       */  
      public void setMapperLocations(Resource[] mapperLocations) {  
        this.mapperLocations = mapperLocations;  
      }  
      
      /** 
       * Set optional properties to be passed into the SqlSession configuration, as alternative to a 
       * {@code } tag in the configuration xml file. This will be used to 
       * resolve placeholders in the config file. 
       */  
      public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {  
        this.configurationProperties = sqlSessionFactoryProperties;  
      }  
      
      /** 
       * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} 
       * should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same 
       * JNDI DataSource for both. 
       * 
       * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code 
       * accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. 
       * 
       * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not 
       * a {@code TransactionAwareDataSourceProxy}. Only data access code may work with 
       * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the 
       * underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} 
       * passed in, it will be unwrapped to extract its target {@code DataSource}. 
       * 
       */  
      public void setDataSource(DataSource dataSource) {  
        if (dataSource instanceof TransactionAwareDataSourceProxy) {  
          // If we got a TransactionAwareDataSourceProxy, we need to perform  
          // transactions for its underlying target DataSource, else data  
          // access code won't see properly exposed transactions (i.e.  
          // transactions for the target DataSource).  
          this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();  
        } else {  
          this.dataSource = dataSource;  
        }  
      }  
      
      /** 
       * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}. 
       * 
       * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By 
       * default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances. 
       * 
       */  
      public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {  
        this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;  
      }  
      
      /** 
       * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory} 
       * 
       * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: 
       * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction, 
       * SqlSession operations will execute SQL statements non-transactionally. 
       * 
       * It is strongly recommended to use the default {@code TransactionFactory}. If not used, any 
       * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if 
       * a transaction is active. 
       * 
       * @see SpringManagedTransactionFactory 
       * @param transactionFactory the MyBatis TransactionFactory 
       */  
      public void setTransactionFactory(TransactionFactory transactionFactory) {  
        this.transactionFactory = transactionFactory;  
      }  
      
      /** 
       * NOTE: This class overrides any {@code Environment} you have set in the MyBatis 
       * config file. This is used only as a placeholder name. The default value is 
       * {@code SqlSessionFactoryBean.class.getSimpleName()}. 
       * 
       * @param environment the environment name 
       */  
      public void setEnvironment(String environment) {  
        this.environment = environment;  
      }  
      
      /** 
       * {@inheritDoc} 
       */  
      @Override  
      public void afterPropertiesSet() throws Exception {  
        notNull(dataSource, "Property 'dataSource' is required");  
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");  
      
        this.sqlSessionFactory = buildSqlSessionFactory();  
      }  
      
      /** 
       * Build a {@code SqlSessionFactory} instance. 
       * 
       * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a 
       * {@code SqlSessionFactory} instance based on an Reader. 
       * 
       * @return SqlSessionFactory 
       * @throws IOException if loading the config file failed 
       */  
      protected SqlSessionFactory buildSqlSessionFactory() throws IOException {  
      
        Configuration configuration;  
      
        XMLConfigBuilder xmlConfigBuilder = null;  
        if (this.configLocation != null) {  
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);  
          configuration = xmlConfigBuilder.getConfiguration();  
        } else {  
          if (LOGGER.isDebugEnabled()) {  
            LOGGER.debug("Property 'configLocation' not specified, using default MyBatis Configuration");  
          }  
          configuration = new Configuration();  
          configuration.setVariables(this.configurationProperties);  
        }  
      
        if (this.objectFactory != null) {  
          configuration.setObjectFactory(this.objectFactory);  
        }  
      
        if (this.objectWrapperFactory != null) {  
          configuration.setObjectWrapperFactory(this.objectWrapperFactory);  
        }  
      
        if (hasLength(this.typeAliasesPackage)) {  
          String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,  
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
          for (String packageToScan : typeAliasPackageArray) {  
            // ThinkGem 修改实体类重名的时候抛出并打印异常,否则系统会一直递归造成无法启动  
            try {  
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,  
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);  
            } catch (Exception ex) {  
                LOGGER.error("Scanned package: '" + packageToScan + "' for aliases", ex);  
                throw new NestedIOException("Scanned package: '" + packageToScan + "' for aliases", ex);  
            } finally {  
                ErrorContext.instance().reset();  
            }  
            // ThinkGem end  
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");  
            }  
          }  
        }  
      
        if (!isEmpty(this.typeAliases)) {  
          for (Class typeAlias : this.typeAliases) {  
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);  
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Registered type alias: '" + typeAlias + "'");  
            }  
          }  
        }  
      
        if (!isEmpty(this.plugins)) {  
          for (Interceptor plugin : this.plugins) {  
            configuration.addInterceptor(plugin);  
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Registered plugin: '" + plugin + "'");  
            }  
          }  
        }  
      
        if (hasLength(this.typeHandlersPackage)) {  
          String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,  
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
          for (String packageToScan : typeHandlersPackageArray) {  
            configuration.getTypeHandlerRegistry().register(packageToScan);  
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");  
            }  
          }  
        }  
      
        if (!isEmpty(this.typeHandlers)) {  
          for (TypeHandler typeHandler : this.typeHandlers) {  
            configuration.getTypeHandlerRegistry().register(typeHandler);  
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Registered type handler: '" + typeHandler + "'");  
            }  
          }  
        }  
      
        if (xmlConfigBuilder != null) {  
          try {  
            xmlConfigBuilder.parse();  
      
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");  
            }  
          } catch (Exception ex) {  
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);  
          } finally {  
            ErrorContext.instance().reset();  
          }  
        }  
      
        if (this.transactionFactory == null) {  
          this.transactionFactory = new SpringManagedTransactionFactory();  
        }  
      
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));  
      
        if (this.databaseIdProvider != null) {  
          try {  
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));  
          } catch (SQLException e) {  
            throw new NestedIOException("Failed getting a databaseId", e);  
          }  
        }  
      
        if (!isEmpty(this.mapperLocations)) {  
          for (Resource mapperLocation : this.mapperLocations) {  
            if (mapperLocation == null) {  
              continue;  
            }  
      
            try {  
              XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),  
                  configuration, mapperLocation.toString(), configuration.getSqlFragments());  
              xmlMapperBuilder.parse();  
            } catch (Exception e) {  
                // ThinkGem MapperXML有错误的时候抛出并打印异常,否则系统会一直递归造成无法启动  
                LOGGER.error("Failed to parse mapping resource: '" + mapperLocation + "'", e);  
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);  
            } finally {  
              ErrorContext.instance().reset();  
            }  
      
            if (LOGGER.isDebugEnabled()) {  
              LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");  
            }  
          }  
            
          // ThinkGem 启动刷新MapperXML定时器(有助于开发者调试)。  
          new MapperRefresh(this.mapperLocations, configuration).run();  
            
        } else {  
          if (LOGGER.isDebugEnabled()) {  
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");  
          }  
        }  
      
        return this.sqlSessionFactoryBuilder.build(configuration);  
      }  
      
      /** 
       * {@inheritDoc} 
       */  
      @Override  
      public SqlSessionFactory getObject() throws Exception {  
        if (this.sqlSessionFactory == null) {  
          afterPropertiesSet();  
        }  
      
        return this.sqlSessionFactory;  
      }  
      
      /** 
       * {@inheritDoc} 
       */  
      @Override  
      public Class getObjectType() {  
        return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();  
      }  
      
      /** 
       * {@inheritDoc} 
       */  
      @Override  
      public boolean isSingleton() {  
        return true;  
      }  
      
      /** 
       * {@inheritDoc} 
       */  
      @Override  
      public void onApplicationEvent(ApplicationEvent event) {  
        if (failFast && event instanceof ContextRefreshedEvent) {  
          // fail-fast -> check all statements are completed  
          this.sqlSessionFactory.getConfiguration().getMappedStatementNames();  
        }  
      }  
      
    }   
配置sqlSessionFactoryBean的时候就是用当前修改的这个用来覆盖mybatis-spring中的sqlSessionFactoryBean

      
          
              
              
              
              
          
最后再配置一个属性配置文件mybatis-refresh.properties

    #是否开启刷新线程  
    enabled=true  
    #延迟启动刷新程序的秒数  
    delaySeconds=60  
    #刷新扫描间隔的时长秒数  
    sleepSeconds=3  
    #扫描Mapper文件的资源路径  
    mappingPath=mappings  

方式二:

实现思路:使用定时器定时扫描mapper文件的改动,如果有改动则调用mapper文件的加载方法XMLMapperBuilder.parse()。

1.首先编写一个热加载mapper xml文件的java类

首先需要构建一个sqlSessionFactory对象,并指定mybatis的Configuration.xml配置文件路径,之后定时扫描并判断mapper文件是否有改动,如果有改动则重新加载。

    package zttc.itat.user.utils;  
      
    import java.io.IOException;  
    import java.io.InputStream;  
    import java.lang.reflect.Field;  
    import java.util.HashMap;  
    import java.util.Map;  
    import java.util.Set;  
    import java.util.concurrent.Executors;  
    import java.util.concurrent.ScheduledExecutorService;  
    import java.util.concurrent.TimeUnit;  
      
    import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
    import org.apache.ibatis.io.Resources;  
    import org.apache.ibatis.session.Configuration;  
    import org.apache.ibatis.session.SqlSessionFactory;  
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
    import org.apache.log4j.Logger;  
    import org.springframework.core.io.Resource;  
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
      
    /** 
     * mybatis的mapper文件有改动时,进行重新加载 
     * @author ycblus 
     * 
     */  
    public class SqlSessionCache {       
        private Logger log  = Logger.getLogger(SqlSessionCache.class);            
        private Resource[] mapperLocations;       
        private String packageSearchPath = "classpath*:zttc/itat/user/mapper/*.xml";       
        SqlSessionFactory sqlSessionFactory;  
        Configuration configuration;  
        private HashMap fileMapping = new HashMap();// 记录文件是否变化   
          
        {  
            String resource = "Configuration.xml";  
            InputStream inputStream;  
            try {  
                inputStream = Resources.getResourceAsStream(resource);  
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) ;  
                configuration = this.sqlSessionFactory.getConfiguration(); //扫描文件       
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
              
        }  
          
        public void refreshMapper() throws Exception{           
            try {                   
                try {                   
                    this.scanMapperXml();               
                    } catch (IOException e) {                   
                        log.error("packageSearchPath扫描包路径配置错误");                   
                        return;               
                    }  
                    Runnable runnable = new Runnable() {    
                      public void run() {    
                          // task to run goes here    
                          try {  
                          // 判断是否有文件发生了变化               
                            if (isChanged()) {                   
                                // 清理                   
                                this.removeConfig(configuration);                    
                                // 重新加载                   
                                for (Resource configLocation : mapperLocations) {                       
                                    try {                           
                                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(),  
                                                configuration, configLocation.toString(), configuration.getSqlFragments());                           
                                        xmlMapperBuilder.parse();                           
                                        log.info("mapper文件[" + configLocation.getFilename() + "]加载成功");                       
                                        } catch (IOException e) {                           
                                            log.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");                           
                                            continue;                       
                                        }                   
                                    }               
                                }   
                            } catch (Exception e) {  
                                // TODO Auto-generated catch block  
                                e.printStackTrace();  
                            }  
                     }   
                        
                      /**      * 清空Configuration中几个重要的缓存      * @param configuration      * @throws Exception      */       
                      private void removeConfig(Configuration configuration) throws Exception {           
                          Class classConfig = configuration.getClass();           
                          clearMap(classConfig, configuration, "mappedStatements");           
                          clearMap(classConfig, configuration, "caches");           
                          clearMap(classConfig, configuration, "resultMaps");           
                          clearMap(classConfig, configuration, "parameterMaps");           
                          clearMap(classConfig, configuration, "keyGenerators");           
                          clearMap(classConfig, configuration, "sqlFragments");            
                          clearSet(classConfig, configuration, "loadedResources");        
                      }        
                        
                      /**      * 判断文件是否发生了变化      * @param resource      * @return      * @throws IOException      */       
                      boolean isChanged() throws IOException {           
                          boolean flag = false;           
                          for (Resource resource : mapperLocations) {               
                              String resourceName = resource.getFilename();                            
                              boolean addFlag = !fileMapping.isEmpty() && !fileMapping.containsKey(resourceName);// 此为新增标识                            
                              // 修改文件:判断文件内容是否有变化               
                              Long compareFrame = fileMapping.get(resourceName);               
                              long lastFrame = resource.contentLength() + resource.lastModified();               
                              boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识   
                                
                              fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值  
                              // 新增或是修改时,存储文件               
                              if(addFlag || modifyFlag) {  
                                  flag = true;               
                              }           
                          }           
                          return flag;       
                      }   
                  };   
                  ScheduledExecutorService service = Executors    
                          .newSingleThreadScheduledExecutor();    
                  // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间    
                  service.scheduleAtFixedRate(runnable, 1, 10, TimeUnit.SECONDS);    
                                           
            } catch (Exception e) {               
                e.printStackTrace();           
            }       
        }            
        public void setPackageSearchPath(String packageSearchPath) {           
            this.packageSearchPath = packageSearchPath;       
        }       
          
        public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {           
            this.sqlSessionFactory = sqlSessionFactory;       
        }    
          
        /**      * 扫描xml文件所在的路径      * @throws IOException       */       
        private void scanMapperXml() throws IOException {           
            this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);       
        }        
          
        /**      * 清空Configuration中几个重要的缓存      * @param configuration      * @throws Exception      */       
        private void removeConfig(Configuration configuration) throws Exception {           
            Class classConfig = configuration.getClass();           
            clearMap(classConfig, configuration, "mappedStatements");           
            clearMap(classConfig, configuration, "caches");           
            clearMap(classConfig, configuration, "resultMaps");           
            clearMap(classConfig, configuration, "parameterMaps");           
            clearMap(classConfig, configuration, "keyGenerators");           
            clearMap(classConfig, configuration, "sqlFragments");            
            clearSet(classConfig, configuration, "loadedResources");        
        }        
          
        @SuppressWarnings("rawtypes")       
        private void clearMap(Class classConfig, Configuration configuration, String fieldName) throws Exception {           
            Field field = classConfig.getDeclaredField(fieldName);           
            field.setAccessible(true);           
            Map mapConfig = (Map) field.get(configuration);           
            mapConfig.clear();       
        }    
          
        @SuppressWarnings("rawtypes")       
        private void clearSet(Class classConfig, Configuration configuration, String fieldName) throws Exception {           
            Field field = classConfig.getDeclaredField(fieldName);           
            field.setAccessible(true);           
            Set setConfig = (Set) field.get(configuration);           
            setConfig.clear();       
        }       
          
        public static void main(String[] args) {  
            HashMap fileMapping = new HashMap();  
            boolean f = !fileMapping.isEmpty() && !fileMapping.containsKey("Hello.xml");  
            System.out.println(f);  
        }  
    }  

2.创建一mybatis配置文件configuration.xml文件

这个配置文件在这里只作为扫描配置使用,可以看到其他配置都没有,因为我是在spring里面统一配置了


 
     
         
         
     
 

3.最后再编写一个servlet,用于在服务器启动时加载前面的java类

package zttc.itat.user.servlet;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
 
import org.apache.log4j.Logger;
 
import zttc.itat.user.utils.SqlSessionCache;
 
/**
 * Servlet implementation class MapperReloadServlet
 *
 * when mybatis files changed,reload them 
 */
public class MapperReloadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
        
    /**
     * @see HttpServlet#HttpServlet()
     */
    public MapperReloadServlet() {
        super();
        // TODO Auto-generated constructor stub
    }
 
    public void init()throws ServletException
    {
      Logger logger = Logger.getLogger(this.getClass());
      logger.info("The mapper reload timer starting... ");
       
      try {
        new SqlSessionCache().refreshMapper();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }       
    }
}

此外,需在web.xml中加入这个servlet类,添加代码如下:

 


    MapperReloadServlet
    zttc.itat.user.servlet.MapperReloadServlet
    7
   

这样每次当mapper文件有改动时,就会重新加载。不过会把所有的mapper文件重新加载一遍,如果需要对指定文件进行加载也是可以的,需要修改下重新加载的类。

另外,服务器应该配置为reloadable="true",否则改动了东西也不会自动部署到服务上去的,但是这样有一个风险,就是reloadable="true"以后,如果服务器主机性能差的话,容易挂掉!。



你可能感兴趣的:(#,mybatis)