Mybatis复杂sql自动热更新

671 阅读2分钟

在springboot项目中用过mybatis一些框架之后都会遇到一个可能会影响效率的点: 项目越来越大,启动一次好几分钟,而dev阶段,mybatis-xxxx.xml的复杂sql一次性又不能写对,这个时候就不能不改一次xml,重新启动一次,然后再测试,效率极低;

项目是不可能因为这个点而频繁重复启动的,增量变动直接idea走热更新逻辑即可 但是mybatis的xml文件虽然会在变动后热更新到target包中,但是默认是不会刷新的 这个时候可以使用如下的默认配置类,来实现定时定时刷新最新的xml,以期达到快速测试的目的;

注意不要将相关配置扔到prod上

项目中随便找个地方放入如下的bean,启动之后自然就会定时刷新xml文件了:

MyBatisMapperHotRefreshHandler.java
package com.xxxx.initialize.impl;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

 import com.xxx.xxx.order.xxx.web.configuration.DataSourceConfiguration;
import com.xxx.framework.xxxxx.Foundation;
import com.google.common.collect.Maps;

@Component
public class MyBatisMapperHotRefreshHandler {

    private static final LoggerService LOG =
        LoggerServiceFactory.getLoggerService(MyBatisMapperHotRefreshHandler.class);
    private static final String LOG_TITLE = "MyBatisMapperHotRefreshHandler";

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

 

    private Resource[] mappersResources;
    private final Map<String, Long> fileChangedMapping = Maps.newHashMap();

    @PostConstruct
    public void init() {
        try {
            // 非fat环境不执行
            if (!Foundation.server().getEnv().isFAT()) {
                return;
            }
            firstLoad();
            LOG.info(LOG_TITLE, "will set hot reload mapper.");

            ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
            scheduledThreadPoolExecutor.setMaximumPoolSize(10); // unused
            scheduledThreadPoolExecutor.scheduleAtFixedRate(this::handleHotReloadMapper, 60, 10, TimeUnit.SECONDS);
        } catch (Exception ex) {
            LOG.error(LOG_TITLE, "try set hot reload mapper failed.");
        }
    }

    private void firstLoad() throws IOException {
        sqlSessionFactory.getConfiguration().setLogImpl(StdOutImpl.class);
        this.mappersResources = getMapperResources();
        for (Resource resource : mappersResources) {
            fileChangedMapping.put(resource.getFilename(), getFileFrame(resource));
        }
    }

    private long getFileFrame(Resource resource) throws IOException {
        return resource.contentLength() + resource.lastModified();
    }

    private void refreshSqlSessionFactoryBean() throws Exception {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        removeConfig(configuration);

        for (Resource mappersResource : mappersResources) {
            (new XMLMapperBuilder(mappersResource.getInputStream(), configuration, mappersResource.toString(),
                configuration.getSqlFragments())).parse();
        }

    }

    private Resource[] getMapperResources() throws IOException {
        return new PathMatchingResourcePatternResolver().getResources(
            PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + DataSourceConfiguration.MAPPER_LOCATION);
    }

    private void handleHotReloadMapper() {
        try {
            if (!isChanged()) {
                return;
            }
            refreshSqlSessionFactoryBean();
            mappersResources = getMapperResources();
        } catch (Exception e) {
            LOG.info(LOG_TITLE, e);
        }
    }

    private boolean isChanged() throws IOException {
        boolean changedFlag = false;
        for (Resource mappersResource : mappersResources) {
            String filename = mappersResource.getFilename();
            boolean addFileFlag = !fileChangedMapping.containsKey(filename);
            long currentFileFrame = getFileFrame(mappersResource);
            boolean modifyFileFlag = fileChangedMapping.containsKey(filename)
                && currentFileFrame != fileChangedMapping.getOrDefault(filename, 0L);

            fileChangedMapping.put(mappersResource.getFilename(), currentFileFrame);

            if (addFileFlag || modifyFileFlag) {
                LOG.info(LOG_TITLE, "{} changed.", filename);
                changedFlag = true;
            }
        }
        return changedFlag;
    }

    /**
     * 清空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();
    }

}

代码略丑,但可用,可自行调整