Method of hot deployment without restarting application after mybatis dynamically updates xml files

Posted by karq on Thu, 03 Oct 2019 22:26:22 +0200

Links to the original text: http://ju.outofmemory.cn/entry/356304

mybatis application program, because it is semi-automated sql, a large number of SQL is configurated in XML files, and in the process of developing programs, it is usually necessary to write SQL variable debugging applications. But by default, SQL statements configurated in XML files are put into the cache. Every time an XML file with SQL statements is changed, the application needs to be restarted, which is inefficient. Therefore, it is very desirable to have a function of dynamically loading xml files, automatically loading new SQL statements, and re-writing them into the cache. A lot of information is referred to online, and finally a new one is created. Simple things come out and are written directly as controller of spring mvc. The code is as follows:
 

package com.yihaomen.controller;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Scope("prototype")
@Controller
@RequestMapping("/sql")
public class SQLSessionCacheController {
    
    private Log log  = LogFactory.getLog(SQLSessionCacheController.class);
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    private Resource[] mapperLocations;
    private String packageSearchPath = "classpath*:**/mappers/*.xml";
    private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// Does the record file change?
    
    @RequestMapping("/refresh")
    @ResponseBody
    public String refreshMapper() {
        try {
            Configuration configuration = this.sqlSessionFactory.getConfiguration();
            
            // step.1 Scan file
            try {
                this.scanMapperXml();
            } catch (IOException e) {
                log.error("packageSearchPath Scanning Packet Path Configuration Error ";
                return "packageSearchPath Scanning Packet Path Configuration Error.
            }
            
            System.out.println("==============Content in mapper before refresh==========================";
            for (String name : configuration.getMappedStatementNames()) {
                System.out.println(name);
            }
            
            // step.2 Determine if any documents have changed
            if (this.isChanged()) {
                // step.2.1 Clear
                this.removeConfig(configuration);
                // step.2.2 Reload
                for (Resource configLocation : mapperLocations) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                        log.info("mapper The file ["+configLocation.getFilename()+"] cached successfully ";
                    } catch (IOException e) {
                        log.error("mapper The file ["+configLocation.getFilename()+"] does not exist or the content format is incorrect ";
                        continue;
                    }
                }
            }
            
            System.out.println("==============The content in mapper after refresh==========================";
            for (String name : configuration.getMappedStatementNames()) {
                System.out.println(name);
            }
            return "The mybatis xml configuration statement was successfully refreshed.
        } catch (Exception e) {
            e.printStackTrace();
            return "Failed to refresh mybatis xml configuration statement.
        }
    }
    
    public void setPackageSearchPath(String packageSearchPath) {
        this.packageSearchPath = packageSearchPath;
    }
    
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    /**
     * The path where the xml file is scanned
     * @throws IOException 
     */
    private void scanMapperXml() throws IOException {
        this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
    }
    /**
     * Empty several important caches in 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();
    }
    
    /**
     * Determine whether the document has changed
     * @param resource
     * @return
     * @throws IOException
     */
    private boolean isChanged() throws IOException {
        boolean flag = false;
        for (Resource resource : mapperLocations) {
            String resourceName = resource.getFilename();
            
            boolean addFlag = !fileMapping.containsKey(resourceName);// This is a new logo
            
            // Modify the document: Determine whether the content of the document has changed
            Long compareFrame = fileMapping.get(resourceName);
            long lastFrame = resource.contentLength() + resource.lastModified();
            boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// This is the modified identifier
            
            // Store files when new or modified
            if(addFlag || modifyFlag) {
                fileMapping.put(resourceName, Long.valueOf(lastFrame));// File Content Frame Value
                flag = true;
            }
        }
        return flag;
    }
}



Notes:
In my application, the mybatis configuration file is placed here: classpath*:**/mappers/*.xml, so I've defined it dead and need to modify it to my own path.

Test methods:
You can access it directly by entering url on the browser through the address provided by the controller, such as: http://localhost:8080/sql/refresh  Of course, you can also use ajax to call through js, which is all right.

Topics: xml SQL Java Apache