ehcache作为Mybatis二级缓存的问题

来源:互联网 发布:易约软件 编辑:程序博客网 时间:2024/05/29 19:24

在项目中曾经出现mybatis-ehcache做二级缓存导致数据脏读的问题。在读场景居多的情况下,这种方式可以提高了缓存命中率。但是在实际应用场景下,开发规范不可控,坑可能更多些。。。

<!-- ecache -->        <dependency>            <groupId>com.googlecode.ehcache-spring-annotations</groupId>            <artifactId>ehcache-spring-annotations</artifactId>            <version>1.1.2</version>            <type>jar</type>            <scope>compile</scope>        </dependency>        <dependency>            <groupId>net.sf.ehcache</groupId>            <artifactId>ehcache-core</artifactId>            <version>2.1.0</version>        </dependency>        <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->        <dependency>            <groupId>org.mybatis.caches</groupId>            <artifactId>mybatis-ehcache</artifactId>            <version>1.0.3</version>        </dependency>        <!-- ecache -->

一般会配置如下的pom,然后配置好ehcache与Mapper文件就可以了。这里边会引用mybatis-ehcache。

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

Mybatis本身只支持一级缓存,提供二级缓存的接入能力。当有更新产生时,会清空一二级缓存,借个网上的图说明下缓存架构
这里写图片描述

这里有个关键点二级缓存是基于mapper映射文件的namespace。可以查看org.mybatis.caches.ehcache.EhcacheCache源码

public final class EhcacheCache implements Cache {    /**     * The cache manager reference.     */    private static final CacheManager CACHE_MANAGER = CacheManager.create();    /**     * The cache id (namespace)     */    private final String id;    /**     * The cache instance     */    private final Ehcache cache;    /**     * Dummy lock that actually does not lock.      * As of 3.2.6 MyBatis does not call locking methods.      */    private final ReadWriteLock readWriteLock = new DummyReadWriteLock();

EhcacheCache继承了Mybatis的org.apache.ibatis.cache.Cache接口。其中的cacheId即为Mapper文件的namespace。而mybatis 对cache的管理是通过cacheId来做的。

我们看下Mybatis创建cache,此部分在org.apache.ibatis.builder.MapperBuilderAssistant。

public Cache useNewCache(Class<? extends Cache> typeClass,      Class<? extends Cache> evictionClass,      Long flushInterval,      Integer size,      boolean readWrite,      Properties props) {    typeClass = valueOrDefault(typeClass, PerpetualCache.class);    evictionClass = valueOrDefault(evictionClass, LruCache.class);    Cache cache = new CacheBuilder(currentNamespace)        .implementation(typeClass)        .addDecorator(evictionClass)        .clearInterval(flushInterval)        .size(size)        .readWrite(readWrite)        .properties(props)        .build();    configuration.addCache(cache);    currentCache = cache;    return cache;  }

configuration.addCache(cache)是在org.apache.ibatis.session.Configuration中添加cache的

public void addCache(Cache cache) {    caches.put(cache.getId(), cache);  }

此处的cacheId即为org.mybatis.caches.ehcache.EhcacheCache中的id值。

在org.apache.ibatis.executor.CachingExecutor可以看到Mybatis的清除cache执行方法。

private void flushCacheIfRequired(MappedStatement ms) {    Cache cache = ms.getCache();    if (cache != null && ms.isFlushCacheRequired()) {            tcm.clear(cache);    }  }

cache从属于MappedStatement,每个ms对应一个mapper文件的命名空间。Mybatis是在不同的namesapce下对各自的cache进行操作的。
从而mybatis在ehcache作为缓存情况下出现多表或者关联查询时,将很可能出现脏数据。那位同学就是因为在一个mapper中关联查询,关联表更新,但是对应这个mapper 中的cache还是原来的导致的脏读问题。

总的来看这种二级缓存方式弊要大于利。
1、这种方式基于mapper的namespace区分,要能确保开发人员在一个mapper中不引入其他的表。如果出现关联查询则更难处理。
2、使用场景非常有限,需要读远超过其他三种方式。

综上,建议还是在业务逻辑层加入缓存,而尽量谨慎选择Mybatis的二级缓存方式。

原创粉丝点击