精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-07-11
最后修改:2011-07-14
在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没有提供这个功能,也或许我不知道怎么使用这个功能(如果有人知道麻烦赐教) <mapper namespace="A"> <cache flushInterval="86400000" eviction="LRU" readOnly="true" /> .... <select id="getInfo" useCache=true> select A.name, B.detail from A left join B.id = A.id </select> ... </mapper> 为了解决这个问题,我使用了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.a.b.interceptor.FlushCacheInterceptor"> <property name="ClientGroup.getClientGroupByClientId" value="Client"/> </plugin> </plugins> 实现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; } } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-07-11
LZ还可以考虑另一种思路,需要客户端指定Entry的主键Key,每次update操作,清空有包含该key的相关cache数据即可。
|
|
返回顶楼 | |
发表时间:2011-07-11
agapple 写道 LZ还可以考虑另一种思路,需要客户端指定Entry的主键Key,每次update操作,清空有包含该key的相关cache数据即可。 目前似乎是在服务端拦截select 类型的query 或取参数来得到key,不知道你说的客户端如何指定entry的主键key. |
|
返回顶楼 | |
发表时间:2011-07-11
强制要求客户端每个Entry必须设定@PrimityKey,扫描对应的annotation。如果不指定这可以采用简单的clean all清空缓存
|
|
返回顶楼 | |
发表时间:2011-07-13
思路不错,攒个, ,但是你这个拦截器里,很多变量都是线程不安全噢,都没同步,呵呵。
|
|
返回顶楼 | |
发表时间:2011-07-13
bao231 写道 思路不错,攒个, ,但是你这个拦截器里,很多变量都是线程不安全噢,都没同步,呵呵。 嗯,是的,线程安全要考虑。 |
|
返回顶楼 | |
发表时间:2011-07-13
赞~~终于看到有研究Mybatis缓存的贴子了!
楼主的问题,几乎是每个用mybatis都会遇到的问题.在此看到用plugins来解决问题,眼前一亮,不错! 但是官方好象不建议用plugin 我的方法比较苯,更新B表的时候Cache cache = mappedStatement.getConfiguration().getCache("A") 锁A的cache,清空指定select cache. 不过遇到a join b join c......的时候比较崩溃 - -! |
|
返回顶楼 | |
发表时间:2011-07-14
公司DBA不建议连表查询,我们一般是拆分成两条sql来做的,可以避开ibatis的cache刷新问题。
|
|
返回顶楼 | |
发表时间:2011-07-14
kfliyangfan 写道 公司DBA不建议连表查询,我们一般是拆分成两条sql来做的,可以避开ibatis的cache刷新问题。 那你们是怎么 比较好的解决N+1问题的呢? |
|
返回顶楼 | |
发表时间:2011-07-14
我想mybatis后续的版本应该会考虑这个问题吧,直接SQL语句本来就是MYBATIS的卖点,而JOIN语句又是SQL比较常用的一个功能,google不应该不考虑这个问题。
|
|
返回顶楼 | |