關于MyBatis中Mapper?XML熱加載優(yōu)化
前幾天在琢磨mybatis xml熱加載的問題,原理還是通過定時掃描xml文件去跟新,但放到項目上就各種問題,由于用了mybatisplus死活不生效。本著"即插即用"的原則,狠心把其中的代碼優(yōu)化了一遍,能夠兼容mybatisplus,還加入了一些日志,直接上代碼
package com.bzd.core.mybatis; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.net.URL; 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 lombok.SneakyThrows; 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.slf4j.Logger; import org.slf4j.LoggerFactory; 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 = LoggerFactory.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; // 延遲刷新秒數(shù) private static int sleepSeconds; // 休眠時間 private static String mappingPath; // xml文件夾匹配字符串,需要根據(jù)需要修改 static { // try { // prop.load(MapperRefresh.class.getResourceAsStream(filename)); // } catch (Exception e) { // e.printStackTrace(); // System.out.println("Load mybatis-refresh “"+filename+"” file error."); // } URL url = MapperRefresh.class.getClassLoader().getResource(filename); InputStream is; try { is = url.openStream(); if (is == null) { log.warn("applicationConfig.properties not found."); } else { prop.load(is); } } catch (IOException e) { e.printStackTrace(); } String value = getPropString("enabled"); System.out.println(value); enabled = "true".equalsIgnoreCase(value); 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() { @SneakyThrows @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()); s = mapperLocation.getFile().getParent(); 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 { log.info("start refresh"); List<Resource> res = getModifyResource(mapperLocations,beforeTime); if(res.size()>0) runnable.refresh(res); // 如果刷新了文件,則修改刷新時間,否則不修改 beforeTime = System.currentTimeMillis(); log.info("end refresh("+res.size()+")"); } catch (Exception e1) { e1.printStackTrace(); } try { Thread.sleep(sleepSeconds * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "MyBatis-Mapper-Refresh").start(); } } private List<Resource> getModifyResource(Resource[] mapperLocations,long time){ List<Resource> resources = new ArrayList<>(); for (int i = 0; i < mapperLocations.length; i++) { try { if(isModify(mapperLocations[i],time)) resources.add(mapperLocations[i]); } catch (IOException e) { throw new RuntimeException("讀取mapper文件異常",e); } } return resources; } private boolean isModify(Resource resource,long time) throws IOException { if (resource.lastModified() > time) { return true; } return false; } /** * 執(zhí)行刷新 * @param filePath 刷新目錄 * @param beforeTime 上次刷新時間 * @throws NestedIOException 解析異常 * @throws FileNotFoundException 文件未找到 * @author ThinkGem */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void refresh(List<Resource> mapperLocations) throws Exception { // 獲取需要刷新的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 < mapperLocations.size(); i++) { Resource resource = mapperLocations.get(i); InputStream inputStream = resource.getInputStream(); System.out.println("refreshed : "+resource.getDescription()); //這個是mybatis 加載的資源標識,沒有絕對標準 String resourcePath = resource.getDescription(); try { clearMybatis(resourcePath); //重新編譯加載資源文件。 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, resourcePath, configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + resourcePath + "'", 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()); }*/ } } private void clearMybatis(String resource) throws NoSuchFieldException, IllegalAccessException { // 清理原有資源,更新為自己的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); } /** * 獲取需要刷新的文件列表 * @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; } /** * 獲取整數(shù)屬性 * @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).trim(); } /** * 重寫 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 如果現(xiàn)在狀態(tài)為刷新,則刷新(先刪除后添加) 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; } } } }
這里提供兩種配置方式一種是springboot,一種就是普通的spring項目
1.springboot 方式 就是
package com.bzd.bootadmin.conf; import com.bzd.core.mybatis.MapperRefresh; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.MybatisProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import javax.annotation.PostConstruct; /** * * @author Created by bzd on 2020/12/28/15:10 **/ @Configuration @MapperScan(value = {"com.bzd.bootadmin.modular.index.mapper"}) public class MybatisConfig { @Autowired SqlSessionFactory factory; @Autowired MybatisProperties properties; @PostConstruct public void postConstruct() { Resource[] resources = this.properties.resolveMapperLocations(); new MapperRefresh(resources, factory.getConfiguration()).run(); } }
2:普通spring項目
import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.core.io.Resource; import javax.annotation.PostConstruct; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created By lxr on 2020/5/3 **/ public class RefreshStarter { SqlSessionFactory factory; Resource[] mapperLocations; @PostConstruct public void postConstruct() throws IOException { Resource[] resources = mapperLocations; enableMybatisPlusRefresh(factory.getConfiguration()); new MapperRefresh(resources, factory.getConfiguration()).run(); } /** * 反射配置開啟 MybatisPlus 的 refresh,不使用MybatisPlus也不會有影響 * @param configuration */ public void enableMybatisPlusRefresh(Configuration configuration){ try { Method method = Class.forName("com.baomidou.mybatisplus.toolkit.GlobalConfigUtils") .getMethod("getGlobalConfig", Configuration.class); Object globalConfiguration = method.invoke(null, configuration); method = Class.forName("com.baomidou.mybatisplus.entity.GlobalConfiguration") .getMethod("setRefresh", boolean.class); method.invoke(globalConfiguration, true); } catch (ClassNotFoundException e) { } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } public SqlSessionFactory getFactory() { return factory; } public void setFactory(SqlSessionFactory factory) { this.factory = factory; } public Resource[] getMapperLocations() { return mapperLocations; } public void setMapperLocations(Resource[] mapperLocations) { this.mapperLocations = mapperLocations; } }
在xml中配置
<bean class="com.foxtail.core.mybatis.RefreshStarter"> <property name="mapperLocations"> <array> <value>classpath:com/foxtail/mapping/*/*.xml</value> <value>classpath:com/foxtail/mapping/*.xml</value> </array> </property> <property name="factory" ref="sqlSessionFactory" /> </bean>
最后在resources中加入mybatis-refresh.properties
#是否開啟刷新線程 enabled=true #延遲啟動刷新程序的秒數(shù) delaySeconds=5 #刷新掃描間隔的時長秒數(shù) sleepSeconds=3
到這里就大功告成了,最后加到代碼里面有沒有生效呢,改完sql之后總會想到底是更新了還是沒更新,又看不到,這是程序員最頭疼的。因為加了日志都不用擔心啦
到此這篇關于關于MyBatis中Mapper XML熱加載優(yōu)化的文章就介紹到這了,更多相關MyBatis Mapper XML熱加載內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用JDBC工具類實現(xiàn)簡單的登錄管理系統(tǒng)
這篇文章主要為大家詳細介紹了使用JDBC工具類實現(xiàn)簡單的登錄管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02SpringCloud之動態(tài)刷新、重試、服務化的實現(xiàn)
這篇文章主要介紹了SpringCloud 之動態(tài)刷新、重試、服務化的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10解決后端傳long類型數(shù)據(jù)到前端精度丟失問題
這篇文章主要介紹了解決后端傳long類型數(shù)據(jù)到前端精度丟失問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Spring security密碼加密實現(xiàn)代碼實例
這篇文章主要介紹了Spring security密碼加密實現(xiàn)代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-04-04