>  기사  >  Java  >  [마이바티스 소스코드 분석] 마이바티스 1차, 2차 캐시

[마이바티스 소스코드 분석] 마이바티스 1차, 2차 캐시

巴扎黑
巴扎黑원래의
2017-06-26 10:00:341319검색

MyBatis Cache

빈번한 데이터베이스 작업은 성능을 많이 소모한다는 것을 알고 있습니다(주로 DB의 경우 데이터가 디스크에 유지되므로 쿼리 작업은 IO를 거쳐야 하기 때문에 IO 작업 속도는 메모리에 비해 작업 속도가 몇 배나 느립니다. 특히 일부 동일한 쿼리 문의 경우 쿼리 결과를 저장할 수 있으며 다음에 동일한 내용을 쿼리할 때 데이터를 메모리에서 직접 얻을 수 있습니다. 특정 시간 쿼리 효율성은 일부 시나리오에서 크게 향상될 수 있습니다.

MyBatis의 캐시는 두 가지 유형으로 나뉩니다.

  1. 첫 번째 수준 캐시는 SqlSession 수준 캐시입니다.

  2. 두 번째 수준 캐시는 Mapper 수준에 있는 캐시입니다. 태그 구성에 따라 여러 개의 매퍼 파일이 하나의 캐시를 공유할 수 있습니다.

MyBatis의 첫 번째 및 두 번째 레벨 캐시를 자세히 살펴보겠습니다.

MyBatis 1차 캐시 워크플로

그런 다음 MyBatis 1차 캐시 워크플로를 살펴보세요. 앞서 언급했듯이 MyBatis의 첫 번째 수준 캐시는 SqlSession() 메서드 실행이 완료되거나 SqlSession의 close 메서드가 적극적으로 호출되면 SqlSession이 재활용되고 첫 번째 수준 캐시도 재활용됩니다. 같은 시간. 이전 글에서 언급했듯이, MyBatis에서는 selectOne과 selectList 메소드 모두 결국 실행을 위해 selectList 메소드로 변환되므로 SqlSession의 selectList 메소드 구현을 살펴보세요.

 1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2     try { 3       MappedStatement ms = configuration.getMappedStatement(statement); 4       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 5     } catch (Exception e) { 6       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e); 7     } finally { 8       ErrorContext.instance().reset(); 9     }10 }

계속해서 다음을 따르세요. 4행의 코드에서 BaseExeccutor의 쿼리 메서드로 이동합니다.

1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {2     BoundSql boundSql = ms.getBoundSql(parameter);3     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);4     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);5 }

3행은 캐시 조건 CacheKey를 작성합니다. 이는 동일한 조건이 다음을 반환할 수 있기 때문에 조건이 이전 쿼리와 어떻게 동일한지에 대한 질문을 포함합니다. 이전 시간 결과가 반환되고 코드의 이 부분은 다음 부분의 분석을 위해 남겨집니다.

다음으로 4행의 쿼리 메서드 구현을 살펴보세요. 코드는 CachingExecutor에 있습니다.

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2       throws SQLException { 3     Cache cache = ms.getCache(); 4     if (cache != null) { 5       flushCacheIfRequired(ms); 6       if (ms.isUseCache() && resultHandler == null) { 7         ensureNoOutParams(ms, parameterObject, boundSql); 8         @SuppressWarnings("unchecked") 9         List<E> list = (List<E>) tcm.getObject(cache, key);10         if (list == null) {11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12           tcm.putObject(cache, key, list); // issue #578 and #11613         }14         return list;15       }16     }17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }

3~16행의 코드를 무시하고 17행의 쿼리 메서드를 계속 진행합니다. 코드는 In BaseExecutor에 있습니다:

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 3     if (closed) { 4       throw new ExecutorException("Executor was closed."); 5     } 6     if (queryStack == 0 && ms.isFlushCacheRequired()) { 7       clearLocalCache(); 8     } 9     List<E> list;10     try {11       queryStack++;12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;13       if (list != null) {14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);15       } else {16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);17       }18     } finally {19       queryStack--;20     }21     ...22 }

看12行,query的时候会尝试从localCache中去获取查询结果,如果获取到的查询结果为null,那么执行16行的代码从DB中捞数据,捞完之后会把CacheKey作为key,把查询结果作为value放到localCache中。

MyBatis一级缓存存储流程看完了,接着我们从这段代码中可以得到三个结论:

  1. MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor

  2. MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果

  3. MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将标签所在的Mapper的Namespace+标签中定义的sql语句

即只要两次查询满足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。

 

MyBatis二级缓存

上面说完了MyBatis,接着看一下MyBatis二级缓存,还是从二级缓存工作流程开始。还是从DefaultSqlSession的selectList方法进去:

 1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2     try { 3       MappedStatement ms = configuration.getMappedStatement(statement); 4       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 5     } catch (Exception e) { 6       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e); 7     } finally { 8       ErrorContext.instance().reset(); 9     }10 }

执行query方法,方法位于CachingExecutor中:

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

继续跟第4行的query方法,同样位于CachingExecutor中:

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2       throws SQLException { 3     Cache cache = ms.getCache(); 4     if (cache != null) { 5       flushCacheIfRequired(ms); 6       if (ms.isUseCache() && resultHandler == null) { 7         ensureNoOutParams(ms, parameterObject, boundSql); 8         @SuppressWarnings("unchecked") 9         List<E> list = (List<E>) tcm.getObject(cache, key);10         if (list == null) {11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12           tcm.putObject(cache, key, list); // issue #578 and #11613         }14         return list;15       }16     }17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }

从这里看到,执行第17行的BaseExecutor的query方法之前,会先拿Mybatis二级缓存,而BaseExecutor的query方法会优先读取MyBatis一级缓存,由此可以得出一个重要结论:假如定义了MyBatis二级缓存,那么MyBatis二级缓存读取优先级高于MyBatis一级缓存

而第3行~第16行的逻辑:

  • 第5行的方法很好理解,根据flushCache=true或者flushCache=false判断是否要清理二级缓存

  • 第7行的方法是保证MyBatis二级缓存不会存储存储过程的结果

  • 第9行的方法先尝试从tcm中获取查询结果,这个tcm解释一下,这又是一个装饰器模式(数数MyBatis用到了多少装饰器模式了),创建一个事物缓存TranactionalCache,持有Cache接口,Cache接口的实现类就是根据我们在Mapper文件中配置的创建的Cache实例

  • 第10行~第12行,如果没有从MyBatis二级缓存中拿到数据,那么就会查一次数据库,然后放到MyBatis二级缓存中去

至于如何判定上次查询和这次查询是一次查询?由于这里的CacheKey和MyBatis一级缓存使用的是同一个CacheKey,因此它的判定条件和前文写过的MyBatis一级缓存三个维度的判定条件是一致的。

最后再来谈一点,"Cache cache = ms.getCache()"这句代码十分重要,这意味着Cache是从MappedStatement中获取到的,而MappedStatement又和每一个 태그의 id 속성

  1. RowBounds의 오프셋 및 제한 속성에 있습니다. RowBounds는 페이징을 처리하기 위해 MyBatis에서 사용하는 클래스이며 기본값은 Integer.MAX_VALUE
  2. < select>
  3. 라벨에 정의된 sql 문은 MapperA의 경우 어떠한 조건도 변경되지 않았으며 자연스럽게 원래 결과가 반환됩니다.

이 문제는 마이바티스의 2차 캐시에서는 해결할 수 없는 문제이므로 마이바티스의 2차 캐시를 사용하기 위해서는 전제조건이 있습니다:

모든 추가, 삭제, 수정 및 쿼리가 동일한 네임스페이스에 있는지 확인해야 합니다.

.

위 내용은 [마이바티스 소스코드 분석] 마이바티스 1차, 2차 캐시의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.