日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

springboot mybatis 热加载mapper.xml文件(最简单)

發(fā)布時(shí)間:2023/12/10 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 springboot mybatis 热加载mapper.xml文件(最简单) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

大家好,我是烤鴨:

? ? 今天介紹一下springboot mybatis 熱加載mapper.xml文件。

? ? 本來不打算寫的,看到網(wǎng)上比較流行的方式都比較麻煩,想著簡(jiǎn)化一下。

? ? 網(wǎng)上流行的版本。

????https://www.cnblogs.com/oskyhg/p/8587701.html

? ? 總結(jié)一下需要:mybatis-config,mybatis-refresh.properties,MapperRefresh.java,SqlSessionFactoryBean.java

? ? 按照這個(gè)博客寫的,確實(shí)挺好用的。但是,springboot簡(jiǎn)便就是簡(jiǎn)便在沒有配置文件。

? ? 于是看看能不能優(yōu)化一下。優(yōu)化后只需要mybatis-refresh.propertiesMapperRefresh.java+一行代碼

環(huán)境:

? ? springboot ????2.0.0.RELEASE? ??

????mybatis????3.4.4

????mybatis-spring-boot-starter????1.3.0

1.????MapperRefresh.java(同上,復(fù)制)

????????定時(shí)讀取指定目錄下的mapper.xml文件,是否被修改

package com.xxx.web.common.config.mybatis;import java.io.File; import java.io.FileInputStream; 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 org.apache.commons.lang.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; // 刷新啟用后,是否啟動(dòng)了刷新線程 private Set<String> location; // Mapper實(shí)際資源路徑 private Resource[] mapperLocations; // Mapper資源路徑 private Configuration configuration; // MyBatis配置對(duì)象 private Long beforeTime = 0L; // 上一次刷新時(shí)間 private static int delaySeconds; // 延遲刷新秒數(shù) private static int sleepSeconds; // 休眠時(shí)間 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) { // 啟動(dòng)刷新線程 final MapperRefresh runnable = this; new Thread(new java.lang.Runnable() { @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()); 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 { for (String s : location) { runnable.refresh(s, beforeTime); } } catch (Exception e1) { e1.printStackTrace(); } try { Thread.sleep(sleepSeconds * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "MyBatis-Mapper-Refresh").start(); } } /** * 執(zhí)行刷新 * @param filePath 刷新目錄 * @param beforeTime 上次刷新時(shí)間 * @throws NestedIOException 解析異常 * @throws FileNotFoundException 文件未找到 * @author ThinkGem */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void refresh(String filePath, Long beforeTime) throws Exception { // 本次刷新時(shí)間 Long refrehTime = System.currentTimeMillis(); // 獲取需要刷新的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 < fileList.size(); i++) { InputStream inputStream = new FileInputStream(fileList.get(i)); String resource = fileList.get(i).getAbsolutePath(); try { // 清理原有資源,更新為自己的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); } } // 清理已加載的資源標(biāo)識(shí),方便讓它重新加載。 Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources"); loadedResourcesField.setAccessible(true); Set loadedResourcesSet = ((Set)loadedResourcesField.get(configuration)); loadedResourcesSet.remove(resource); //重新編譯加載資源文件。 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + resource + "'", 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()); } } // 如果刷新了文件,則修改刷新時(shí)間,否則不修改 if (fileList.size() > 0) { this.beforeTime = refrehTime; } } /** * 獲取需要刷新的文件列表 * @param dir 目錄 * @param beforeTime 上次刷新時(shí)間 * @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 上次刷新時(shí)間 * @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 方法,允許反復(fù) 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; } } } }

2.????mybatis-refresh.properties(同上,復(fù)制)

????? ?設(shè)置讀取配置文件的參數(shù),定時(shí)和頻率,是否多線程

enabled=true delaySeconds=30 sleepSeconds=10 mappingPath=mybatis

?3.?? ? 關(guān)于sqlSessionFactory

????? ? 網(wǎng)上大多的實(shí)現(xiàn)方式都是重新創(chuàng)建SqlSessionFactory,然后再注入。

????? ? 類似這樣:

? ?????

????? ? 我的疑問:

????? ? 1.????MapperRefresh需要當(dāng)前sqlSessionBean的configuration。既然springboot都已經(jīng)sqlSessionFactory把創(chuàng)建好了,直接獲取sqlSessionBean的configuration就好了么。

????? ? 2.? ? yml配置mybatis的時(shí)候,沒有sqlMapConfig(myabtis-config).xml這個(gè)配置文件,還得先創(chuàng)建一個(gè),很不方便。試著把配置文件那行注釋掉,會(huì)報(bào)錯(cuò),Location 不能為空。

4.? ? 改進(jìn)

????? ? 只需要在mybatis的sqlSessionFactory創(chuàng)建完成,注入SqlSession,調(diào)用MapperRefresh啟動(dòng)。

????? ? RootConfiguration.java

package com.xxx.web.common.config;import java.io.IOException; import java.util.concurrent.Executors;import javax.annotation.PostConstruct;import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Controller;import com.xxx.web.common.config.mybatis.MapperRefresh;@Configuration @ComponentScan(value = "com.xxx", excludeFilters = { @Filter(Controller.class),@Filter(type = FilterType.ASSIGNABLE_TYPE, value = { RootConfiguration.class }) }) @MapperScan({"com.xxx.web.**.dao"}) public class RootConfiguration extends SpringBootServletInitializer {@Autowiredprivate SqlSession sqlSession;@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {application.registerShutdownHook(false);return application.sources(RootConfiguration.class);}@PostConstructpublic void postConstruct() throws IOException {//Constant.threadPool = Executors.newFixedThreadPool(20);Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/**/*Mapper.xml");new MapperRefresh(resources, sqlSession.getConfiguration()).run();} }

啟動(dòng)類:

????????MainApplication.java

package com.xxx.web;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;import com.xxx.web.common.config.RootConfiguration;@SpringBootApplication public class MainApplication {public static void main(String[] args) {SpringApplication.run(RootConfiguration.class, args);} }

application.yml

spring: thymeleaf:prefix: classpath:/templates/datasource: type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/xxx?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8username: adminpassword: admin@2017initialSize: 1minIdle: 3maxActive: 20# 配置獲取連接等待超時(shí)的時(shí)間maxWait: 60000# 配置間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一個(gè)連接在池中最小生存的時(shí)間,單位是毫秒minEvictableIdleTimeMillis: 50000validationQuery: select 'x'testWhileIdle: truetestOnBorrow: falsetestOnReturn: false# 打開PSCache,并且指定每個(gè)連接上PSCache的大小poolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20# 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻 #,wallfilters: stat,slf4j# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 合并多個(gè)DruidDataSource的監(jiān)控?cái)?shù)據(jù)useGlobalDataSourceStat: true mybatis: configuration:map-underscore-to-camel-case: true#打印日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: mybatis/**/*Mapper.xmltypeAliasesPackage: com.xxx.entity.* #配置緩存和session存儲(chǔ)方式,默認(rèn)ehcache,可選redis cacheType: ehcache

5.? ? 多說一句

????? ? 關(guān)于mybatis-refresh.properties中mappingPath

????????????指的是src/main/resources的最父級(jí)的mybatis文件夾,按下圖的話:

mappingPath=mybatis

????????

總結(jié)

以上是生活随笔為你收集整理的springboot mybatis 热加载mapper.xml文件(最简单)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。