什么 JRebel 热部署失效?多数据源分页失效?
liebian365 2024-11-14 18:05 25 浏览 0 评论
- 上一次有讲到配置多数据源,但就在此时我的JRebel热部署失效了,但也不是完全失效,更改代码可以实现热部署,可是编写xml改变sql 就不会生效,不能热部署就要重启项目,由于项目太大启动就要2分钟,这效率可太慢了!
怎么办?,那就自己写个xml 等资源文件的热部署吧.
编写代码
package com.ym.web.config.mybatis;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* mybatis热部署插件
* 设置mybatis配置文件cache-enabled: false不缓存,开启热部署xml
*
* @author:
* @version: 2021年03月28日 14:10
*/
@Component
public class MapperHotDeployPlugin implements InitializingBean, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(MapperHotDeployPlugin.class);
@Autowired
private MybatisProperties mybatisProperties;
private Configuration configuration;
@Override
public void afterPropertiesSet() {
// 设置mybatis配置文件cache-enabled: false不缓存,开启热部署xml
if (!mybatisProperties.getConfiguration().isCacheEnabled()) {
new WatchThread().start();
logger.info("热部署开启{}",mybatisProperties);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("ds1SqlSessionFactory");
configuration = sqlSessionFactory.getConfiguration();
logger.info("setApplicationContext{},{}",sqlSessionFactory,configuration);
System.out.println();
}
class WatchThread extends Thread {
private final Logger logger = LoggerFactory.getLogger(WatchThread.class);
@Override
public void run() {
startWatch();
}
/**
* 启动监听
*/
private void startWatch() {
try {
WatchService watcher = FileSystems.getDefault().newWatchService();
getWatchPaths().forEach(p -> {
try {
Paths.get(p).register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
} catch (Exception e) {
logger.error("ERROR: 注册xml监听事件", e);
throw new RuntimeException("ERROR: 注册xml监听事件", e);
}
});
while (true) {
WatchKey watchKey = watcher.take();
Set<String> set = new HashSet<>();
for (WatchEvent<?> event : watchKey.pollEvents()) {
set.add(event.context().toString());
}
// 重新加载xml
reloadXml(set);
boolean valid = watchKey.reset();
if (!valid) {
break;
}
}
} catch (Exception e) {
System.out.println("Mybatis的xml监控失败!");
logger.info("Mybatis的xml监控失败!", e);
}
}
/**
* 加载需要监控的文件父路径
*/
private Set<String> getWatchPaths() {
Set<String> set = new HashSet<>();
Arrays.stream(getResource()).forEach(r -> {
try {
logger.info("资源路径:{}", r.toString());
set.add(r.getFile().getParentFile().getAbsolutePath());
} catch (Exception e) {
logger.info("获取资源路径失败", e);
throw new RuntimeException("获取资源路径失败");
}
});
logger.info("需要监听的xml资源: {}", set);
return set;
}
/**
* 获取配置的mapperLocations
*/
private Resource[] getResource() {
return mybatisProperties.resolveMapperLocations();
}
/**
* 删除xml元素的节点缓存
* "mappedStatements",
*/
private void clearMap(String nameSpace) {
logger.info("清理Mybatis的namespace={}在mappedStatements、caches、resultMaps、parameterMaps、keyGenerators、sqlFragments中的缓存");
Arrays.asList("mappedStatements" ,"caches", "resultMaps", "parameterMaps", "keyGenerators", "sqlFragments").forEach(fieldName -> {
Object value = getFieldValue(configuration, fieldName);
if (value instanceof Map) {
Map<?, ?> map = (Map) value;
List<Object> list = map.keySet().stream().filter(o -> o.toString().startsWith(nameSpace + ".")).collect(Collectors.toList());
logger.info("需要清理的元素: {}", list);
list.forEach(k -> map.remove((Object) k));
}
});
}
/**
* 清除文件记录缓存
*/
private void clearSet(String resource) {
logger.info("清理mybatis的资源{}在容器中的缓存", resource);
Object value = getFieldValue(configuration, "loadedResources");
if (value instanceof Set) {
Set<?> set = (Set) value;
set.remove(resource);
set.remove("namespace:" + resource);
}
}
/**
* 获取对象指定属性
*
* @param obj 对象信息
* @param fieldName 属性名称
*/
private Object getFieldValue(Object obj, String fieldName) {
logger.info("从{}中加载{}属性", obj, fieldName);
try {
Field field = obj.getClass().getDeclaredField(fieldName);
boolean accessible = field.isAccessible();
field.setAccessible(true);
Object value = field.get(obj);
field.setAccessible(accessible);
return value;
} catch (Exception e) {
logger.info("ERROR: 加载对象中[{}]", fieldName, e);
throw new RuntimeException("ERROR: 加载对象中[" + fieldName + "]", e);
}
}
/**
* 重新加载set中xml
*
* @param set 修改的xml资源
*/
private void reloadXml(Set<String> set) {
logger.info("需要重新加载的文件列表: {}", set);
List<Resource> list = Arrays.stream(getResource())
.filter(p -> set.contains(p.getFilename()))
.collect(Collectors.toList());
logger.info("需要处理的资源路径:{}", list);
list.forEach(r -> {
try {
clearMap(getNamespace(r));
clearSet(r.toString());
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(r.getInputStream(), configuration,
r.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
logger.info("ERROR: 重新加载[{}]失败", r.toString(), e);
throw new RuntimeException("ERROR: 重新加载[" + r.toString() + "]失败", e);
} finally {
ErrorContext.instance().reset();
}
});
logger.info("成功热部署文件列表: {}", set);
}
/**
* 获取xml的namespace
*
* @param resource xml资源
*/
private String getNamespace(Resource resource) {
logger.info("从{}获取namespace", resource.toString());
try {
XPathParser parser = new XPathParser(resource.getInputStream(), true, null, new XMLMapperEntityResolver());
return parser.evalNode("/mapper").getStringAttribute("namespace");
} catch (Exception e) {
logger.info("ERROR: 解析xml中namespace失败", e);
throw new RuntimeException("ERROR: 解析xml中namespace失败", e);
}
}
}
}
实话说 网上百度也有源代码 但是对于我配置的多数据源来说 些许不同
问题点1: 我的多数据源原来的是MybatisSqlSessionFactory 把这里改成 SqlSessionFactory 后问题就来了
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("ds1SqlSessionFactory");
configuration = sqlSessionFactory.getConfiguration();
logger.info("setApplicationContext{},{}",sqlSessionFactory,configuration);
System.out.println();
}
报错
java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
原因: SqlSessionFactory 自动注入了PageHelper 也就是分页插件
分析源码:
/**
* 自定注入分页插件
*
* @author liuzh
*/
@Configuration
@ConditionalOnBean(SqlSessionFactory.class)
@EnableConfigurationProperties(PageHelperProperties.class)
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class PageHelperAutoConfiguration {
//这里是所有的数据源 并都注入分页插件 问题点就在这
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Autowired
private PageHelperProperties properties;
/**
* 接受分页插件额外的属性
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
public Properties pageHelperProperties() {
return new Properties();
}
@PostConstruct
public void addPageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
//先把一般方式配置的属性放进去
properties.putAll(pageHelperProperties());
//在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
properties.putAll(this.properties.getProperties());
interceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
}
@Configuration 配置类
@ConditionalOnBean(SqlSessionFactory.class) 装配条件该类装配完成
@EnableConfigurationProperties(PageHelperProperties.class) 自动装配
@AutoConfigureAfter(MybatisAutoConfiguration.class) 最后装配
配置类无疑
//获取了所有sqlSessionFactoryList,多数据源就注入了两次
private List sqlSessionFactoryList;
问题找到了 ,既然多次注入 那就不要让他注入我们手动注入
1. 在SpringApplication 入口取消分页自动注入
@EnableAutoConfiguration(exclude= PageHelperAutoConfiguration.class)
2.编写手动注入分页bean交给spring
package com.ym.web.config.datasource;
import com.github.pagehelper.PageInterceptor;
import com.github.pagehelper.autoconfigure.PageHelperProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Properties;
/**
* @Author wenbo
* @Date 2021/3/25 19:05
**/
@Component
public class MybatisPageInterceptor {
/**
* 接受分页插件额外的属性
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
public Properties pageHelperProperties() {
return new Properties();
}
/**
* 配置插件
*
* @return bean
*/
@Bean(name = "pagePlugin")
public PageInterceptor pageInterceptor() {
//org.apache.ibatis.plugin.Interceptor
PageInterceptor interceptor = new PageInterceptor();
//java.util.Properties
Properties properties = new Properties();
properties.putAll(pageHelperProperties());
//properties.putAll(this.properties.getProperties());
// 是否返回行数,相当于MySQL的count(*)
//properties.setProperty("rowBoundsWithCount", "true");
interceptor.setProperties(properties);
return interceptor;
}
}
3.在每个数据源中手动加入到 mybatis的拦截器
configuration.addInterceptor(pageInterceptor.pageInterceptor());
数据源1
@Autowired
private MybatisPageInterceptor pageInterceptor;
/**
* 主数据源 ds1数据源
* @return
* @throws Exception
*/
@Primary
@Bean("ds1SqlSessionFactory")
@DependsOn("pagePlugin")
public SqlSessionFactory ds1SqlSessionFactory(@Qualifier("ds1SqlSessionFactoryBean") SqlSessionFactoryBean sqlSessionFactoryBean) throws Exception {
//sqlSessionFactoryBean.setPlugins(plugins ());
SqlSessionFactory sqlSessionFactor = sqlSessionFactoryBean.getObject();
org.apache.ibatis.session.Configuration configuration = sqlSessionFactor.getConfiguration();
configuration.addInterceptor(pageInterceptor.pageInterceptor());
configuration.setMapUnderscoreToCamelCase(true);
return sqlSessionFactoryBean.getObject();
}
4.数据源2相同操作
然后测试分页两个数据源都生效了 热部署也生效了
重点: 如果使用 MybatisSqlSessionFactory 就不会有多个分页这种错误 这无疑是一种快捷的方式!!
喜欢我微信扫码关注[看]
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)