Mybatis Mapper配置XML文件热加载实现

日常开发中数据持久层, 我大多选用mybatis, 轻量化和可控性高, sql掌握在自己手里, 感觉踏实一些.
但是从ibatis到mybatis以来, 一直有个十分困扰我的问题, 就是mapperXML文件的热加载问题, 相信这也同样困扰着不少同行们.

这几天实在忍无可忍, 于是在网上搜索了一番, 找了找思路, 结合网上的几个例子改了一个工具类出来, 可解决热加载问题.
实际测试有效, 终于可以摆脱改下SQL必须重启的命运了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class XMLMapperReloader implements ApplicationContextAware {

private ConfigurableApplicationContext context = null;
private HashMap<String, Long> fileMapping = new HashMap<String, Long>();
private Scanner scanner = null;
private String packageSearchPath;
private ScheduledExecutorService service = null;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = (ConfigurableApplicationContext) applicationContext;

}

public XMLMapperReloader(){
service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Task(), 5, 5, TimeUnit.SECONDS);
}

class Task implements Runnable {
@Override
public void run() {
try {
scanner = new Scanner();
scanner.refreshMapper();
} catch (Exception e) {
e.printStackTrace();
}
}

}

@SuppressWarnings({ "rawtypes" })
class Scanner {
private Resource[] mapperLocations;

public void refreshMapper() {
try {
SqlSessionFactory factory = context.getBean(SqlSessionFactory.class);
Configuration configuration = factory.getConfiguration();

// step.1 扫描文件
try {
this.scanMapperXml();
} catch (IOException e) {
return;
}

// step.2 判断是否有文件发生了变化
if (this.isChanged()) {
System.out.println("==============检测到mapper有改变, 开始刷新...===============");
// step.2.1 清理
this.removeConfig(configuration);

// step.2.2 重新加载
for (Resource configLocation : mapperLocations) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (IOException e) {
continue;
}
}
System.out.println("==============mapper刷新完毕===============");
}


} catch (Exception e) {
e.printStackTrace();
}
}

private void scanMapperXml() throws IOException {
this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
}

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");

}

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();
}

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();
}

private boolean isChanged() throws IOException {
boolean flag = false;
for (Resource resource : mapperLocations) {
String resourceName = resource.getFilename();

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 || modifyFlag) {
fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
flag = true;
}
}
return flag;
}
}

public String getPackageSearchPath() {
return packageSearchPath;
}

public void setPackageSearchPath(String packageSearchPath) {
this.packageSearchPath = packageSearchPath;
}
}

这个工具类加入项目后, 还需要配置一个bean.

1
2
3
<bean id="XMLMapperReloader" class="com.xx.xx.dao.XMLMapperReloader">
<property name="packageSearchPath" value="classpath:com/xx/xx/dao/*/*.xml"/>
</bean>

注意 packageSearchPath 这个属性的值和mybatissqlSessionFactorymapperLocations配置一样.

1
2
3
4
5
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/xx/xx/dao/*/*.xml" />
</bean>
文章目录
,