实现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 "<sqlmapper>" 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 <properties>} 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="<span style="line-height: 1.5;">com.thinkgem.jeesite</span><span style="line-height: 1.5;">.persistence.BaseEntity"/></span>  
        <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 


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis Mapper XML is a configuration file used in MyBatis, a Java-based persistence framework, to define SQL mappings between Java objects and database tables. The Mapper XML file contains SQL statements and mapping rules that are used to interact with the database. In the Mapper XML file, you define the SQL statements such as SELECT, INSERT, UPDATE, DELETE, etc., using the MyBatis XML syntax. You also define the mapping rules to map the result of the SQL queries to Java objects or vice versa. Here is an example of a simple Mapper XML file: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.UserMapper"> <select id="getUserById" resultType="com.example.User"> SELECT * FROM users WHERE id = #{id} </select> <insert id="insertUser" parameterType="com.example.User"> INSERT INTO users (id, name, email) VALUES (#{id}, #{name}, #{email}) </insert> <update id="updateUser" parameterType="com.example.User"> UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id} </update> <delete id="deleteUser" parameterType="int"> DELETE FROM users WHERE id = #{id} </delete> </mapper> ``` In this example, the Mapper XML file defines four SQL statements: `getUserById`, `insertUser`, `updateUser`, and `deleteUser`. Each statement has an ID, parameterType (input parameter), resultType (output result mapping), and the actual SQL query. You can then use the defined SQL statements in your Java code by referencing the Mapper XML file and the statement ID using MyBatis API. Note that the actual mapping between Java objects and database tables is usually done through Java annotations or XML configuration files in addition to the Mapper XML file. The Mapper XML file primarily focuses on defining the SQL statements and their mappings. I hope this gives you a basic understanding of MyBatis Mapper XML. Let me know if you have any further questions!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值