> Java > java지도 시간 > 본문

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

巴扎黑
풀어 주다: 2017-06-26 10:00:34
원래의
1340명이 탐색했습니다.

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!