大家好,我是烤鸭:

今天介绍一下springboot mybatis 热加载mapper.xml文件。

本来不打算写的,看到网上比较流行的方式都比较麻烦,想着简化一下。

网上流行的版本。

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

总结一下需要:mybatis-config,mybatis-refresh.properties,MapperRefresh.java,SqlSessionFactoryBean.java

按照这个博客写的,确实挺好用的。但是,springboot简便就是简便在没有配置文件。

于是看看能不能优化一下。优化后只需要mybatis-refresh.properties,MapperRefresh.java+一行代码

环境:

springboot     2.0.0.RELEASE

mybatis    3.4.4

mybatis-spring-boot-starter    1.3.0

1.    MapperRefresh.java(同上,复制)

定时读取指定目录下的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;         // 刷新启用后,是否启动了刷新线程  private Set<String> location;         // Mapper实际资源路径  private Resource[] mapperLocations;     // Mapper资源路径  private Configuration configuration;        // MyBatis配置对象  private Long beforeTime = 0L;           // 上一次刷新时间  private static int delaySeconds;        // 延迟刷新秒数  private static int sleepSeconds;        // 休眠时间  private static String mappingPath;      // xml文件夹匹配字符串,需要根据需要修改  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() {  @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();  }  }  /** * 执行刷新 * @param filePath 刷新目录 * @param beforeTime 上次刷新时间 * @throws NestedIOException 解析异常 * @throws FileNotFoundException 文件未找到 * @author ThinkGem */  @SuppressWarnings({ "rawtypes", "unchecked" })  private void refresh(String filePath, Long beforeTime) throws Exception {  // 本次刷新时间  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);  }  }  // 清理已加载的资源标识,方便让它重新加载。  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());  }  }  // 如果刷新了文件,则修改刷新时间,否则不修改  if (fileList.size() > 0) {  this.beforeTime = refrehTime;  }  }  /** * 获取需要刷新的文件列表 * @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;  }  /** * 获取整数属性 * @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 如果现在状态为刷新,则刷新(先删除后添加)  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(同上,复制)

设置读取配置文件的参数,定时和频率,是否多线程

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

3.     关于sqlSessionFactory

网上大多的实现方式都是重新创建SqlSessionFactory,然后再注入。

类似这样:

我的疑问:

1.    MapperRefresh需要当前sqlSessionBean的configuration。既然springboot都已经sqlSessionFactory把创建好了,直接获取sqlSessionBean的configuration不就好了么。

2.    yml配置mybatis的时候,没有sqlMapConfig(myabtis-config).xml这个配置文件,还得先创建一个,很不方便。试着把配置文件那行注释掉,会报错,Location 不能为空。

4.    改进

只需要在mybatis的sqlSessionFactory创建完成,注入SqlSession,调用MapperRefresh启动。

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();}
}

启动类:

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# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 50000validationQuery: select 'x'testWhileIdle: truetestOnBorrow: falsetestOnReturn: false# 打开PSCache,并且指定每个连接上PSCache的大小poolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙   #,wallfilters: stat,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 合并多个DruidDataSource的监控数据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存储方式,默认ehcache,可选redis
cacheType: ehcache

5.    多说一句

关于mybatis-refresh.properties中mappingPath

指的是src/main/resources的最父级的mybatis文件夹,按下图的话:

mappingPath=mybatis

springboot mybatis 热加载mapper.xml文件(最简单)相关推荐

  1. SpringBoot+Mybatis加载Mapper.xml文件的两种方式

    前言:我们在平常工作中用到mybatis去加载Mapper.xml文件,可能mapper文件放的路径不一样,由此我们需要配置多个路径,幸运的是Mybatis支持我们配置多个不同路径.现在介绍两种方法. ...

  2. (转)mybatis热部署加载*Mapper.xml文件,手动刷新*Mapper.xml文件

    转自: https://blog.csdn.net/LOVELONG8808/article/details/78738086 由于项目已经发布到线上,要是修改一个Mapper.xml文件的话,需要重 ...

  3. 项目启动过后,停在了加载mapper xml文件过程中

    原因:查看Mapper接口中是否有断点 intellij idea 如何一键清除所有断点

  4. DB数据源之SpringBoot+MyBatis踏坑过程(三)手工+半自动注解配置数据源与加载Mapper.xml扫描...

    DB数据源之SpringBoot+MyBatis踏坑过程(三)手工+半自动注解配置数据源与加载Mapper.xml扫描 liuyuhang原创,未经允许禁止转载    系列目录连接 DB数据源之Spr ...

  5. spring boot:从零开始搭建一个项目 - day 7 springboot devtools热加载+MybatisPlus配置+kisso从入门到放弃

    spring boot:从零开始搭建一个项目 - day 7 springboot devtools热加载+MybatisPlus配置+kisso从入门到放弃 一.springboot devtool ...

  6. mybatis热加载的实现

    最近在使用mybatis,由于是刚刚开始用,用的并不顺手,目前是感觉有2个地方非常的不好用: 1.mybatis调试不方便 由于dao层只有接口,实现只是一个map的xml文件,想加断点都没有地方加, ...

  7. SSM项目使用junit单元测试时Mybatis通配符加载Mapper不能正常加载

    问题描述:项目使用maven build 以及tomcat run能够正常运行,但是使用junit单元测试时却报错误 Caused by: java.lang.IllegalArgumentExcep ...

  8. 后端技术:SpringBoot配置热加载工具(devtools)笔记

    今天给大家介绍IDEA开发工具如何配置devtools热加载工具. 1.devtools原理介绍 spring-boot-devtools是spring为开发者提供的热加载工具包. 主要原理是使用了两 ...

  9. IDEA-jetty热加载(类文件和静态资源)

    一.场景: 1.实际项目开发.调试阶段,重新打包部署影响效率,热部署.热加载可以提高效率: 2.IDEA在running/debugging状态下没有自动编译的选项: 二.Jetty的maven插件: ...

最新文章

  1. python response.json()报错_Django JsonResponse json格式报错 解决Django响应JsonResponse返回json格式数据报错问题...
  2. 添加css的方式:link与@import区别
  3. 【网络安全】Metasploit生成的Shellcode的导入函数解析以及执行流程分析(2)
  4. [源码和文档分享]基于C++实现的运动会统分系统
  5. C#中的Explicit和Implicit了解一下吧
  6. yii schema.mysql.sql_YII学习,初体验 ,对YII的一些理解.
  7. struct和typedef struct彻底明白了
  8. Eclipse·Maven·构建SpringMVC简单工程-3
  9. 7个示例科普CPU Cache(from 酷壳网)
  10. 云服务器宽带1M代表的什么意思?下载速度是1M/S吗?
  11. note2便携式WLAN热点开启后没连接时自动关闭时长在哪设置
  12. 联想台式计算机重装系统教程,台式机装系统步骤 联想台式电脑怎么重装系统...
  13. java jnlp被阻止_JNLP 被java安全阻止
  14. [转]Linux块设备加速缓存bcache和dm-cache:使用SSD来加速服务器
  15. 关于性能测试中使用物理机和虚拟机的区别
  16. 文字细化算法matlab,[转载](转)二值图像的细化算法和源程序代码(Matlab)
  17. 初学编程该如何选择编程语言?
  18. 入门级资料——电池的工作过程
  19. 在 SAP BTP 上体验 SAP HANA Cloud 试用版本
  20. 2016年指维科技的P2P网贷平台系统市场占有率居榜首

热门文章

  1. [html] 如何关闭HTML页面在IOS下的键盘首字母自动大写?
  2. [html] 写一个标签云的布局
  3. [css] 怎么IE6下在使用margin:0 auto;无法使其居中?
  4. 前端学习(2782):获取轮播图的数据
  5. 前端学习(2648):vue3.0的处理展示
  6. “睡服”面试官系列第十三篇之函数的扩展(建议收藏学习)
  7. oracle之单行函数1
  8. 前端学习(1288):nodejs模块化开发
  9. 前端学习(4):chome浏览器
  10. java学习(10):数据类型