发布时间:2026-07-21阅读(1)
怎么办?,那就自己写个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 */@Componentpublic class MapperHotDeployPlugin implements InitializingBean, ApplicationContextAware {private final static Logger logger = LoggerFactory.getLogger(MapperHotDeployPlugin.class);@Autowiredprivate MybatisProperties mybatisProperties;private Configuration configuration;@Overridepublic void afterPropertiesSet() {// 设置mybatis配置文件cache-enabled: false不缓存,开启热部署xmlif (!mybatisProperties.getConfiguration().isCacheEnabled()) {new WatchThread().start();logger.info("热部署开启{}",mybatisProperties);}}@Overridepublic 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);@Overridepublic 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());}// 重新加载xmlreloadXml(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 后问题就来了
@Overridepublic 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)
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 **/@Componentpublic 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; }}
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(); }
然后测试分页两个数据源都生效了 热部署也生效了
重点: 如果使用 MybatisSqlSessionFactory 就不会有多个分页这种错误 这无疑是一种快捷的方式!!

喜欢我微信扫码关注[看]
Copyright © 2024 有趣生活 All Rights Reserve吉ICP备19000289号-5 TXT地图HTML地图XML地图