由于项目已经发布到线上,要是修改一个Mapper.xml文件的话,需要重启整个服务,这个是很耗时间的,而且在一段时间内导致服务不可用,严重影响用户
的体验度。所以希望可以有一个机制可以,当修改某个mapper.xml的时候,只要重新加载这个mapper.xml就好了,参考网上的一些资料和demo,加上一些
自己的总结,下面的代码是通过测试的,可以供你们参考和使用。
- import java.io.IOException;
- import java.lang.reflect.Field;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.ibatis.builder.xml.XMLMapperBuilder;
- import org.apache.ibatis.session.Configuration;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.springframework.core.io.Resource;
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
- public class RefreshMapperCache {
- private Log log = LogFactory.getLog(RefreshMapperCache.class);
- private SqlSessionFactory sqlSessionFactory;
- private Resource[] mapperLocations;
- private String packageSearchPath;
- private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化
- //记录发生改变的xml文件名称
- private List<String> changeResourceNameList = new ArrayList<>();
- public void refreshMapper() {
- try {
- Configuration configuration = this.sqlSessionFactory.getConfiguration();
- // step.1 扫描文件
- try {
- this.scanMapperXml();
- } catch (IOException e) {
- log.error("packageSearchPath扫描包路径配置错误");
- return;
- }
- // System.out.println("==============刷新前mapper中的内容 start===============");
- // //获取xml中的每个语句的名称即 id = "findUserById";
- // for (String name : configuration.getMappedStatementNames()) {
- // System.out.println(name);
- // }
- // System.out.println("==============刷新前mapper中的内容 end===============");
- //清空被修改过后的文件名称,确保该集合是空的
- changeResourceNameList.clear();
- // step.2 判断是否有文件发生了变化
- if (this.isChanged()) {
- // step.2.1 清理
- this.removeConfig(configuration);
- // step.2.2 重新加载
- for (Resource configLocation : mapperLocations) {
- try {
- //匹配被修改过的mapper文件,如果存在,则重新加载
- //如果想要重新加载全部mapper,可以不匹配
- if(changeResourceNameList.contains(configLocation.getFilename())){
- XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
- xmlMapperBuilder.parse();
- System.out.println("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");
- }
- } catch (IOException e) {
- System.out.println("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
- continue;
- }
- }
- //清空被修改过后的文件名称
- changeResourceNameList.clear();
- }
- // System.out.println("--------------------------刷新后mapper中的内容 start--------------------------");
- // for (String name : configuration.getMappedStatementNames()) {
- // System.out.println(name);
- // }
- // System.out.println("--------------------------刷新后mapper中的内容 end--------------------------");
- } catch (Exception e) {
- System.out.println("****************刷新缓存异常: "+e.getMessage());
- }
- }
- public void setPackageSearchPath(String packageSearchPath) {
- this.packageSearchPath = packageSearchPath;
- }
- public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
- this.sqlSessionFactory = sqlSessionFactory;
- }
- /**
- * 扫描xml文件所在的路径
- * @throws IOException
- */
- private void scanMapperXml() throws IOException {
- this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
- }
- /**
- * 清空Configuration中几个重要的缓存
- * @param configuration
- * @throws Exception
- */
- private void removeConfig(Configuration configuration) throws Exception {
- Class<?> classConfig = configuration.getClass();
- clearMap(classConfig, configuration, "mappedStatements");
- clearMap(classConfig, configuration, "caches");
- clearMap(classConfig, configuration, "resultMaps");
- clearMap(classConfig, configuration, "parameterMaps");
- clearMap(classConfig, configuration, "keyGenerators");
- clearMap(classConfig, configuration, "sqlFragments");
- clearSet(classConfig, configuration, "loadedResources");
- }
- @SuppressWarnings("rawtypes")
- private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
- Field field = classConfig.getDeclaredField(fieldName);
- field.setAccessible(true);
- Map mapConfig = (Map) field.get(configuration);
- mapConfig.clear();
- }
- @SuppressWarnings("rawtypes")
- private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
- Field field = classConfig.getDeclaredField(fieldName);
- field.setAccessible(true);
- Set setConfig = (Set) field.get(configuration);
- setConfig.clear();
- }
- /**
- * 判断文件是否发生了变化
- * @param resource
- * @return
- * @throws IOException
- */
- private boolean isChanged() throws IOException {
- boolean flag = false;
- System.out.println("***************************获取文件名 开始************************************");
- for (Resource resource : mapperLocations) {
- String resourceName = resource.getFilename();
- System.out.println("resourceName == " + resourceName+", path = "+resource.getURL().getPath());
- boolean addFlag = !fileMapping.containsKey(resourceName);// 此为新增标识
- // 修改文件:判断文件内容是否有变化
- Long compareFrame = fileMapping.get(resourceName);
- long lastFrame = resource.contentLength() + resource.lastModified();
- boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识
- if(addFlag){
- System.out.println(" 新增了:==="+ resourceName);
- }
- if(modifyFlag){
- System.out.println(" 修改了:==="+ resourceName);
- }
- // 新增或是修改时,存储文件
- if(addFlag || modifyFlag) {
- fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
- flag = true;
- changeResourceNameList.add(resourceName);
- }
- }
- System.out.println("***************************获取文件名 结束************************************");
- return flag;
- }
- }
写一个实体类,然后在spring中配置改实体类的bean即可
- <bean id="refreshMapperCache" class="com.company.project.util.RefreshMapperCache" >
- <!-- 扫描的映射mapper.xml的文件路径
- 这个地方要注意mapper的文件,多数据源情况下,只能扫描自己数据源下的mapper,否则会报异常 -->
- <property name="packageSearchPath" value="classpath*:mapper/trade/**/*.xml"></property>
- <!-- 配置自己的数据源 -->
- <property name="sqlSessionFactory" ref="sqlSessionFactoryPay"></property>
- </bean>
由于我们公司使用的是多数据源,所以在配置bean的时候,要给每个数据源配置一个bean,注意点就是在配置bean的时候
1. 如果是多数据源的情况 , 扫描mapper.xml文件的时候,只能扫该数据源下的mapper.xml文件
2. 多数据源情况想,设置sqlSessionFactory 的时候,要设置为对应的数据源
3. 如果是但数据源的情况,那么就简单了,只需要配置当前数据源及对应的mapper.xml文件即可