设为首页 收藏本站
查看: 1493|回复: 0

[经验分享] mybatis 解决join语句缓存刷新研究

[复制链接]

尚未签到

发表于 2016-11-26 08:22:21 | 显示全部楼层 |阅读模式
项目中由于需要从ibatis升级到mybatis,因为实现的改变,缓存功能也相应的改变,由于需要实现一个当前mybatis没提供的缓存功能,花了些时间研究了源码,并且实现了相应的功能,现就一些心得与大家分享,由于实现中使用了mybatis的plugin拦截器,有可能会改变mybatis的核心行为,所以不保证我的方法足够的安全,只是一个解决问题的思路,言归正传:
在mybatis下每个mapper的namespace对应一个cache, 也就是说一个cahce的id就是mapper的namespace, 当mapper中select 标签中使用useCache=true,那么该select语句就会被保存到cache中,某一个namespace下的某一个select语句可能会有不同的参数值,所以mybatis会分别把不同参值的sql查询结果保存到cache中去,也就是说同一个namespace下的同一个select语句会对应N个不同的cache, 当前mybatis的缓存flush机制是得到namespace对应的缓存后直接clear, 这个的话,同一个namespace下的所有select语句所对应的缓存都被刷新,这一点与ibatis一样,只要cacheModel声明过的select语句就会被flush,我们如何能做到细粒度的flush某个select呢? 或者是当namespace A 中的SQL语句要join 另一个namespace B中的表(B表),比如: namespace A中有这样一个SQL,当B表作了update操作的时候,那么SQL语句 getInfo所对应的缓存就需要flush, 但是当前mybatis没有提供这个功能,也或许我不知道怎么使用这个功能(如果有人知道麻烦赐教)
<select id="getInfo" useCache=true>
select A.name, B.detail from A left join B.id = A.id
</select>
为了解决这个问题,我使用了mybatis的plugin功能,首先拦截Executor class的query方法来得到以下三个参数MappedStatement.class, Object.class, RowBounds.class, 这个三个参数是为了计算存放到cahce中的key,然后再由Executor.createCacheKey(mappedStatement, parameter, rowBounds)方法计算出cacheKey, 这样就可以得到每个select语句被缓存到cahce中时所对应的key, 顺带说一下这个cacheKey的计算是由几个要素来计算的,1.select标签的id, 可通过MappedStatement.getId得到 2. RowBounds 的getOffset()和getLimit() 3. select的sql语句  4. 最重要的一点,也是决定这key是否相同的一点, sql的参数,由上面三个参数中的第二个提供, 当然cahceKey的不同也可能会由RowBounds的不同而不同。
得到cahceKey之后把它保存到一个Map<String, Set<CacheKey>>类型的map里,String对应一个sqlid, 比如上面提到的sql语句 getInfo, 不过还要加上namesapace那就是 A.getInfo,  Set<CacheKey> 保存某个SQL所对应的不同查询参数的不同结果。当我们得到想要flush的select 的cachekey之后,就可以拦腰Executor class的update方法(包括insert,update,delete), 至于过程很简单,上源码。
在sqlMapConfig.xml中加上:
  <plugins>
          <plugin interceptor="com.nexaweb.bankcore.interceptor.FlushCacheInterceptor">
               <property name="ClientGroup.getClientGroupByClientId" value="Client"/>
   </plugin>
实现Interceptor接口:

@Intercepts( {
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }) })
public class FlushCacheInterceptor implements Interceptor {
private String property;
private Properties properties;
private Map<String, Set<CacheKey>> keyMap = new HashMap<String, Set<CacheKey>>();
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation
.getArgs()[0];
if (!mappedStatement.getConfiguration().isCacheEnabled())
return invocation.proceed();
String sqlId = mappedStatement.getId();
String nameSpace = sqlId.substring(0, sqlId.indexOf('.'));
Executor exe = (Executor) invocation.getTarget();
String methodName = invocation.getMethod().getName();
if (methodName.equals("query")) {
for (Object key : properties.keySet()) {
if (key.equals(sqlId)) {
Object parameter = invocation.getArgs()[1];
RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
Cache cache = mappedStatement.getConfiguration().getCache(nameSpace);
cache.getReadWriteLock().readLock().lock();
CacheKey cacheKey = exe.createCacheKey(mappedStatement, parameter, rowBounds);
try {
if (cache.getObject(cacheKey) == null) {
if (keyMap.get(sqlId) == null) {
Set<CacheKey> cacheSet = new HashSet<CacheKey>();
cacheSet.add(cacheKey);
keyMap.put(sqlId, cacheSet);
} else {
keyMap.get(sqlId).add(cacheKey);
}
}
} finally {
cache.getReadWriteLock().readLock().unlock();
}
break;
}
}
} else if (methodName.equals("update")) {
for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) {
String cacheSqlId = (String) e.nextElement();
String updateNameSpace = properties.getProperty(cacheSqlId);
if (updateNameSpace.equals(nameSpace)) {
String cacheNamespace = cacheSqlId.substring(0, cacheSqlId.indexOf('.'));
Cache cache = mappedStatement.getConfiguration().getCache(cacheNamespace);
Set<CacheKey> cacheSet = keyMap.get(cacheSqlId);
cache.getReadWriteLock().writeLock().lock();
try {
for (Iterator it = cacheSet.iterator(); it.hasNext();) {
cache.removeObject(it.next());
}
} finally {
cache.getReadWriteLock().writeLock().unlock();
keyMap.remove(cacheSqlId);
}
}
}
}
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-305656-1-1.html 上篇帖子: mybatis generator 的复合查询 下篇帖子: mybatis支持属性使用驼峰的命名
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表