Mybatis-PageHelper Reentrantlock锁使用问题
Mybatis-PageHelper应该是目前使用比较广泛的一个Mybatis分页插件。我在几个项目里都引入了该插件。今天偶然阅读其源码,却发现了一个不小的问题。
注:我阅读的是最新的4.1.3的源码。
该分页插件的核心是PageHelper类,该类实现了Mybatis提供的Interceptor接口。接口定义的intercept方法实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}
其中,getSqlUtil方法,实现如下:
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
/**
* 根据daatsource创建对应的sqlUtil
*
* @param invocation
*/
public SqlUtil getSqlUtil(Invocation invocation) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
//改为对dataSource做缓存
DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
String url = getUrl(dataSource);
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
ReentrantLock lock = new ReentrantLock(); //这里我认为是有问题的。
try {
lock.lock();
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
if (StringUtil.isEmpty(url)) {
throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");
}
String dialect = Dialect.fromJdbcUrl(url);
if (dialect == null) {
throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");
}
SqlUtil sqlUtil = new SqlUtil(dialect);
if (this.properties != null) {
sqlUtil.setProperties(properties);
} else if (this.sqlUtilConfig != null) {
sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);
}
urlSqlUtilMap.put(url, sqlUtil);
return sqlUtil;
} finally {
lock.unlock();
}
}
这里有个加锁的操作,使用的是Reentrantlock局部变量。这里是有问题的,局部变量的lock是无法起到锁的作用的。这段代码实际上仍不是线程安全的。将该lock变量提升为类的全局变量即可解决。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private ReentrantLock lock = new ReentrantLock();
/**
* 根据datasource创建对应的sqlUtil
*
* @param invocation
*/
public SqlUtil getSqlUtil(Invocation invocation) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
//改为对dataSource做缓存
DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
String url = getUrl(dataSource);
if (urlSqlUtilMap.containsKey(url)) {
return urlSqlUtilMap.get(url);
}
try {
lock.lock();
...
顺便也修改了一个datasource拼写错误的问题。
给作者提交了Pull Request。
本文由作者按照 CC BY 4.0 进行授权