MybatisMapperRefresh.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /**
  2. * Copyright (c) 2011-2020, hubin (jobob@qq.com).
  3. * <p>
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. * <p>
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. * <p>
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.baomidou.mybatisplus.spring;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.lang.reflect.Field;
  20. import java.util.ArrayList;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import org.apache.ibatis.binding.MapperRegistry;
  27. import org.apache.ibatis.builder.xml.XMLMapperBuilder;
  28. import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
  29. import org.apache.ibatis.executor.ErrorContext;
  30. import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
  31. import org.apache.ibatis.io.Resources;
  32. import org.apache.ibatis.logging.Log;
  33. import org.apache.ibatis.logging.LogFactory;
  34. import org.apache.ibatis.parsing.XNode;
  35. import org.apache.ibatis.parsing.XPathParser;
  36. import org.apache.ibatis.session.Configuration;
  37. import org.apache.ibatis.session.SqlSessionFactory;
  38. import org.springframework.core.io.FileSystemResource;
  39. import org.springframework.core.io.Resource;
  40. import org.springframework.core.io.UrlResource;
  41. import org.springframework.util.ResourceUtils;
  42. import com.baomidou.mybatisplus.entity.GlobalConfiguration;
  43. import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
  44. import com.baomidou.mybatisplus.toolkit.SystemClock;
  45. /**
  46. * <p>
  47. * 切莫用于生产环境(后果自负)<br>
  48. * Mybatis 映射文件热加载(发生变动后自动重新加载).<br>
  49. * 方便开发时使用,不用每次修改xml文件后都要去重启应用.<br>
  50. * </p>
  51. *
  52. * @author nieqiurong
  53. * @Date 2016-08-25
  54. */
  55. public class MybatisMapperRefresh implements Runnable {
  56. private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);
  57. /**
  58. * 记录jar包存在的mapper
  59. */
  60. private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
  61. private SqlSessionFactory sqlSessionFactory;
  62. @Deprecated
  63. private Resource[] mapperLocations;
  64. private Long beforeTime = 0L;
  65. private Configuration configuration;
  66. /**
  67. * 是否开启刷新mapper
  68. */
  69. private boolean enabled;
  70. /**
  71. * xml文件目录
  72. */
  73. private Set<String> fileSet;
  74. /**
  75. * 延迟加载时间
  76. */
  77. private int delaySeconds = 10;
  78. /**
  79. * 刷新间隔时间
  80. */
  81. private int sleepSeconds = 20;
  82. /**
  83. * see com.baomidou.mybatisplus.spring.MybatisMapperRefresh#MybatisMapperRefresh(org.apache.ibatis.session.SqlSessionFactory, int, int, boolean)
  84. *
  85. * @param mapperLocations
  86. * @param sqlSessionFactory
  87. * @param delaySeconds
  88. * @param sleepSeconds
  89. * @param enabled
  90. */
  91. public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, int delaySeconds,
  92. int sleepSeconds, boolean enabled) {
  93. this.mapperLocations = mapperLocations.clone();
  94. this.sqlSessionFactory = sqlSessionFactory;
  95. this.delaySeconds = delaySeconds;
  96. this.enabled = enabled;
  97. this.sleepSeconds = sleepSeconds;
  98. this.configuration = sqlSessionFactory.getConfiguration();
  99. this.run();
  100. }
  101. /**
  102. * see com.baomidou.mybatisplus.spring.MybatisMapperRefresh#MybatisMapperRefresh(org.apache.ibatis.session.SqlSessionFactory, boolean)
  103. *
  104. * @param mapperLocations
  105. * @param sqlSessionFactory
  106. * @param enabled
  107. */
  108. @Deprecated
  109. public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, boolean enabled) {
  110. this.mapperLocations = mapperLocations.clone();
  111. this.sqlSessionFactory = sqlSessionFactory;
  112. this.enabled = enabled;
  113. this.configuration = sqlSessionFactory.getConfiguration();
  114. this.run();
  115. }
  116. public MybatisMapperRefresh(SqlSessionFactory sqlSessionFactory, boolean enabled) throws Exception {
  117. this.sqlSessionFactory = sqlSessionFactory;
  118. this.enabled = enabled;
  119. this.configuration = sqlSessionFactory.getConfiguration();
  120. this.run();
  121. }
  122. public MybatisMapperRefresh(SqlSessionFactory sqlSessionFactory, int delaySeconds, int sleepSeconds, boolean enabled) throws Exception {
  123. this.sqlSessionFactory = sqlSessionFactory;
  124. this.delaySeconds = delaySeconds;
  125. this.sleepSeconds = sleepSeconds;
  126. this.enabled = enabled;
  127. this.configuration = sqlSessionFactory.getConfiguration();
  128. this.run();
  129. }
  130. @Override
  131. public void run() {
  132. final GlobalConfiguration globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
  133. /*
  134. * 启动 XML 热加载
  135. */
  136. if (enabled) {
  137. beforeTime = SystemClock.now();
  138. final MybatisMapperRefresh runnable = this;
  139. new Thread(new Runnable() {
  140. @Override
  141. public void run() {
  142. if (fileSet == null) {
  143. fileSet = new HashSet<>();
  144. if (mapperLocations != null) {
  145. for (Resource mapperLocation : mapperLocations) {
  146. try {
  147. if (ResourceUtils.isJarURL(mapperLocation.getURL())) {
  148. String key = new UrlResource(ResourceUtils.extractJarFileURL(mapperLocation.getURL()))
  149. .getFile().getPath();
  150. fileSet.add(key);
  151. if (jarMapper.get(key) != null) {
  152. jarMapper.get(key).add(mapperLocation);
  153. } else {
  154. List<Resource> resourcesList = new ArrayList<>();
  155. resourcesList.add(mapperLocation);
  156. jarMapper.put(key, resourcesList);
  157. }
  158. } else {
  159. fileSet.add(mapperLocation.getFile().getPath());
  160. }
  161. } catch (IOException ioException) {
  162. ioException.printStackTrace();
  163. }
  164. }
  165. }
  166. }
  167. try {
  168. Thread.sleep(delaySeconds * 1000);
  169. } catch (InterruptedException interruptedException) {
  170. interruptedException.printStackTrace();
  171. }
  172. do {
  173. try {
  174. for (String filePath : fileSet) {
  175. File file = new File(filePath);
  176. if (file.isFile() && file.lastModified() > beforeTime) {
  177. globalConfig.setRefresh(true);
  178. List<Resource> removeList = jarMapper.get(filePath);
  179. if (removeList != null && !removeList.isEmpty()) {// 如果是jar包中的xml,将刷新jar包中存在的所有xml,后期再修改加载jar中修改过后的xml
  180. for (Resource resource : removeList) {
  181. runnable.refresh(resource);
  182. }
  183. } else {
  184. runnable.refresh(new FileSystemResource(file));
  185. }
  186. }
  187. }
  188. if (globalConfig.isRefresh()) {
  189. beforeTime = SystemClock.now();
  190. }
  191. globalConfig.setRefresh(true);
  192. } catch (Exception exception) {
  193. exception.printStackTrace();
  194. }
  195. try {
  196. Thread.sleep(sleepSeconds * 1000);
  197. } catch (InterruptedException interruptedException) {
  198. interruptedException.printStackTrace();
  199. }
  200. } while (true);
  201. }
  202. }, "mybatis-plus MapperRefresh").start();
  203. }
  204. }
  205. /**
  206. * 刷新mapper
  207. *
  208. * @throws Exception
  209. */
  210. @SuppressWarnings("rawtypes")
  211. private void refresh(Resource resource) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
  212. this.configuration = sqlSessionFactory.getConfiguration();
  213. boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
  214. try {
  215. Field loadedResourcesField = isSupper ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
  216. : configuration.getClass().getDeclaredField("loadedResources");
  217. loadedResourcesField.setAccessible(true);
  218. Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
  219. XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
  220. new XMLMapperEntityResolver());
  221. XNode context = xPathParser.evalNode("/mapper");
  222. String namespace = context.getStringAttribute("namespace");
  223. Field field = MapperRegistry.class.getDeclaredField("knownMappers");
  224. field.setAccessible(true);
  225. Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
  226. mapConfig.remove(Resources.classForName(namespace));
  227. loadedResourcesSet.remove(resource.toString());
  228. configuration.getCacheNames().remove(namespace);
  229. cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
  230. cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
  231. cleanKeyGenerators(context.evalNodes("insert|update"), namespace);
  232. cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);
  233. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
  234. sqlSessionFactory.getConfiguration(), // 注入的sql先不进行处理了
  235. resource.toString(), sqlSessionFactory.getConfiguration().getSqlFragments());
  236. xmlMapperBuilder.parse();
  237. logger.debug("refresh: '" + resource + "', success!");
  238. } catch (IOException e) {
  239. logger.error("Refresh IOException :" + e.getMessage());
  240. } finally {
  241. ErrorContext.instance().reset();
  242. }
  243. }
  244. /**
  245. * 清理parameterMap
  246. *
  247. * @param list
  248. * @param namespace
  249. */
  250. private void cleanParameterMap(List<XNode> list, String namespace) {
  251. for (XNode parameterMapNode : list) {
  252. String id = parameterMapNode.getStringAttribute("id");
  253. configuration.getParameterMaps().remove(namespace + "." + id);
  254. }
  255. }
  256. /**
  257. * 清理resultMap
  258. *
  259. * @param list
  260. * @param namespace
  261. */
  262. private void cleanResultMap(List<XNode> list, String namespace) {
  263. for (XNode resultMapNode : list) {
  264. String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
  265. configuration.getResultMapNames().remove(id);
  266. configuration.getResultMapNames().remove(namespace + "." + id);
  267. clearResultMap(resultMapNode, namespace);
  268. }
  269. }
  270. private void clearResultMap(XNode xNode, String namespace) {
  271. for (XNode resultChild : xNode.getChildren()) {
  272. if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
  273. || "case".equals(resultChild.getName())) {
  274. if (resultChild.getStringAttribute("select") == null) {
  275. configuration.getResultMapNames().remove(
  276. resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
  277. configuration.getResultMapNames().remove(
  278. namespace + "." + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
  279. if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
  280. clearResultMap(resultChild, namespace);
  281. }
  282. }
  283. }
  284. }
  285. }
  286. /**
  287. * 清理selectKey
  288. *
  289. * @param list
  290. * @param namespace
  291. */
  292. private void cleanKeyGenerators(List<XNode> list, String namespace) {
  293. for (XNode context : list) {
  294. String id = context.getStringAttribute("id");
  295. configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
  296. configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
  297. }
  298. }
  299. /**
  300. * 清理sql节点缓存
  301. *
  302. * @param list
  303. * @param namespace
  304. */
  305. private void cleanSqlElement(List<XNode> list, String namespace) {
  306. for (XNode context : list) {
  307. String id = context.getStringAttribute("id");
  308. configuration.getSqlFragments().remove(id);
  309. configuration.getSqlFragments().remove(namespace + "." + id);
  310. }
  311. }
  312. }