Mybatis plus 3.1 xml 热更新
程序员文章站
2022-10-04 08:43:45
Mybatis plus 3.1 xml 热更新环境介绍功能说明代码环境介绍mybatis plus3.1 + springboot功能说明1.监控项目源码中的xml变化自动热更新代码import lombok.Data;import lombok.EqualsAndHashCode;import lombok.SneakyThrows;import lombok.experimental.Accessors;import lombok.extern.slf4j.Slf4j;impor...
环境介绍
mybatis plus3.1 + springboot
功能说明
1.监控项目源码中的xml变化自动热更新
代码
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import org.zyq.core.algorithm.MD5;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* zouyq 2020 07 15
*/
@Slf4j
public class MybatisXmlHotRefresh implements Runnable {
/**
* 记录jar包存在的mapper
*/
private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
private SqlSessionFactory sqlSessionFactory;
private List<Resource> mapperLocations;
private Configuration configuration;
private Set<String> fileSet = new HashSet<>();
private int delaySeconds = 1;
private int sleepSeconds = 20;
public MybatisXmlHotRefresh(SqlSessionFactory sqlSessionFactory, List<String> packageSearchPath,
int sleepSeconds) {
PathMatchingResourcePatternResolver path_reolver = new PathMatchingResourcePatternResolver();
this.mapperLocations = packageSearchPath.stream().flatMap(p -> {
try {
return Stream.of(path_reolver.getResources(p));
} catch (IOException e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
this.sqlSessionFactory = sqlSessionFactory;
this.sleepSeconds = sleepSeconds;
this.configuration = sqlSessionFactory.getConfiguration();
this.run();
}
private volatile boolean in_do;
private List<String> s_path = List.of("/src/main/java", "/src/main/resources");
public Struct idea_gradle_find_src(File file) {
Struct s = new Struct();
String absolutePath = file.getAbsolutePath();
s.setBuildFilePath(absolutePath);
int r_res_path = absolutePath.lastIndexOf("/resources/main");
int d = 15;
if (r_res_path == -1) {
d = 10;
r_res_path = absolutePath.lastIndexOf("/resources");
}
String prefix = absolutePath.substring(0, r_res_path);
String suffix = absolutePath.substring(r_res_path + d);
int out_index = prefix.indexOf("/out/");
if (out_index == -1) {
out_index = prefix.lastIndexOf("/build");
}
if (out_index > -1) {
String o = absolutePath.substring(0, out_index);
for (String p : s_path) {
File f = new File(o + p + suffix);
if (f.exists()) {
s.setSrc(f);
s.setLast_time(f.lastModified());
s.setMd5(md5(f));
s.setName(f.getName());
break;
}
}
}
return s;
}
@Override
public void run() {
// final GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
final MybatisXmlHotRefresh runnable = this;
new Thread(new Runnable() {
private List<Struct> structs = new ArrayList<>();
@Override
public void run() {
if (mapperLocations != null) {
for (Resource mapperLocation : mapperLocations) {
try {
if (ResourceUtils.isJarURL(mapperLocation.getURL())) {
String key = new UrlResource(ResourceUtils.extractJarFileURL(mapperLocation.getURL()))
.getFile().getPath();
fileSet.add(key);
if (jarMapper.get(key) != null) {
jarMapper.get(key).add(mapperLocation);
} else {
List<Resource> resourcesList = new ArrayList<>();
resourcesList.add(mapperLocation);
jarMapper.put(key, resourcesList);
}
} else {
fileSet.add(mapperLocation.getFile().getPath());
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
fileSet.parallelStream().forEach(f -> {
File file = new File(f);
structs.add(idea_gradle_find_src(file));
});
try {
Thread.sleep(delaySeconds * 1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
do {
if (!in_do) {
try {
for (Struct s : structs) {
if (s.newLastTime() > s.getLast_time()) {
in_do = true;
break;
}
}
if (in_do) {
List<Struct> collect = structs.stream().filter(f -> {
String new_md5 = md5(f.getSrc());
boolean b = !Objects.equals(f.md5, new_md5);
if (b) {
f.setLast_time(new File(f.getSrc().getAbsolutePath()).lastModified());
f.setMd5(new_md5);
}
return b;
}).collect(Collectors.toList());
for (Struct filePath : collect) {
List<Resource> removeList = jarMapper.get(filePath.getBuildFilePath());
if (removeList != null && !removeList.isEmpty()) {
for (Resource resource : removeList) {
runnable.refresh(resource);
}
} else {
runnable.refresh(new FileSystemResource(filePath.getSrc()));
}
}
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
in_do = false;
}
}
try {
Thread.sleep(sleepSeconds * 1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
} while (true);
}
}, getClass().getName()).start();
}
@SneakyThrows
public String md5(File file) {
return MD5.encode(file);
}
@SneakyThrows
@SuppressWarnings("rawtypes")
private void refresh(Resource resource) {
this.configuration = sqlSessionFactory.getConfiguration();
boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
try {
Field loadedResourcesField = isSupper ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
: configuration.getClass().getDeclaredField("loadedResources");
loadedResourcesField.setAccessible(true);
Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
new XMLMapperEntityResolver());
XNode context = xPathParser.evalNode("/mapper");
String namespace = context.getStringAttribute("namespace");
Field field = MapperRegistry.class.getDeclaredField("knownMappers");
field.setAccessible(true);
Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
Collection<String> mappedStatementNames = configuration.getMappedStatementNames();
mapConfig.remove(Resources.classForName(namespace));
loadedResourcesSet.remove(resource.toString());
configuration.getCacheNames().remove(namespace);
cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
cleanKeyGenerators(context.evalNodes("insert|update|select"), namespace);
cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
sqlSessionFactory.getConfiguration(),
resource.toString(), sqlSessionFactory.getConfiguration().getSqlFragments());
xmlMapperBuilder.parse();
log.debug("热更新: {}, 成功!", resource.getFilename());
} catch (IOException e) {
log.error("Refresh IOException :" + e.getMessage());
} finally {
ErrorContext.instance().reset();
}
}
private void cleanParameterMap(List<XNode> list, String namespace) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
configuration.getParameterMaps().remove(namespace + "." + id);
}
}
private void cleanResultMap(List<XNode> list, String namespace) {
for (XNode resultMapNode : list) {
String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
configuration.getResultMapNames().remove(id);
configuration.getResultMapNames().remove(namespace + "." + id);
clearResultMap(resultMapNode, namespace);
}
}
private void clearResultMap(XNode xNode, String namespace) {
for (XNode resultChild : xNode.getChildren()) {
if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
|| "case".equals(resultChild.getName())) {
if (resultChild.getStringAttribute("select") == null) {
configuration.getResultMapNames().remove(
resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
configuration.getResultMapNames().remove(
namespace + "." + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
clearResultMap(resultChild, namespace);
}
}
}
}
}
private void cleanKeyGenerators(List<XNode> list, String namespace) {
for (XNode context : list) {
String id = context.getStringAttribute("id");
configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
String k = namespace + "." + id;
List<Object> objects = Stream.of(mappedStatements.toArray()).filter(p -> p instanceof MappedStatement && Objects.equals(((MappedStatement) p).getId(), k)).collect(Collectors.toList());
mappedStatements.removeAll(objects);
}
}
private void cleanSqlElement(List<XNode> list, String namespace) {
for (XNode context : list) {
String id = context.getStringAttribute("id");
configuration.getSqlFragments().remove(id);
configuration.getSqlFragments().remove(namespace + "." + id);
}
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
private static class Struct {
private String buildFilePath;
private String name;
private File src;
private String md5;
private long last_time;
public long newLastTime() {
return new File(src.getAbsolutePath()).lastModified();
}
}
}
其中的md5类
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
public static String encode(String str) {
return encode(str.getBytes(StandardCharsets.UTF_8));
}
public static String encode(byte[] bytes) {
try {
return toHash(MessageDigest.getInstance("MD5").digest(bytes));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String toHash(byte[] bytes) {
StringBuffer hex = new StringBuffer();
for (byte b : bytes) {
int val = ((int) b) & 0xff;
if (val < 16) {
hex.append("0");
}
hex.append(Integer.toHexString(val));
}
return hex.toString();
}
public static String encode(File file) throws IOException {
return encode(FileUtils.readFileToByteArray(file));
}
public static String encode(String salt, String str) {
try {
return encode(salt.getBytes("UTF-8"), str.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String encode(byte[] salt, byte[] str) {
try {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(salt);
m.update(str);
return toHash(m.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
本文地址:https://blog.csdn.net/qq_23668595/article/details/107353737
下一篇: 大话IO
推荐阅读
-
mybatis plus更新字段为null处理方法
-
mybatis plus更新字段为null处理方法
-
Mybatis plus 3.1 xml 热更新
-
详解mybatis-plus的 mapper.xml 路径配置的坑
-
Mybatis-Plus自动填充更新操作相关字段的实现
-
Mybatis Plus 字段为空值时执行更新方法未更新解决方案
-
mybatis-plus 通用字段自动化(如逻辑删除和更新时间等)
-
mybatis-plus 执行insert(),实体的id自动更新问题
-
SpringBoot整合MyBatis-Plus3.1教程详解
-
MP(MyBatis-Plus)实现乐观锁更新功能的示例代码