mybatis plus3.1.0 热加载mapper

最近项目里面开始使用mybatis plus3.1.0集成springBoot2.0.4进行开发,之前用mybatis plus2的时候是可以配置xml热加载的。但是到了3的时候已经不能用了,为了提高开发效率,不用每次修改xml文件后都重启,各种找资料并整理。终于可以实现热加载,下面是具体的代码实现。

1.在application.yml里面加入配置:

mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml
  refresh-mapper: true

2.新建MybatisPlusConfig配置类,代码如下:

@Configuration
//@MapperScan("com.cloud.*.dao.*")
@AutoConfigureAfter(SqlSessionFactory.class)
public class MybatisPlusConfig {

//	@Autowired
//	private SqlSessionFactory sqlSessionFactory;


	@Value("${mybatis-plus.mapper-locations}")
	private String mapperLocations;

	@Value("${mybatis-plus.refresh-mapper}")
	private Boolean refreshMapper;

	private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
	/*
	    * 分页插件,自动识别数据库类型
	    * 多租户,请参考官网【插件扩展】
	    */
	   @Bean
	   public PaginationInterceptor paginationInterceptor() {
	      return new PaginationInterceptor();
	   }


	   /* * oracle数据库配置JdbcTypeForNull
	    * 参考:https://gitee.com/baomidou/mybatisplus-boot-starter/issues/IHS8X
	    不需要这样配置了,参考 yml:
	    mybatis-plus:
	      confuguration
	        dbc-type-for-null: 'null' */
	   @Bean
	   public ConfigurationCustomizer configurationCustomizer(){
	       return new MybatisPlusCustomizers();
	   }

	   class MybatisPlusCustomizers implements ConfigurationCustomizer {

	       @Override
	       public void customize(org.apache.ibatis.session.Configuration configuration) {
	           configuration.setJdbcTypeForNull(JdbcType.NULL);
	       }
	   }

	/**
	 * mapper.xml 热加载
	 * @return
	 */
	@Bean
	public MybatisMapperRefresh mybatisMapperRefresh(){
		SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) SpringContextHolder.getBean("sqlSessionFactory");
		Resource[] resources = new Resource[0];
		try {
			resources = resourceResolver.getResources(mapperLocations);
		} catch (IOException e) {
			e.printStackTrace();
		}
		MybatisMapperRefresh mybatisMapperRefresh = new MybatisMapperRefresh(resources,sqlSessionFactory,10,5,refreshMapper);
		return mybatisMapperRefresh;

	}

	/**
	 * 逻辑删除
	 * @return
	 */
	@Bean
	public ISqlInjector sqlInjector() {
		return new LogicSqlInjector();
	}




//	    @Bean
//	    @ConfigurationProperties("spring.datasource.druid" )
//	    public DataSource dataSource() {
//	        return DruidDataSourceBuilder
//	                .create()
//	                .build();
//	    }



}

4.新建MybatisMapperRefresh类,代码如下:

public class MybatisMapperRefresh implements Runnable {
    private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);
    /**
     * 记录jar包存在的mapper
     */
    private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
    private SqlSessionFactory sqlSessionFactory;
    private Resource[] mapperLocations;
    private Long beforeTime = 0L;
    private Configuration configuration;
    /**
     * 是否开启刷新mapper
     */
    private boolean enabled;
    /**
     * xml文件目录
     */
    private Set<String> fileSet;
    /**
     * 延迟加载时间
     */
    private int delaySeconds = 10;
    /**
     * 刷新间隔时间
     */
    private int sleepSeconds = 20;

    public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, int delaySeconds,
                                int sleepSeconds, boolean enabled) {
        this.mapperLocations = mapperLocations.clone();
        this.sqlSessionFactory = sqlSessionFactory;
        this.delaySeconds = delaySeconds;
        this.enabled = enabled;
        this.sleepSeconds = sleepSeconds;
        this.configuration = sqlSessionFactory.getConfiguration();
        this.run();
    }

    public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, boolean enabled) {
        this.mapperLocations = mapperLocations.clone();
        this.sqlSessionFactory = sqlSessionFactory;
        this.enabled = enabled;
        this.configuration = sqlSessionFactory.getConfiguration();
        this.run();
    }

    @Override
    public void run() {
        final GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
        /*
         * 启动 XML 热加载
         */
        if (enabled) {
            beforeTime = SystemClock.now();
            final MybatisMapperRefresh runnable = this;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    if (fileSet == null) {
                        fileSet = new HashSet<>();
                        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();
                                }
                            }
                        }
                    }
                    try {
                        Thread.sleep(delaySeconds * 1000);
                    } catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                    do {
                        try {
                            for (String filePath : fileSet) {
                                File file = new File(filePath);
                                if (file.isFile() && file.lastModified() > beforeTime) {
                                    List<Resource> removeList = jarMapper.get(filePath);
                                    if (removeList != null && !removeList.isEmpty()) {
                                        for (Resource resource : removeList) {
                                            runnable.refresh(resource);
                                        }
                                    } else {
                                        runnable.refresh(new FileSystemResource(file));
                                    }
                                }
                            }
                        } catch (Exception exception) {
                            exception.printStackTrace();
                        }
                        try {
                            Thread.sleep(sleepSeconds * 1000);
                        } catch (InterruptedException interruptedException) {
                            interruptedException.printStackTrace();
                        }

                    } while (true);
                }
            }, "mybatis-plus MapperRefresh").start();
        }
    }

    /**
     * 刷新mapper
     *
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    private void refresh(Resource resource) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        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();
            logger.debug("refresh: '" + resource + "', success!");
        } catch (IOException e) {
            logger.error("Refresh IOException :" + e.getMessage());
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 清理parameterMap
     *
     * @param list
     * @param namespace
     */
    private void cleanParameterMap(List<XNode> list, String namespace) {
        for (XNode parameterMapNode : list) {
            String id = parameterMapNode.getStringAttribute("id");
            configuration.getParameterMaps().remove(namespace + "." + id);
        }
    }

    /**
     * 清理resultMap
     *
     * @param list
     * @param namespace
     */
    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);
                    }
                }
            }
        }
    }

    /**
     * 清理selectKey
     *
     * @param list
     * @param 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();
            List<MappedStatement> objects = Lists.newArrayList();
            for (MappedStatement mappedStatement : mappedStatements) {
                if (mappedStatement.getId().equals(namespace + "." + id)) {
                    objects.add(mappedStatement);
                }
            }
            mappedStatements.removeAll(objects);
        }
    }

    /**
     * 清理sql节点缓存
     *
     * @param list
     * @param namespace
     */
    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);
        }
    }
}

5.修改mapper文件后,idea中Ctrl+F9手动编译,结果如下:

至此,热加载就已经配置完成。已经测试过,没有问题。上线的时候只需要配置yml里面refresh-mapper: false。

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值