疑难杂症-MyBatis一级缓存引起的分页插件失效

本文介绍了在使用自定义MyBatis分页插件时遇到的问题,由于一级缓存机制,导致不同分页参数查询结果相同。分析了问题原因并提出了解决方案,包括拦截Executor以在生成CacheKey前拼装SQL或在执行时禁用缓存。同时,解释了MyBatis二级缓存的工作原理。
摘要由CSDN通过智能技术生成

症状:使用自定义MyBatis分页插件,只有分页参数不同的方法在短时间内使用不同分页参数查询出来的结果相同。
病因:自定义MyBatis插件拦截目标为StatementHandler,而在同一个SqlSession中,在StatementHandler.prepare之前,MyBatis的已经命中了一级缓存,所以直接返回了缓存中的内容。
治疗方案:重写自定义MyBatis分页插件使之拦截Executor,或增加新的插件,使之拦截Executor清除一级缓存。

这是我最近在一个项目中排查的一个问题,在这里记录一下以备后查。
首先这个项目并没有使用比较流行的PageHelper插件,而是自己实现了一个,由于不是本人的代码,就不贴出来了。网上搜一下的话也有很多类似的,主要的实现原理是使用MyBatis提供的@Intercepts拦截StatementHandler类的prepare方法,通过反射获取到MappedStatement和BoundSql。如果执行的是约定的分页方法(MappedStatement的id带有Page后缀),那么就把BoundSql中的sql字段更改为带有分页功能的sql。如果要使用分页查询,那么分页方法的参数需要带有分页参数,同时分页方法名需要带有约定的Page后缀。乍一看没有什么问题,但是在真正使用的时候,由于MyBatis一级缓存的存在,同一个SqlSession中后续的分页方法生成了相同的CacheKey,导致直接返回了缓存中的内容。这里主要的问题是拦截的时机,拦截发生在MyBatis决定是否要使用缓存之后!!
关于MyBatis的缓存机制,网上有很多资料讲的很详细,不清楚的可以先了解了解。总的来说,在同一个SqlSession中,执行同一条sql MyBatis会直接返回缓存。不过对于我们碰到的问题,一开始我是带着疑虑的,两次执行的方法明明分页参数是不同的,怎么会命中同一个缓存呢?带着这个疑问我们debug MyBatis的源码,查看其生成CacheKey的逻辑。首先当执行一条sql的时候,会进到4参数CachingExecutor.query,(网上一查,这个类是用来处理二级缓存的,明明没用二级缓存,为何会用到这个类?这里先留个悬念,后面再说):

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

再看这里缓存key的生成方法,实际上是调用了delegate的createCacheKey方法。(那这个delegate又是个啥?我们待会一起说):

@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

查看createCacheKey方法的实现类,就只有CachingExecutor和BaseExecutor,所以这里是使用了BaseExecutor.createCacheKey生成缓存key:

@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值