SpringBoot集成MybatisPlus解决Mapper文件修改后动态刷新的问题
很多人在使用SpringBoot集成Mybatis或者MybatisPlus的时候在查询复杂的情况下会写mapper文件,虽然说MyBatisPlus提供了常用的增删查改,但还是难以应付复杂的查询。关于MyBatisPlus是这样介绍的:
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
- 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击
写的也是非常好,大家如果用到的话可以去官网看看:https://mp.baomidou.com
我们可以看到他的插件也提供了XML热加载,但是很不幸,3.0.6
版本上移除了该功能,不过最新快照版已加回来并打上废弃标识,3.1.0
版本上已完全移除。
所以要想使用还得自己来写。我们都知道在做项目的时候修改了xml后不重启是不行的,必须重启他才能动态的加载。如果项目大,启动时间慢,那么这样无疑中就浪费了很多时间,工欲善其事,必先利其器,一个好的架构,效率是成倍提升的。
首先这里我通过两种模式来给大家介绍下:
- 通过URL请求的形式来人为的刷新
- 通过定时任务定时去扫描
各有的个利弊,定时任务动态扫描的话如果文件超过1000多,效率不高,如果项目小,建议使用定时任务,如果项目大,建议使用URL的方式去人为刷新,修改后去执行以下刷新的请求就行。下面我们从搭建一个项目开始。
项目目录如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mybatis.dynamic</groupId>
<artifactId>mybatis-dynamic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-dynamic</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/test?autoReconnect=false&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=
mybatis-plus.mapper-locations= classpath:mapper/**/*.xml
mybatis-plus.type-aliases-package=com.mb.entity
debug = true
User.java
package com.mb.entity;
import java.io.Serializable;
public class User implements Serializable{
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserMapper.java
package com.mb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mb.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
//@Repository
public interface UserMapper extends BaseMapper<User>{
/**
* 根据ID查询
* @param id
* @return
*/
public User findById(@Param("id")Integer id);
}
MybatisDynamicApplication.java
package com.mb;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.mb.common.XMLMapperLoader;
import com.mb.entity.User;
import com.mb.mapper.UserMapper;
@SpringBootApplication
@MapperScan("com.mb.mapper")
@RestController
public class MybatisDynamicApplication {
@Autowired
private UserMapper userMapper;
@Autowired
private SqlSessionFactory sqlSessionFactory;
public static void main(String[] args) {
SpringApplication.run(MybatisDynamicApplication.class, args);
}
@RequestMapping("user")
public User user(Integer id) {
return userMapper.findById(id);
}
/**
* 第一种方式,通过Spring的方式创建Bean来管理,在构造方法中会启动新的线程
*
* @return
*/
@Bean
public XMLMapperLoader xMLMapperLoader() {
return new XMLMapperLoader(sqlSessionFactory,"classpath:mapper/**/*.xml");
}
/**
* 第二种方式 通过URL来刷新
* @return
*/
@RequestMapping("refersh")
public String refershMyBatisXml() {
return new XMLMapperLoader(sqlSessionFactory,"classpath:mapper/**/*.xml").readMapperXml();
}
}
到这里大家要特别注意,上面的两种方式最好用一种。
XMLMapperLoader.java(主要干活的)
package com.mb.common;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
*
* @author yuxuan
*
*/
public class XMLMapperLoader {
private Logger logger = LoggerFactory.getLogger(XMLMapperLoader.class);
private SqlSessionFactory sqlSessionFactory;
private Resource[] mapperLocations;
private String packageSearchPath = "classpath:mapper/**/*.xml";
private HashMap<String, Long> fileMapping = new HashMap<String, Long>();
public XMLMapperLoader(SqlSessionFactory sqlSessionFactory,String packageSearchPath) {
this.sqlSessionFactory = sqlSessionFactory;
if(packageSearchPath != null && packageSearchPath != "") {
this.packageSearchPath = packageSearchPath;
}
startThreadListener();
}
public void startThreadListener() {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
//每5秒执行一次
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
readMapperXml();
}
}, 0, 5,TimeUnit.SECONDS );
}
public String readMapperXml() {
try {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
// step.1 扫描文件
try {
this.scanMapperXml();
} catch (IOException e) {
return "packageSearchPath扫描包路径配置错误";
}
for (String name : configuration.getMappedStatementNames()) {
logger.debug(name);
}
// step.2 判断是否有文件发生了变化
if (this.isChanged()) {
// step.2.1 清理
this.removeConfig(configuration);
// step.2.2 重新加载
for (Resource configLocation : mapperLocations) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
logger.debug("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");
} catch (IOException e) {
logger.debug("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
continue;
}
}
}
for (String name : configuration.getMappedStatementNames()) {
logger.debug(name);
}
return "刷新mybatis xml配置语句成功";
} catch (Exception e) {
e.printStackTrace();
return "刷新mybatis xml配置语句失败";
}
}
public void setPackageSearchPath(String packageSearchPath) {
this.packageSearchPath = packageSearchPath;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/**
* 扫描xml文件所在的路径
* @throws IOException
*/
private void scanMapperXml() throws IOException {
this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
}
/**
* 清空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 = null;
if(configuration.getClass().getName().equals("com.baomidou.mybatisplus.core.MybatisConfiguration")) {
field = classConfig.getSuperclass().getDeclaredField(fieldName);
}else {
field = classConfig.getClass().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 = null;
if(configuration.getClass().getName().equals("com.baomidou.mybatisplus.core.MybatisConfiguration")) {
field = classConfig.getSuperclass().getDeclaredField(fieldName);
}else {
field = classConfig.getClass().getDeclaredField(fieldName);
}
field.setAccessible(true);
Set setConfig = (Set) field.get(configuration);
setConfig.clear();
}
/**
* 判断文件是否发生了变化
* @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);// 此为新增标识
// 修改文件:判断文件内容是否有变化
Long compareFrame = fileMapping.get(resourceName);
long lastFrame = resource.contentLength() + resource.lastModified();
boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识
// 新增或是修改时,存储文件
if(addFlag || modifyFlag) {
fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
flag = true;
}
}
return flag;
}
}
这个文件的话大家不需要改,直接拿到项目里边用就行了,只需要把sqlSessionFactory和mapper文件的路径传进去就行了。这里如果大家嫌弃传路径麻烦,只传一个sqlSessionFactory的话也可以,大家可以把配置的mybatisPlus-mapperLcations路径取出来在里边去做处理。
userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mb.mapper.UserMapper">
<select id="findById" parameterType="Integer" resultType="user">
SELECT * FROM user WHERE id = 10
</select>
</mapper>
以上就是所有代码的一个配置,实现功能最主要的还是XMLMapperLoader这个类。
有问题可以在下面评论,技术问题可以私聊我。
SpringBoot集成MybatisPlus解决Mapper文件修改后动态刷新的问题的更多相关文章
- win7 Host文件修改后无效的解决办法
win7 Host文件修改后无效的解决办法 正常情况下hosts文件随时修改随时生效,如果出现修改后不生效的情况,首先确定文件是ascii编码,以windows格式为换行符,然后依次采用如下方法 1 ...
- Eclipse中js文件修改后浏览器不能及时更新的解决办法
项目中js文件修改后浏览器不能及时更新的解决办法 转载:http://www.codeweblog.com/%E9%A1%B9%E7%9B%AE%E4%B8%ADjs%E6%96%87%E4%BB%B ...
- SpringBoot集成MyBatis-Plus代码生成器(Dao)
1.说明 本文基于SpringBoot集成MyBatis-Plus代码生成器, 把原来生成Entity.Mapper.Mapper XML.Service.Controller等各个模块的代码, 修改 ...
- SpringBoot集成MybatisPlus报错
SpringBoot集成MybatisPlus报错 启动的时候总是报如下错误: java.lang.annotation.AnnotationFormatError: Invalid default: ...
- SpringBoot集成MyBatis-Plus框架
1.说明 本文介绍Spring Boot集成MyBatis-Plus框架, 重点介绍需要注意的地方, 是SpringBoot集成MyBatis-Plus框架详细方法 这篇文章的脱水版, 主要是三个步骤 ...
- eclipse 使用tomcat运行JavaWeb项目,文件修改后为何不用重启tomcat? (运行web项目的4种方式)探究
1.情景说明 在eclipse中,为什么Java文件修改后,重启tomcat class文件才能生效? 为什么jsp修改后,不需重启tomcat就能立即生效? 为什么静 ...
- 13 — springboot集成mybatis-plus — 更新完毕
1.mybatis-plus需要掌握的知识 1).mybatis-plus是什么? 不写了,老衲一般都是直接进官网 mybatis-plus官网地址:https://baomidou.com/guid ...
- 让/etc/profile文件修改后立即生效
方法1: 让/etc/profile文件修改后立即生效 ,可以使用如下命令: # . /etc/profile 注意: . 和 /etc/profile 有空格 方法2: 让/etc/profile ...
- 让/etc/profile文件修改后立即生效(转)
方法1:让/etc/profile文件修改后立即生效 ,可以使用如下命令:# . /etc/profile注意: . 和 /etc/profile 有空格方法2:让/etc/profile文件修改后 ...
随机推荐
- english & utils & tools
english & utils & tools https://openlanguage.com/ https://www.grammarly.com/blog/email-writi ...
- [cf360 div1.C]The Values You Can Make[Dp]
题意:有n个硬币,面值不同,求能组成K的方案中,每个方案的硬币可以凑成那些答案. 例如, K=5 面值={1,1,1,2,3} K={1,1,1,2} K={1,1,3} K={2,3} 那么答案是 ...
- ****使用ftp软件上传下载php文件时换行符丢失bug
在使用ftp软件上传下载php源文件时,我们偶尔会发现在本地windows下notepad++编辑器写好的php文件,在使用ftp上传到linux服务器后,php文件的换行符全部丢失了,导致php文件 ...
- UVA 1025_A Spy in the Metro
[题意](小紫书)一个人从站台1出发,乘车要在时刻T到达站台n,为使在站台等车时间最短,她可以选择乘坐两个方向的列车,并在客车停靠站的时候换车. [分析]每次停站下车时,她都有三种选择,1.原地不动 ...
- 二级域名相同的情况下子页面调用父页面的js方法
这两天项目遇到这种需求.项目是一个平台级系统,其中嵌入了多款应用.在平台上可以使用这些应用操作业务. 现在产品提出了个需求:即在A应用中需要调用js方法来打开B应用. 处理方法是:平台js中给出个打开 ...
- Servlet表单数据处理
以下内容引用自http://wiki.jikexueyuan.com/project/servlet/form-data.html: 当需要从浏览器到Web服务器传递一些信息并最终传回到后台程序时,一 ...
- iframe显示滚动条
子页面通过iframe加载,出现了竖向滚动条 最后查出原因:文档申明 iframe有滚动条的页面的文档申明 <!DOCTYPE html> 改成如下就行了 <!DOCTYPE HTM ...
- 彻底来理解下hashmap吧
1.什么叫hashmap? 答:首先是一种map集合,其次呢,它是一种利用hash表来存储的数据结构.所以叫hashmap. 2.hashmap的特点是什么? 答:hashmap的特点是key值不能重 ...
- easyui中点击datagrid的分页刷新按钮,数据无法更新到最新状态
原因分析:点击刷新按钮是对当前页数据进行reload,因此所传的请求参数皆为上一次加载当前页的参数即datagrid的load方法中的参数,主要是因为请求的最终时间无法更新到最新状态 解决方案:对点击 ...
- field load respone data
问题: AJAX 使用谷歌浏览器 POST 请求报如下错误 field load respone data 使用 火狐 浏览器就正常 调试1: 发现其实我请求的回调函数能打印出来数据,但是,在netw ...