由於項目已經發布到線上,要是修改一個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文件即可