云数据库对应的ecs服务器(什么JRebel热部署失效)

  • 上一次有讲到配置多数据源,但就在此时我的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 就不会有多个分页这种错误 这无疑是一种快捷的方式!!

云数据库对应的ecs服务器(什么JRebel热部署失效)(1)

喜欢我微信扫码关注[看]

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页