mybatis熱部署載入*Mapper.xml檔案,手動重新整理*Mapper.xml檔案
阿新 • • 發佈:2019-01-25
由於專案已經發布到線上,要是修改一個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檔案即可