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

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

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

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

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

/**
 * Copyright (c) 2012-Now https://github.com/thinkgem/jeesite.
 */
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<String> 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<File> fileList = this.getRefreshFile(new File(filePath), beforeTime);
		if (fileList.size() > 0) {
			log.debug("Refresh file: " + fileList.size());
		}
		for (int i = 0; i < fileList.size(); i++) {
			InputStream inputStream = new FileInputStream(fileList.get(i));
			String resource = fileList.get(i).getAbsolutePath();
			try {
				
				// 清理原有资源,更新为自己的StrictMap方便,增量重新加载
				String[] mapFieldNames = new String[]{
					"mappedStatements", "caches",
					"resultMaps", "parameterMaps",
					"keyGenerators", "sqlFragments"
				};
				for (String fieldName : mapFieldNames){
					Field field = configuration.getClass().getDeclaredField(fieldName);
					field.setAccessible(true);
					Map map = ((Map)field.get(configuration));
					if (!(map instanceof StrictMap)){
						Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");
						for (Object key : map.keySet()){
							try {
								newMap.put(key, map.get(key));
							}catch(IllegalArgumentException ex){
								newMap.put(key, ex.getMessage());
							}
						}
						field.set(configuration, newMap);
					}
				}
				
				// 清理已加载的资源标识,方便让它重新加载。
				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<File> getRefreshFile(File dir, Long beforeTime) {
		List<File> fileList = new ArrayList<File>();

		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				File file = files[i];
				if (file.isDirectory()) {
					fileList.addAll(this.getRefreshFile(file, beforeTime));
				} else if (file.isFile()) {
					if (this.checkFile(file, beforeTime)) {
						fileList.add(file);
					}
				} else {
					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<V> extends HashMap<String, V> {

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

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

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

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

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

		@SuppressWarnings("unchecked")
		public V put(String key, V value) {
			// 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<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  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 "&lt;sqlmapper&gt;" 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 &lt;properties&gt;} 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.
   *
   * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> 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;
  }

  /**
   * <b>NOTE:</b> This class <em>overrides</em> 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<? extends SqlSessionFactory> 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就的修改下Spring的MyBatis配置部分: 

<!-- MyBatis SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="com.thinkgem.jeesite.common.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.thinkgem.jeesite"/>
        <property name="typeAliasesSuperType" value="com.thinkgem.jeesite.persistence.BaseEntity"/>
        <property name="mapperLocations" value="classpath*:/mappings/**/*.xml"/>
		<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
    </bean>

 

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

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

 

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