百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

什么 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字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: