实现MyBatis Mapper XML文件增量动态刷新,自动加载,热加载,热部署

最初启动服务后Mapper XML文件,必须重启服务才能生效,这样就大大影响了我们的开发效率。

    网上同学们也有实现类似功能,但都是全部清空,全部刷新XML,这样硬件消耗比较严重,加载时间也比较长。我们只修改了几行SQL就没有必要全部加载,只需要加载修改的问题就行了。

    后来为了急需解决这个问题,进行修改MyBatis源码实现Mapper XML增量刷新,直接覆盖方式实现,使用classloader的加载机制优先加载,并应用到了jeesite中,但是经过MyBatis几次升级后,不得不需要重新修改,部署也麻烦,入侵性太强。

    周末有幸又重新研究下源代码将刷新部分,分离出来,实现MyBatis Mapper文件动态重新加载,只加载修改的文件,今天分享出来,不多说,看源码,注释很详细: 

Java代码  
  1. /** 
  2.  * Copyright (c) 2012-Now https://github.com/thinkgem/jeesite. 
  3.  */  
  4. package com.thinkgem.jeesite.mybatis.thread;  
  5.   
  6. import java.io.File;  
  7. import java.io.FileInputStream;  
  8. import java.io.FileNotFoundException;  
  9. import java.io.InputStream;  
  10. import java.lang.reflect.Field;  
  11. import java.util.ArrayList;  
  12. import java.util.HashMap;  
  13. import java.util.List;  
  14. import java.util.Map;  
  15. import java.util.Properties;  
  16. import java.util.Set;  
  17.   
  18. import org.apache.commons.lang3.StringUtils;  
  19. import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
  20. import org.apache.ibatis.executor.ErrorContext;  
  21. import org.apache.ibatis.session.Configuration;  
  22. import org.apache.log4j.Logger;  
  23. import org.springframework.core.NestedIOException;  
  24. import org.springframework.core.io.Resource;  
  25.   
  26. import com.google.common.collect.Sets;  
  27.   
  28. /** 
  29.  * 刷新MyBatis Mapper XML 线程 
  30.  * @author ThinkGem 
  31.  * @version 2016-5-29 
  32.  */  
  33. public class MapperRefresh implements java.lang.Runnable {  
  34.   
  35.     public static Logger log = Logger.getLogger(MapperRefresh.class);  
  36.   
  37.     private static String filename = "/mybatis-refresh.properties";  
  38.     private static Properties prop = new Properties();  
  39.   
  40.     private static boolean enabled;         // 是否启用Mapper刷新线程功能  
  41.     private static boolean refresh;         // 刷新启用后,是否启动了刷新线程  
  42.       
  43.     private Set location;         // Mapper实际资源路径  
  44.       
  45.     private Resource[] mapperLocations;     // Mapper资源路径  
  46.     private Configuration configuration;        // MyBatis配置对象  
  47.       
  48.     private Long beforeTime = 0L;           // 上一次刷新时间  
  49.     private static int delaySeconds;        // 延迟刷新秒数  
  50.     private static int sleepSeconds;        // 休眠时间  
  51.     private static String mappingPath;      // xml文件夹匹配字符串,需要根据需要修改  
  52.   
  53.     static {  
  54.           
  55.         try {  
  56.             prop.load(MapperRefresh.class.getResourceAsStream(filename));  
  57.         } catch (Exception e) {  
  58.             e.printStackTrace();  
  59.             System.out.println("Load mybatis-refresh “"+filename+"” file error.");  
  60.         }  
  61.   
  62.         enabled = "true".equalsIgnoreCase(getPropString("enabled"));  
  63.           
  64.         delaySeconds = getPropInt("delaySeconds");  
  65.         sleepSeconds = getPropInt("sleepSeconds");  
  66.         mappingPath = getPropString("mappingPath");  
  67.   
  68.         delaySeconds = delaySeconds == 0 ? 50 : delaySeconds;  
  69.         sleepSeconds = sleepSeconds == 0 ? 3 : sleepSeconds;  
  70.         mappingPath = StringUtils.isBlank(mappingPath) ? "mappings" : mappingPath;  
  71.   
  72.         log.debug("[enabled] " + enabled);  
  73.         log.debug("[delaySeconds] " + delaySeconds);  
  74.         log.debug("[sleepSeconds] " + sleepSeconds);  
  75.         log.debug("[mappingPath] " + mappingPath);  
  76.     }  
  77.   
  78.     public static boolean isRefresh() {  
  79.         return refresh;  
  80.     }  
  81.   
  82.     public MapperRefresh(Resource[] mapperLocations, Configuration configuration) {  
  83.         this.mapperLocations = mapperLocations;  
  84.         this.configuration = configuration;  
  85.     }  
  86.   
  87.     @Override  
  88.     public void run() {  
  89.   
  90.         beforeTime = System.currentTimeMillis();  
  91.   
  92.         log.debug("[location] " + location);  
  93.         log.debug("[configuration] " + configuration);  
  94.   
  95.         if (enabled) {  
  96.             // 启动刷新线程  
  97.             final MapperRefresh runnable = this;  
  98.             new Thread(new java.lang.Runnable() {  
  99.                 @Override  
  100.                 public void run() {  
  101.                       
  102.                     if (location == null){  
  103.                         location = Sets.newHashSet();  
  104.                         log.debug("MapperLocation's length:" + mapperLocations.length);  
  105.                         for (Resource mapperLocation : mapperLocations) {  
  106.                             String s = mapperLocation.toString().replaceAll("\\\\", "/");  
  107.                             s = s.substring("file [".length(), s.lastIndexOf(mappingPath) + mappingPath.length());  
  108.                             if (!location.contains(s)) {  
  109.                                 location.add(s);  
  110.                                 log.debug("Location:" + s);  
  111.                             }  
  112.                         }  
  113.                         log.debug("Locarion's size:" + location.size());  
  114.                     }  
  115.   
  116.                     try {  
  117.                         Thread.sleep(delaySeconds * 1000);  
  118.                     } catch (InterruptedException e2) {  
  119.                         e2.printStackTrace();  
  120.                     }  
  121.                     refresh = true;  
  122.   
  123.                     System.out.println("========= Enabled refresh mybatis mapper =========");  
  124.   
  125.                     while (true) {  
  126.                         try {  
  127.                             for (String s : location) {  
  128.                                 runnable.refresh(s, beforeTime);  
  129.                             }  
  130.                         } catch (Exception e1) {  
  131.                             e1.printStackTrace();  
  132.                         }  
  133.                         try {  
  134.                             Thread.sleep(sleepSeconds * 1000);  
  135.                         } catch (InterruptedException e) {  
  136.                             e.printStackTrace();  
  137.                         }  
  138.   
  139.                     }  
  140.                 }  
  141.             }, "MyBatis-Mapper-Refresh").start();  
  142.         }  
  143.     }  
  144.   
  145.     /** 
  146.      * 执行刷新 
  147.      * @param filePath 刷新目录 
  148.      * @param beforeTime 上次刷新时间 
  149.      * @throws NestedIOException 解析异常 
  150.      * @throws FileNotFoundException 文件未找到 
  151.      * @author ThinkGem 
  152.      */  
  153.     @SuppressWarnings({ "rawtypes""unchecked" })  
  154.     private void refresh(String filePath, Long beforeTime) throws Exception {  
  155.   
  156.         // 本次刷新时间  
  157.         Long refrehTime = System.currentTimeMillis();  
  158.   
  159.         // 获取需要刷新的Mapper文件列表  
  160.         List fileList = this.getRefreshFile(new File(filePath), beforeTime);  
  161.         if (fileList.size() > 0) {  
  162.             log.debug("Refresh file: " + fileList.size());  
  163.         }  
  164.         for (int i = 0; i < fileList.size(); i++) {  
  165.             InputStream inputStream = new FileInputStream(fileList.get(i));  
  166.             String resource = fileList.get(i).getAbsolutePath();  
  167.             try {  
  168.                   
  169.                 // 清理原有资源,更新为自己的StrictMap方便,增量重新加载  
  170.                 String[] mapFieldNames = new String[]{  
  171.                     "mappedStatements""caches",  
  172.                     "resultMaps""parameterMaps",  
  173.                     "keyGenerators""sqlFragments"  
  174.                 };  
  175.                 for (String fieldName : mapFieldNames){  
  176.                     Field field = configuration.getClass().getDeclaredField(fieldName);  
  177.                     field.setAccessible(true);  
  178.                     Map map = ((Map)field.get(configuration));  
  179.                     if (!(map instanceof StrictMap)){  
  180.                         Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");  
  181.                         for (Object key : map.keySet()){  
  182.                             try {  
  183.                                 newMap.put(key, map.get(key));  
  184.                             }catch(IllegalArgumentException ex){  
  185.                                 newMap.put(key, ex.getMessage());  
  186.                             }  
  187.                         }  
  188.                         field.set(configuration, newMap);  
  189.                     }  
  190.                 }  
  191.                   
  192.                 // 清理已加载的资源标识,方便让它重新加载。  
  193.                 Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");  
  194.                 loadedResourcesField.setAccessible(true);  
  195.                 Set loadedResourcesSet = ((Set)loadedResourcesField.get(configuration));  
  196.                 loadedResourcesSet.remove(resource);  
  197.                   
  198.                 //重新编译加载资源文件。  
  199.                 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration,   
  200.                         resource, configuration.getSqlFragments());  
  201.                 xmlMapperBuilder.parse();  
  202.             } catch (Exception e) {  
  203.                 throw new NestedIOException("Failed to parse mapping resource: '" + resource + "'", e);  
  204.             } finally {  
  205.                 ErrorContext.instance().reset();  
  206.             }  
  207.             System.out.println("Refresh file: " + mappingPath + StringUtils.substringAfterLast(fileList.get(i).getAbsolutePath(), mappingPath));  
  208.             if (log.isDebugEnabled()) {  
  209.                 log.debug("Refresh file: " + fileList.get(i).getAbsolutePath());  
  210.                 log.debug("Refresh filename: " + fileList.get(i).getName());  
  211.             }  
  212.         }  
  213.         // 如果刷新了文件,则修改刷新时间,否则不修改  
  214.         if (fileList.size() > 0) {  
  215.             this.beforeTime = refrehTime;  
  216.         }  
  217.     }  
  218.       
  219.     /** 
  220.      * 获取需要刷新的文件列表 
  221.      * @param dir 目录 
  222.      * @param beforeTime 上次刷新时间 
  223.      * @return 刷新文件列表 
  224.      */  
  225.     private List getRefreshFile(File dir, Long beforeTime) {  
  226.         List fileList = new ArrayList();  
  227.   
  228.         File[] files = dir.listFiles();  
  229.         if (files != null) {  
  230.             for (int i = 0; i < files.length; i++) {  
  231.                 File file = files[i];  
  232.                 if (file.isDirectory()) {  
  233.                     fileList.addAll(this.getRefreshFile(file, beforeTime));  
  234.                 } else if (file.isFile()) {  
  235.                     if (this.checkFile(file, beforeTime)) {  
  236.                         fileList.add(file);  
  237.                     }  
  238.                 } else {  
  239.                     System.out.println("Error file." + file.getName());  
  240.                 }  
  241.             }  
  242.         }  
  243.         return fileList;  
  244.     }  
  245.   
  246.     /** 
  247.      * 判断文件是否需要刷新 
  248.      * @param file 文件 
  249.      * @param beforeTime 上次刷新时间 
  250.      * @return 需要刷新返回true,否则返回false 
  251.      */  
  252.     private boolean checkFile(File file, Long beforeTime) {  
  253.         if (file.lastModified() > beforeTime) {  
  254.             return true;  
  255.         }  
  256.         return false;  
  257.     }  
  258.   
  259.     /** 
  260.      * 获取整数属性 
  261.      * @param key 
  262.      * @return 
  263.      */  
  264.     private static int getPropInt(String key) {  
  265.         int i = 0;  
  266.         try {  
  267.             i = Integer.parseInt(getPropString(key));  
  268.         } catch (Exception e) {  
  269.         }  
  270.         return i;  
  271.     }  
  272.   
  273.     /** 
  274.      * 获取字符串属性 
  275.      * @param key 
  276.      * @return 
  277.      */  
  278.     private static String getPropString(String key) {  
  279.         return prop == null ? null : prop.getProperty(key);  
  280.     }  
  281.   
  282.     /** 
  283.      * 重写 org.apache.ibatis.session.Configuration.StrictMap 类 
  284.      * 来自 MyBatis3.4.0版本,修改 put 方法,允许反复 put更新。 
  285.      */  
  286.     public static class StrictMap extends HashMap {  
  287.   
  288.         private static final long serialVersionUID = -4950446264854982944L;  
  289.         private String name;  
  290.   
  291.         public StrictMap(String name, int initialCapacity, float loadFactor) {  
  292.             super(initialCapacity, loadFactor);  
  293.             this.name = name;  
  294.         }  
  295.   
  296.         public StrictMap(String name, int initialCapacity) {  
  297.             super(initialCapacity);  
  298.             this.name = name;  
  299.         }  
  300.   
  301.         public StrictMap(String name) {  
  302.             super();  
  303.             this.name = name;  
  304.         }  
  305.   
  306.         public StrictMap(String name, Mapextends V> m) {  
  307.             super(m);  
  308.             this.name = name;  
  309.         }  
  310.   
  311.         @SuppressWarnings("unchecked")  
  312.         public V put(String key, V value) {  
  313.             // ThinkGem 如果现在状态为刷新,则刷新(先删除后添加)  
  314.             if (MapperRefresh.isRefresh()) {  
  315.                 remove(key);  
  316.                 MapperRefresh.log.debug("refresh key:" + key.substring(key.lastIndexOf(".") + 1));  
  317.             }  
  318.             // ThinkGem end  
  319.             if (containsKey(key)) {  
  320.                 throw new IllegalArgumentException(name + " already contains value for " + key);  
  321.             }  
  322.             if (key.contains(".")) {  
  323.                 final String shortKey = getShortName(key);  
  324.                 if (super.get(shortKey) == null) {  
  325.                     super.put(shortKey, value);  
  326.                 } else {  
  327.                     super.put(shortKey, (V) new Ambiguity(shortKey));  
  328.                 }  
  329.             }  
  330.             return super.put(key, value);  
  331.         }  
  332.   
  333.         public V get(Object key) {  
  334.             V value = super.get(key);  
  335.             if (value == null) {  
  336.                 throw new IllegalArgumentException(name + " does not contain value for " + key);  
  337.             }  
  338.             if (value instanceof Ambiguity) {  
  339.                 throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name  
  340.                         + " (try using the full name including the namespace, or rename one of the entries)");  
  341.             }  
  342.             return value;  
  343.         }  
  344.   
  345.         private String getShortName(String key) {  
  346.             final String[] keyparts = key.split("\\.");  
  347.             return keyparts[keyparts.length - 1];  
  348.         }  
  349.   
  350.         protected static class Ambiguity {  
  351.             private String subject;  
  352.   
  353.             public Ambiguity(String subject) {  
  354.                 this.subject = subject;  
  355.             }  
  356.   
  357.             public String getSubject() {  
  358.                 return subject;  
  359.             }  
  360.         }  
  361.     }  
  362. }  

 

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

 

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

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

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

Java代码  
  1. /** 
  2.  *    Copyright 2010-2015 the original author or authors. 
  3.  * 
  4.  *    Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  *    you may not use this file except in compliance with the License. 
  6.  *    You may obtain a copy of the License at 
  7.  * 
  8.  *       http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  *    Unless required by applicable law or agreed to in writing, software 
  11.  *    distributed under the License is distributed on an "AS IS" BASIS, 
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  *    See the License for the specific language governing permissions and 
  14.  *    limitations under the License. 
  15.  */  
  16. package com.thinkgem.jeesite.mybatis.spring;  
  17.   
  18. import static org.springframework.util.Assert.notNull;  
  19. import static org.springframework.util.ObjectUtils.isEmpty;  
  20. import static org.springframework.util.StringUtils.hasLength;  
  21. import static org.springframework.util.StringUtils.tokenizeToStringArray;  
  22.   
  23. import java.io.IOException;  
  24. import java.sql.SQLException;  
  25. import java.util.Properties;  
  26.   
  27. import javax.sql.DataSource;  
  28.   
  29. import org.apache.ibatis.builder.xml.XMLConfigBuilder;  
  30. import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
  31. import org.apache.ibatis.executor.ErrorContext;  
  32. import org.apache.ibatis.logging.Log;  
  33. import org.apache.ibatis.logging.LogFactory;  
  34. import org.apache.ibatis.mapping.DatabaseIdProvider;  
  35. import org.apache.ibatis.mapping.Environment;  
  36. import org.apache.ibatis.plugin.Interceptor;  
  37. import org.apache.ibatis.reflection.factory.ObjectFactory;  
  38. import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;  
  39. import org.apache.ibatis.session.Configuration;  
  40. import org.apache.ibatis.session.SqlSessionFactory;  
  41. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  42. import org.apache.ibatis.transaction.TransactionFactory;  
  43. import org.apache.ibatis.type.TypeHandler;  
  44. import org.mybatis.spring.transaction.SpringManagedTransactionFactory;  
  45. import org.springframework.beans.factory.FactoryBean;  
  46. import org.springframework.beans.factory.InitializingBean;  
  47. import org.springframework.context.ApplicationEvent;  
  48. import org.springframework.context.ApplicationListener;  
  49. import org.springframework.context.ConfigurableApplicationContext;  
  50. import org.springframework.context.event.ContextRefreshedEvent;  
  51. import org.springframework.core.NestedIOException;  
  52. import org.springframework.core.io.Resource;  
  53. import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;  
  54.   
  55. import com.thinkgem.jeesite.common.mybatis.thread.MapperRefresh;  
  56.   
  57. /** 
  58.  * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}. 
  59.  * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context; 
  60.  * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection. 
  61.  * 
  62.  * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction 
  63.  * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions 
  64.  * which span multiple databases or when container managed transactions (CMT) are being used. 
  65.  * 
  66.  * @author Putthibong Boonbong 
  67.  * @author Hunter Presnall 
  68.  * @author Eduardo Macarron 
  69.  *  
  70.  * @see #setConfigLocation 
  71.  * @see #setDataSource 
  72.  * @version $Id$ 
  73.  * @modify ThinkGem 2016-5-24 来自 MyBatisSpring1.2.3版本 
  74.  */  
  75. public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {  
  76.   
  77.   private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);  
  78.   
  79.   private Resource configLocation;  
  80.   
  81.   private Resource[] mapperLocations;  
  82.   
  83.   private DataSource dataSource;  
  84.   
  85.   private TransactionFactory transactionFactory;  
  86.   
  87.   private Properties configurationProperties;  
  88.   
  89.   private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  
  90.   
  91.   private SqlSessionFactory sqlSessionFactory;  
  92.   
  93.   //EnvironmentAware requires spring 3.1  
  94.   private String environment = SqlSessionFactoryBean.class.getSimpleName();  
  95.   
  96.   private boolean failFast;  
  97.   
  98.   private Interceptor[] plugins;  
  99.   
  100.   private TypeHandler[] typeHandlers;  
  101.   
  102.   private String typeHandlersPackage;  
  103.   
  104.   private Class[] typeAliases;  
  105.   
  106.   private String typeAliasesPackage;  
  107.   
  108.   private Class typeAliasesSuperType;  
  109.   
  110.   //issue #19. No default provider.  
  111.   private DatabaseIdProvider databaseIdProvider;  
  112.   
  113.   private ObjectFactory objectFactory;  
  114.   
  115.   private ObjectWrapperFactory objectWrapperFactory;  
  116.   
  117.   /** 
  118.    * Sets the ObjectFactory. 
  119.    *  
  120.    * @since 1.1.2 
  121.    * @param objectFactory 
  122.    */  
  123.   public void setObjectFactory(ObjectFactory objectFactory) {  
  124.     this.objectFactory = objectFactory;  
  125.   }  
  126.   
  127.   /** 
  128.    * Sets the ObjectWrapperFactory. 
  129.    *  
  130.    * @since 1.1.2 
  131.    * @param objectWrapperFactory 
  132.    */  
  133.   public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {  
  134.     this.objectWrapperFactory = objectWrapperFactory;  
  135.   }  
  136.   
  137.   /** 
  138.    * Gets the DatabaseIdProvider 
  139.    * 
  140.    * @since 1.1.0 
  141.    * @return 
  142.    */  
  143.   public DatabaseIdProvider getDatabaseIdProvider() {  
  144.     return databaseIdProvider;  
  145.   }  
  146.   
  147.   /** 
  148.    * Sets the DatabaseIdProvider. 
  149.    * As of version 1.2.2 this variable is not initialized by default.  
  150.    * 
  151.    * @since 1.1.0 
  152.    * @param databaseIdProvider 
  153.    */  
  154.   public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {  
  155.     this.databaseIdProvider = databaseIdProvider;  
  156.   }  
  157.   
  158.   /** 
  159.    * Mybatis plugin list. 
  160.    * 
  161.    * @since 1.0.1 
  162.    * 
  163.    * @param plugins list of plugins 
  164.    * 
  165.    */  
  166.   public void setPlugins(Interceptor[] plugins) {  
  167.     this.plugins = plugins;  
  168.   }  
  169.   
  170.   /** 
  171.    * Packages to search for type aliases. 
  172.    * 
  173.    * @since 1.0.1 
  174.    * 
  175.    * @param typeAliasesPackage package to scan for domain objects 
  176.    * 
  177.    */  
  178.   public void setTypeAliasesPackage(String typeAliasesPackage) {  
  179.     this.typeAliasesPackage = typeAliasesPackage;  
  180.   }  
  181.   
  182.   /** 
  183.    * Super class which domain objects have to extend to have a type alias created. 
  184.    * No effect if there is no package to scan configured. 
  185.    * 
  186.    * @since 1.1.2 
  187.    * 
  188.    * @param typeAliasesSuperType super class for domain objects 
  189.    * 
  190.    */  
  191.   public void setTypeAliasesSuperType(Class typeAliasesSuperType) {  
  192.     this.typeAliasesSuperType = typeAliasesSuperType;  
  193.   }  
  194.   
  195.   /** 
  196.    * Packages to search for type handlers. 
  197.    * 
  198.    * @since 1.0.1 
  199.    * 
  200.    * @param typeHandlersPackage package to scan for type handlers 
  201.    * 
  202.    */  
  203.   public void setTypeHandlersPackage(String typeHandlersPackage) {  
  204.     this.typeHandlersPackage = typeHandlersPackage;  
  205.   }  
  206.   
  207.   /** 
  208.    * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes} 
  209.    * 
  210.    * @since 1.0.1 
  211.    * 
  212.    * @param typeHandlers Type handler list 
  213.    */  
  214.   public void setTypeHandlers(TypeHandler[] typeHandlers) {  
  215.     this.typeHandlers = typeHandlers;  
  216.   }  
  217.   
  218.   /** 
  219.    * List of type aliases to register. They can be annotated with {@code Alias} 
  220.    * 
  221.    * @since 1.0.1 
  222.    * 
  223.    * @param typeAliases Type aliases list 
  224.    */  
  225.   public void setTypeAliases(Class[] typeAliases) {  
  226.     this.typeAliases = typeAliases;  
  227.   }  
  228.   
  229.   /** 
  230.    * If true, a final check is done on Configuration to assure that all mapped 
  231.    * statements are fully loaded and there is no one still pending to resolve 
  232.    * includes. Defaults to false. 
  233.    * 
  234.    * @since 1.0.1 
  235.    * 
  236.    * @param failFast enable failFast 
  237.    */  
  238.   public void setFailFast(boolean failFast) {  
  239.     this.failFast = failFast;  
  240.   }  
  241.   
  242.   /** 
  243.    * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is 
  244.    * "WEB-INF/mybatis-configuration.xml". 
  245.    */  
  246.   public void setConfigLocation(Resource configLocation) {  
  247.     this.configLocation = configLocation;  
  248.   }  
  249.   
  250.   /** 
  251.    * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} 
  252.    * configuration at runtime. 
  253.    * 
  254.    * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. 
  255.    * This property being based on Spring's resource abstraction also allows for specifying 
  256.    * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml". 
  257.    */  
  258.   public void setMapperLocations(Resource[] mapperLocations) {  
  259.     this.mapperLocations = mapperLocations;  
  260.   }  
  261.   
  262.   /** 
  263.    * Set optional properties to be passed into the SqlSession configuration, as alternative to a 
  264.    * {@code <properties>} tag in the configuration xml file. This will be used to 
  265.    * resolve placeholders in the config file. 
  266.    */  
  267.   public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {  
  268.     this.configurationProperties = sqlSessionFactoryProperties;  
  269.   }  
  270.   
  271.   /** 
  272.    * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} 
  273.    * should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same 
  274.    * JNDI DataSource for both. 
  275.    * 
  276.    * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code 
  277.    * accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. 
  278.    * 
  279.    * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not 
  280.    * a {@code TransactionAwareDataSourceProxy}. Only data access code may work with 
  281.    * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the 
  282.    * underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} 
  283.    * passed in, it will be unwrapped to extract its target {@code DataSource}. 
  284.    * 
  285.    */  
  286.   public void setDataSource(DataSource dataSource) {  
  287.     if (dataSource instanceof TransactionAwareDataSourceProxy) {  
  288.       // If we got a TransactionAwareDataSourceProxy, we need to perform  
  289.       // transactions for its underlying target DataSource, else data  
  290.       // access code won't see properly exposed transactions (i.e.  
  291.       // transactions for the target DataSource).  
  292.       this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();  
  293.     } else {  
  294.       this.dataSource = dataSource;  
  295.     }  
  296.   }  
  297.   
  298.   /** 
  299.    * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}. 
  300.    * 
  301.    * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By 
  302.    * default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances. 
  303.    * 
  304.    */  
  305.   public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {  
  306.     this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;  
  307.   }  
  308.   
  309.   /** 
  310.    * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory} 
  311.    * 
  312.    * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: 
  313.    * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction, 
  314.    * SqlSession operations will execute SQL statements non-transactionally. 
  315.    * 
  316.    * It is strongly recommended to use the default {@code TransactionFactory}. If not used, any 
  317.    * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if 
  318.    * a transaction is active. 
  319.    * 
  320.    * @see SpringManagedTransactionFactory 
  321.    * @param transactionFactory the MyBatis TransactionFactory 
  322.    */  
  323.   public void setTransactionFactory(TransactionFactory transactionFactory) {  
  324.     this.transactionFactory = transactionFactory;  
  325.   }  
  326.   
  327.   /** 
  328.    * NOTE: This class overrides any {@code Environment} you have set in the MyBatis 
  329.    * config file. This is used only as a placeholder name. The default value is 
  330.    * {@code SqlSessionFactoryBean.class.getSimpleName()}. 
  331.    * 
  332.    * @param environment the environment name 
  333.    */  
  334.   public void setEnvironment(String environment) {  
  335.     this.environment = environment;  
  336.   }  
  337.   
  338.   /** 
  339.    * {@inheritDoc} 
  340.    */  
  341.   @Override  
  342.   public void afterPropertiesSet() throws Exception {  
  343.     notNull(dataSource, "Property 'dataSource' is required");  
  344.     notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");  
  345.   
  346.     this.sqlSessionFactory = buildSqlSessionFactory();  
  347.   }  
  348.   
  349.   /** 
  350.    * Build a {@code SqlSessionFactory} instance. 
  351.    * 
  352.    * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a 
  353.    * {@code SqlSessionFactory} instance based on an Reader. 
  354.    * 
  355.    * @return SqlSessionFactory 
  356.    * @throws IOException if loading the config file failed 
  357.    */  
  358.   protected SqlSessionFactory buildSqlSessionFactory() throws IOException {  
  359.   
  360.     Configuration configuration;  
  361.   
  362.     XMLConfigBuilder xmlConfigBuilder = null;  
  363.     if (this.configLocation != null) {  
  364.       xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), nullthis.configurationProperties);  
  365.       configuration = xmlConfigBuilder.getConfiguration();  
  366.     } else {  
  367.       if (LOGGER.isDebugEnabled()) {  
  368.         LOGGER.debug("Property 'configLocation' not specified, using default MyBatis Configuration");  
  369.       }  
  370.       configuration = new Configuration();  
  371.       configuration.setVariables(this.configurationProperties);  
  372.     }  
  373.   
  374.     if (this.objectFactory != null) {  
  375.       configuration.setObjectFactory(this.objectFactory);  
  376.     }  
  377.   
  378.     if (this.objectWrapperFactory != null) {  
  379.       configuration.setObjectWrapperFactory(this.objectWrapperFactory);  
  380.     }  
  381.   
  382.     if (hasLength(this.typeAliasesPackage)) {  
  383.       String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,  
  384.           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
  385.       for (String packageToScan : typeAliasPackageArray) {  
  386.         // ThinkGem 修改实体类重名的时候抛出并打印异常,否则系统会一直递归造成无法启动  
  387.         try {  
  388.             configuration.getTypeAliasRegistry().registerAliases(packageToScan,  
  389.                     typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);  
  390.         } catch (Exception ex) {  
  391.             LOGGER.error("Scanned package: '" + packageToScan + "' for aliases", ex);  
  392.             throw new NestedIOException("Scanned package: '" + packageToScan + "' for aliases", ex);  
  393.         } finally {  
  394.             ErrorContext.instance().reset();  
  395.         }  
  396.         // ThinkGem end  
  397.         if (LOGGER.isDebugEnabled()) {  
  398.           LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");  
  399.         }  
  400.       }  
  401.     }  
  402.   
  403.     if (!isEmpty(this.typeAliases)) {  
  404.       for (Class typeAlias : this.typeAliases) {  
  405.         configuration.getTypeAliasRegistry().registerAlias(typeAlias);  
  406.         if (LOGGER.isDebugEnabled()) {  
  407.           LOGGER.debug("Registered type alias: '" + typeAlias + "'");  
  408.         }  
  409.       }  
  410.     }  
  411.   
  412.     if (!isEmpty(this.plugins)) {  
  413.       for (Interceptor plugin : this.plugins) {  
  414.         configuration.addInterceptor(plugin);  
  415.         if (LOGGER.isDebugEnabled()) {  
  416.           LOGGER.debug("Registered plugin: '" + plugin + "'");  
  417.         }  
  418.       }  
  419.     }  
  420.   
  421.     if (hasLength(this.typeHandlersPackage)) {  
  422.       String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,  
  423.           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
  424.       for (String packageToScan : typeHandlersPackageArray) {  
  425.         configuration.getTypeHandlerRegistry().register(packageToScan);  
  426.         if (LOGGER.isDebugEnabled()) {  
  427.           LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");  
  428.         }  
  429.       }  
  430.     }  
  431.   
  432.     if (!isEmpty(this.typeHandlers)) {  
  433.       for (TypeHandler typeHandler : this.typeHandlers) {  
  434.         configuration.getTypeHandlerRegistry().register(typeHandler);  
  435.         if (LOGGER.isDebugEnabled()) {  
  436.           LOGGER.debug("Registered type handler: '" + typeHandler + "'");  
  437.         }  
  438.       }  
  439.     }  
  440.   
  441.     if (xmlConfigBuilder != null) {  
  442.       try {  
  443.         xmlConfigBuilder.parse();  
  444.   
  445.         if (LOGGER.isDebugEnabled()) {  
  446.           LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");  
  447.         }  
  448.       } catch (Exception ex) {  
  449.         throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);  
  450.       } finally {  
  451.         ErrorContext.instance().reset();  
  452.       }  
  453.     }  
  454.   
  455.     if (this.transactionFactory == null) {  
  456.       this.transactionFactory = new SpringManagedTransactionFactory();  
  457.     }  
  458.   
  459.     configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));  
  460.   
  461.     if (this.databaseIdProvider != null) {  
  462.       try {  
  463.         configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));  
  464.       } catch (SQLException e) {  
  465.         throw new NestedIOException("Failed getting a databaseId", e);  
  466.       }  
  467.     }  
  468.   
  469.     if (!isEmpty(this.mapperLocations)) {  
  470.       for (Resource mapperLocation : this.mapperLocations) {  
  471.         if (mapperLocation == null) {  
  472.           continue;  
  473.         }  
  474.   
  475.         try {  
  476.           XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),  
  477.               configuration, mapperLocation.toString(), configuration.getSqlFragments());  
  478.           xmlMapperBuilder.parse();  
  479.         } catch (Exception e) {  
  480.             // ThinkGem MapperXML有错误的时候抛出并打印异常,否则系统会一直递归造成无法启动  
  481.             LOGGER.error("Failed to parse mapping resource: '" + mapperLocation + "'", e);  
  482.             throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);  
  483.         } finally {  
  484.           ErrorContext.instance().reset();  
  485.         }  
  486.   
  487.         if (LOGGER.isDebugEnabled()) {  
  488.           LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");  
  489.         }  
  490.       }  
  491.         
  492.       // ThinkGem 启动刷新MapperXML定时器(有助于开发者调试)。  
  493.       new MapperRefresh(this.mapperLocations, configuration).run();  
  494.         
  495.     } else {  
  496.       if (LOGGER.isDebugEnabled()) {  
  497.         LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");  
  498.       }  
  499.     }  
  500.   
  501.     return this.sqlSessionFactoryBuilder.build(configuration);  
  502.   }  
  503.   
  504.   /** 
  505.    * {@inheritDoc} 
  506.    */  
  507.   @Override  
  508.   public SqlSessionFactory getObject() throws Exception {  
  509.     if (this.sqlSessionFactory == null) {  
  510.       afterPropertiesSet();  
  511.     }  
  512.   
  513.     return this.sqlSessionFactory;  
  514.   }  
  515.   
  516.   /** 
  517.    * {@inheritDoc} 
  518.    */  
  519.   @Override  
  520.   public Classextends SqlSessionFactory> getObjectType() {  
  521.     return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();  
  522.   }  
  523.   
  524.   /** 
  525.    * {@inheritDoc} 
  526.    */  
  527.   @Override  
  528.   public boolean isSingleton() {  
  529.     return true;  
  530.   }  
  531.   
  532.   /** 
  533.    * {@inheritDoc} 
  534.    */  
  535.   @Override  
  536.   public void onApplicationEvent(ApplicationEvent event) {  
  537.     if (failFast && event instanceof ContextRefreshedEvent) {  
  538.       // fail-fast -> check all statements are completed  
  539.       this.sqlSessionFactory.getConfiguration().getMappedStatementNames();  
  540.     }  
  541.   }  
  542.   
  543. }   

重写SqlSessionFactoryBean就的修改下Spring的MyBatis配置部分: 

Xml代码  
  1.   
  2.     <bean id="sqlSessionFactory" class="com.thinkgem.jeesite.common.mybatis.spring.SqlSessionFactoryBean">  
  3.         <property name="dataSource" ref="dataSource"/>  
  4.         <property name="typeAliasesPackage" value="com.thinkgem.jeesite"/>  
  5.         <property name="typeAliasesSuperType" value="<span style="line-height: 1.5;">com.thinkgem.jeesitespan><span style="line-height: 1.5;">.persistence.BaseEntity"/>span>  
  6.         <property name="mapperLocations" value="classpath*:/mappings/**/*.xml"/>  
  7.         <property name="configLocation" value="classpath:/mybatis-config.xml">property>  
  8.     bean>  

 

 最后附加上属性配置文件:mybatis-refresh.properties

Java代码  
  1. #是否开启刷新线程  
  2. enabled=true  
  3. #延迟启动刷新程序的秒数  
  4. delaySeconds=60  
  5. #刷新扫描间隔的时长秒数  
  6. sleepSeconds=3  
  7. #扫描Mapper文件的资源路径  
  8. mappingPath=mappings  

你可能感兴趣的:(实现MyBatis Mapper XML文件增量动态刷新,自动加载,热加载,热部署)