(转)mybatis热部署加载*Mapper.xml文件,手动刷新*Mapper.xml文件

Posted PacosonSWJTU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(转)mybatis热部署加载*Mapper.xml文件,手动刷新*Mapper.xml文件相关的知识,希望对你有一定的参考价值。

转自: https://blog.csdn.net/LOVELONG8808/article/details/78738086 


由于项目已经发布到线上,要是修改一个Mapper.xml文件的话,需要重启整个服务,这个是很耗时间的,而且在一段时间内导致服务不可用,严重影响用户的体验度。所以希望可以有一个机制可以,当修改某个mapper.xml的时候,只要重新加载这个mapper.xml就好了,参考网上的一些资料和demo,加上一些自己的总结,下面的代码是通过测试的,可以供你们参考和使用。

import java.io.IOException;  
import java.lang.reflect.Field;  
import java.util.ArrayList;
import java.util.HashMap;  
import java.util.List;
import java.util.Map;  
import java.util.Set;  
  
import org.apache.commons.logging.Log;  
import org.apache.commons.logging.LogFactory;  
import org.apache.ibatis.builder.xml.XMLMapperBuilder;  
import org.apache.ibatis.session.Configuration;  
import org.apache.ibatis.session.SqlSessionFactory;  
import org.springframework.core.io.Resource;  
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
  
public class RefreshMapperCache {  
    private Log log  = LogFactory.getLog(RefreshMapperCache.class);  
    
    private SqlSessionFactory sqlSessionFactory;  
    private Resource[] mapperLocations;  
    private String packageSearchPath;  
    private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化  
    
    //记录发生改变的xml文件名称
    private List<String> changeResourceNameList = new ArrayList<>();
      
    public void refreshMapper() {  
        try {  
            Configuration configuration = this.sqlSessionFactory.getConfiguration();  
              
            // step.1 扫描文件  
            try {  
                this.scanMapperXml();  
            } catch (IOException e) {  
                log.error("packageSearchPath扫描包路径配置错误");  
                return;  
            }  
              
//            System.out.println("==============刷新前mapper中的内容 start===============");  
//            //获取xml中的每个语句的名称即 id = "findUserById";
//            for (String name : configuration.getMappedStatementNames()) {  
//                System.out.println(name);  
//            }  
//            System.out.println("==============刷新前mapper中的内容   end===============");  
            
            //清空被修改过后的文件名称,确保该集合是空的
            changeResourceNameList.clear();
            // step.2 判断是否有文件发生了变化  
            if (this.isChanged()) {  
                // step.2.1 清理  
                this.removeConfig(configuration);  
  
                // step.2.2 重新加载  
                for (Resource configLocation : mapperLocations) {  
                    try { 
                    	//匹配被修改过的mapper文件,如果存在,则重新加载
                    	//如果想要重新加载全部mapper,可以不匹配
                    	if(changeResourceNameList.contains(configLocation.getFilename())){
                    		 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());  
                             xmlMapperBuilder.parse();  
                             System.out.println("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");  
                    	}
                    } catch (IOException e) {  
                    	System.out.println("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");  
                        continue;  
                    }  
                }
                //清空被修改过后的文件名称
                changeResourceNameList.clear();
            }  
              
//            System.out.println("--------------------------刷新后mapper中的内容 start--------------------------");  
//            for (String name : configuration.getMappedStatementNames()) {  
//                System.out.println(name);  
//            }
//            System.out.println("--------------------------刷新后mapper中的内容  end--------------------------");   
        } catch (Exception e) {  
           System.out.println("****************刷新缓存异常: "+e.getMessage());
        }  
    }  
      
    public void setPackageSearchPath(String packageSearchPath) {  
        this.packageSearchPath = packageSearchPath;  
    }  
      
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {  
        this.sqlSessionFactory = sqlSessionFactory;  
    }  
  
    /** 
     * 扫描xml文件所在的路径 
     * @throws IOException  
     */  
    private void scanMapperXml() throws IOException {  
        this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);  
    }  
  
    /** 
     * 清空Configuration中几个重要的缓存 
     * @param configuration 
     * @throws Exception 
     */  
    private void removeConfig(Configuration configuration) throws Exception {  
        Class<?> classConfig = configuration.getClass();  
        clearMap(classConfig, configuration, "mappedStatements");  
        clearMap(classConfig, configuration, "caches");  
        clearMap(classConfig, configuration, "resultMaps");  
        clearMap(classConfig, configuration, "parameterMaps");  
        clearMap(classConfig, configuration, "keyGenerators");  
        clearMap(classConfig, configuration, "sqlFragments");  
  
        clearSet(classConfig, configuration, "loadedResources");  
  
    }  
  
    @SuppressWarnings("rawtypes")  
    private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {  
        Field field = classConfig.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        Map mapConfig = (Map) field.get(configuration);  
        mapConfig.clear();  
    }  
  
    @SuppressWarnings("rawtypes")  
    private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {  
        Field field = classConfig.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        Set setConfig = (Set) field.get(configuration);  
        setConfig.clear();  
    }  
      
    /** 
     * 判断文件是否发生了变化 
     * @param resource 
     * @return 
     * @throws IOException 
     */  
    private boolean isChanged() throws IOException {  
        boolean flag = false;  
        System.out.println("***************************获取文件名   开始************************************");
        for (Resource resource : mapperLocations) {  
            String resourceName = resource.getFilename();  
            
            System.out.println("resourceName == " + resourceName+",  path = "+resource.getURL().getPath());
            
            boolean addFlag = !fileMapping.containsKey(resourceName);// 此为新增标识  
              
            // 修改文件:判断文件内容是否有变化  
            Long compareFrame = fileMapping.get(resourceName);  
            long lastFrame = resource.contentLength() + resource.lastModified();  
            boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识  
            
            if(addFlag){
            	System.out.println("            新增了:==="+ resourceName);
            }
            if(modifyFlag){
            	System.out.println("            修改了:==="+ resourceName);
            }
            
            // 新增或是修改时,存储文件  
            if(addFlag || modifyFlag) {  
                fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值  
                flag = true;  
                changeResourceNameList.add(resourceName);
            }  
        } 
        System.out.println("***************************获取文件名   结束************************************");
        return flag;  
    }  
}  

写一个实体类,然后在spring中配置改实体类的bean即可
 

    <bean id="refreshMapperCache" class="com.company.project.util.RefreshMapperCache" >  
        <!-- 扫描的映射mapper.xml的文件路径
               这个地方要注意mapper的文件,多数据源情况下,只能扫描自己数据源下的mapper,否则会报异常          -->
          <property name="packageSearchPath" value="classpath*:mapper/trade/**/*.xml"></property>
          <!-- 配置自己的数据源 -->
          <property name="sqlSessionFactory" ref="sqlSessionFactoryPay"></property>  
    </bean>  


由于我们公司使用的是多数据源,所以在配置bean的时候,要给每个数据源配置一个bean,注意点就是在配置bean的时候

1. 如果是多数据源的情况 ,  扫描mapper.xml文件的时候,只能扫该数据源下的mapper.xml文件

2. 多数据源情况想,设置sqlSessionFactory 的时候,要设置为对应的数据源

3. 如果是但数据源的情况,那么就简单了,只需要配置当前数据源及对应的mapper.xml文件即可
 

以上是关于(转)mybatis热部署加载*Mapper.xml文件,手动刷新*Mapper.xml文件的主要内容,如果未能解决你的问题,请参考以下文章

mybatis介绍

springboot 在idea中实现热部署(转)

2.第一个MyBatis程序

插件ideaJRebel mybatisPlus extension是JRebel热部署插件的扩展支持mybatis的xml文件热部署

热加载 MyBatis 中修改过的 Mapper.xml

mybatis热加载的实现