ehcache作为Mybatis二级缓存的问题

本文讨论了在项目中使用Mybatis-ehcache作为二级缓存时可能出现的数据脏读问题。尽管在读取场景下能提高缓存命中率,但在实际应用中由于开发规范难以控制,可能导致更多问题。Mybatis的一级缓存和二级缓存机制被介绍,并指出二级缓存基于Mapper的namespace,这在多表或关联查询时容易引发脏数据。作者建议在业务逻辑层加入缓存,谨慎使用Mybatis的二级缓存功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在项目中曾经出现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的二级缓存方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值