最简单真正实现spingboot下mybatis、mybatis-plus、tk.mybatis的sql修改后动态加载注解sql、xml配置sql的功能

最近参与了使用springboot+mybatis-plus的项目,使用jrebel能很好的解决sql变化以外的问题,但是在调式的时候,发现sql语句有任何的变化都需要重起项目才能生效。我们的项目框架原因,启动至少要花3分钟,一个稍复杂的sql可能来回修改几次,就得反反复复重起项目,大量的时候耗费在重起上。
尝试网上搜了一些资料,基本只有针对xml配置的sql有动态加载的资料,还比较复杂。而我们的项目,所有的sql都是用注解实现的,搜到一篇也是针对xml修改后加载的文章:https://blog.csdn.net/qq_24434671/article/details/90258908
此文没有解决我的问题,但是很有参参价值,在他的基础上做了改进,实现了我需要修改注解的sql后动态加载的功能。
此文给的方法存在的问题:
1,只能重新加载xml配置的sql。
2,自己定义的方法能动态加载,mapper超类中的通用方法动态加载后就消失了。
3,任何mapper的修改都加载所有的mapper。


经过改进后,使用更简单,只需要在启动类添加一个方法,指定Mapper类和xml路径即可:如图:

启动类中添加的内容为:(修改成自己项目mapper文件对应的路径即可)

@Bean
    public MapperFileLoader mapperFileLoader() {
        return new MapperFileLoader(new String[]{
                    "classpath:mapper/**/*.xml",
                    "classpath:com/dzw/mapper/*Mapper.class"
                    });
    }

以下为mybatis-plus下的完整代码,tk.bybatis需要注释掉removeConfig方法。其它的请使用后反馈问题。

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/*
 * dongzhiwei 
 * 实现mapper修改后动态加载功能
 * */
public class MapperFileLoader {

    private Logger logger = LoggerFactory.getLogger(MapperFileLoader.class);

    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    private static Configuration configuration=null;
    private static Map<String,Class<?>> classMap = new HashMap<>();
    private static Set<String> delMethodSet = new HashSet<>();
    private static final int interValCheckMin = 5;
    private static int checkNum=0;
    private static boolean stop = false;
    private Resource[] mapperLocations;
    private String[] packageSearchPath = {"classpath:mapper/**/*.xml","classpath:**/*Mapper.class"};
    private HashMap<String, Long> fileMapping = new HashMap<String, Long>();
    
    public MapperFileLoader(String[] packageSearchPath) {
        if(packageSearchPath != null && packageSearchPath.length>0) {
            this.packageSearchPath = packageSearchPath;
        }
        startThreadListener();
    }
    
    public void startThreadListener() {
        ScheduledExecutorService service =    Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if(stop){
                    service.shutdown();
                    service.shutdownNow();
                }
                checkNum++;
                try{
                    checkFileIsChanged();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }, 3, interValCheckMin,TimeUnit.SECONDS );
    }

    public  String reloadMapperFile(Set<String> changeFileSet) {
        if(configuration==null){
            configuration = sqlSessionFactory.getConfiguration();
        }
        try {
            
            //清理 tk.mybatis下需要注释掉
            this.removeConfig(configuration);
            
            //重新加载
            for (Resource configLocation : mapperLocations) {
                if(changeFileSet.size()<=0){
                    break;
                }
                try {
                    String resName = configLocation.toString().replace("[", "").replace("]", "");
                    resName = resName.substring(resName.lastIndexOf("\\")+1);
                    if(!changeFileSet.contains(resName)){//没变化的项不需要重新加载
                        continue;
                    }
                    changeFileSet.remove(resName);
                    if(resName.endsWith(".xml")){
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    }else if(resName.endsWith(".class")){
                        String className = configLocation.toString();
                        className = className.substring(className.indexOf("\\classes\\")+"\\classes\\".length(),className.length()-".class".length()-1).replace("\\", ".");
                        Class<?> clazz = null;
                        if(classMap.containsKey(className)){
                            clazz = classMap.get(className);
                        }else{
                            clazz = Class.forName(className);
                            classMap.put(className, clazz);
                        }
                        for(Method m:clazz.getDeclaredMethods()){
                            delMethodSet.add(clazz.getName()+"."+m.getName());
                        }
                        if(delMethodSet.size()>0){
                            clearMap(configuration.getClass(), configuration, "mappedStatements");
                            MapperAnnotationBuilder mapperAnnotationBuilder = new MapperAnnotationBuilder(sqlSessionFactory.getConfiguration(),clazz);
                            mapperAnnotationBuilder.parse();
                        }
                    }
                    logger.info("mapper文件[" + configLocation.getFilename() + "]重新加载成功");
                } catch (Exception e) {
                    logger.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
                    continue;
                }
            }
            
            return "刷新mybatis xml配置语句成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "刷新mybatis xml配置语句失败";
        }
    }
    
    
    public void setPackageSearchPath(String[] packageSearchPath) {
        this.packageSearchPath = packageSearchPath;
    }
    
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 扫描xml文件所在的路径
     * @throws IOException 
     */
    private void scanMapper() throws IOException {
        List<Resource> listRes = new ArrayList<>(32);
        for(String path:packageSearchPath){
            try{
                Resource[] tmp = new PathMatchingResourcePatternResolver().getResources(path);
                listRes.addAll(Arrays.asList(tmp));
            }catch(Exception e){
                logger.error("扫描mapper文件出错:"+(e.getMessage()!=null?e.getMessage():e.getCause()));
            }
            
        }
        if(listRes==null || listRes.size()<0){
            logger.error("mapper资源文件为空!退出任务");
            stop=true;
        }
        this.mapperLocations = new Resource[listRes.size()];
        for(int i=0;i<listRes.size();i++){
            this.mapperLocations[i]=listRes.get(i);
        }
    }

    /**
     * 清空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 = null;
        if(configuration.getClass().getName().equals("com.baomidou.mybatisplus.core.MybatisConfiguration")) {
            field = classConfig.getSuperclass().getDeclaredField(fieldName);
        }else {
            field = classConfig.getClass().getDeclaredField(fieldName);
        }
        field.setAccessible(true);
        Map mapConfig = (Map) field.get(configuration);
        if(fieldName.equals("mappedStatements")){
            for(String delKey:delMethodSet){
                if(mapConfig.containsKey(delKey)){
                    mapConfig.remove(delKey);
                }
            }
            delMethodSet.clear();
            
            return;
        }
        mapConfig.clear();
    }

    @SuppressWarnings("rawtypes")
    private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
        Field field = null;
        if(configuration.getClass().getName().equals("com.baomidou.mybatisplus.core.MybatisConfiguration")) {
            field = classConfig.getSuperclass().getDeclaredField(fieldName);
        }else {
            field = classConfig.getClass().getDeclaredField(fieldName);
        }
        field.setAccessible(true);
        Set setConfig = (Set) field.get(configuration);
        setConfig.clear();
    }
    
    /**
     * 判断文件是否发生了变化并动态加载
     * @param resource
     * @return
     * @throws IOException
     */
    public void checkFileIsChanged() {
        try {
            this.scanMapper();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        Set<String> changeFileSet = new HashSet<>();
        for (Resource resource : mapperLocations) {
            String resourceName = resource.getFilename();
            
            boolean addFlag = !fileMapping.containsKey(resourceName);
            
            // 修改文件:判断文件内容是否有变化
            Long compareFrame = fileMapping.get(resourceName);
            long lastData=0;
            try {
                lastData = resource.contentLength() + resource.lastModified();
            } catch (IOException e) {
                e.printStackTrace();
            }
            boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastData;
            
            // 新增或是修改时,重新加载文件
            if(addFlag || modifyFlag) {
                fileMapping.put(resourceName, Long.valueOf(lastData));
                if (checkNum > 1) {
                    changeFileSet.add(resourceName);
                }
            }
        }
        if(changeFileSet.size()>0){
            reloadMapperFile(changeFileSet);
        }
    }
    
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值