레벨 1 캐시 및 레벨 2 캐시
Mybatis는 데이터 캐시를 2 레벨 구조로 설계하여 1 단계 캐시 및 2 단계 캐시로 나뉩니다.
레벨 1 캐시는 데이터베이스 세션을 나타내는 SQLSESSION 객체에 위치한 세션 세션 수준 캐시이며 로컬 캐시라고도합니다. Level 1 캐시는 Mybatis가 내부적으로 구현 한 기능입니다. 사용자는 기본적으로 구성 할 수없고 자동으로 지원할 수 없습니다. 사용자는 사용자 정의 할 권리가 없습니다 (그러나 이것은 절대적이지 않으며 개발 플러그인을 통해 수정할 수 있습니다).
두 번째 레벨 캐시는 응용 프로그램의 선언주기와 동일한 수명주기가 긴 애플리케이션 수준 캐시입니다. 이는 기능 범위가 전체 애플리케이션 애플리케이션임을 의미합니다.
MyBatis의 첫 번째 수준 캐시 및 2 단계 캐시 구성은 아래 그림에 나와 있습니다.
레벨 1 캐싱의 작동 메커니즘 :
레벨 1 캐시는 세션 세션 수준입니다. 일반적으로 SQLSESSION 객체는 실행자 개체를 사용하여 세션 작업을 완료합니다. executor 객체는 쿼리 성능을 향상시키기 위해 캐시 캐시를 유지합니다.
2 차 캐싱의 작동 메커니즘 :
위에서 언급했듯이 SQLSession 객체는 실행자 개체를 사용하여 세션 작업을 완료합니다. Mybatis의 2 차 캐싱 메커니즘의 핵심은이 집행자 대상에 대한 소란을 만드는 것입니다. 사용자가 "Cacheenabled = true"를 구성한 경우 MyBatis가 SQLSESSION 객체에 대한 실행자 객체를 생성 할 때 Executor Object : CachingExecutor에 데코레이터를 추가합니다. 현재 SQLSESSIO는 캐싱 executor 객체를 사용하여 작업 요청을 완료합니다. 쿼리 요청의 경우 CachingExecutor는 먼저 쿼리 요청에 응용 프로그램 수준 보조 캐시에서 결과가 캐시 된지 여부를 결정합니다. 쿼리 결과가 있으면 캐시 된 결과를 직접 반환합니다. 캐시가없는 경우 실제 실행자 객체로 넘겨져 쿼리 작업을 완료합니다. 그 후, 캐싱 executor는 실제 실행자가 반환 한 쿼리 결과를 캐시에 배치 한 다음 사용자에게 반환합니다.
Mybatis의 보조 캐시는보다 유연하게 설계되었습니다. mybatis에서 정의한 보조 캐시 구현을 사용할 수 있습니다. org.apache.ibatis.cache.cache 인터페이스를 구현하여 캐시를 사용자 정의 할 수도 있습니다. Memcached 등과 같은 타사 메모리 캐시 라이브러리를 사용할 수도 있습니다.
캐시 변환
질문:
가장 일반적인 문제는 캐시를 열면 페이징을 쿼리 할 때 첫 페이지의 데이터가 페이지로 반환된다는 것입니다. 또한 SQL 자동 생성 플러그인을 사용하여 Get 메소드에 대한 SQL을 생성 할 때 전달 된 매개 변수가 작동하지 않습니다. 전달 된 매개 변수에 관계없이 첫 번째 매개 변수의 쿼리 결과가 반환됩니다.
이러한 문제가 발생하는 이유 :
이전에 mybatis의 실행 프로세스를 설명 할 때, 캐시 활성화의 전제에 따라 Mybatis의 집행자는 먼저 캐시에서 데이터를 읽고 데이터베이스로 이동하여 읽을 수없는 경우 쿼리로 이동합니다. 문제는 여기에 있습니다. SQL 자동 생성 플러그인 및 페이징 플러그인의 실행 시간은 StateHandler에 있으며 명령 핸들러는 집행자 후에 실행됩니다. SQL 자동 생성 플러그인 및 페이징 플러그인이 SQL을 다시 작성하여 구현하든, Executor는 캐시 키를 생성하고 읽을 때 원래 SQL을 사용하므로 (키는 SQL 및 해당 매개 변수 값으로 구성됨) 물론 문제가 있습니다.
문제 해결 :
문제의 원인이 발견되면 문제를 해결하는 것이 편리합니다. 인터셉터를 통해 실행자에서 키를 생성하는 방법을 무시하고 자동으로 생성 된 SQL (SQL 자동 생성 플러그인에 해당)을 사용하거나 생성 할 때 페이징 정보 (페이징 플러그인에 해당)를 추가하십시오.
인터셉터 서명 :
@InterCepts ({@signature (type = executor.class, method = "query", args = {mappedStatement.class, object.class, rowbounds.class, resulthandler.class}))) 공개 클래스 캐시 인터셉터는 인터셉터 {...}서명에서 볼 수 있듯이 인터셉트 할 대상 유형은 Executor입니다 (참고 : 유형은 인터페이스 유형으로 만 구성 할 수 있음), 가로 채기 메소드는 Query라는 메소드입니다.
절편의 구현 :
공개 물체 인터셉트 (호출) 던지기 가능 {executor executorProxy = (Executor) invocation.getTarget (); metaobject metaexecutor = metaobject.forobject (executorProxy, default_object_factory, default_object_wrapper_factory); // 프록시 객체 체인을 분리하는 while (metaExecutor.hasgetter ( "h")) {Object 객체 = metaExecutor.getValue ( "h"); MetaExecutor = metaObject.forObject (Object, default_object_factory, default_object_wrapper_factory); } // 마지막 프록시 객체를 분리하는 대상 클래스 (metaExecutor.hasgetter ( "target")) {Object Object = metaExecutor.getValue ( "target"); MetaExecutor = metaObject.forObject (Object, default_object_factory, default_object_wrapper_factory); } object [] args = invocation.getArgs (); reply.query (metaexecutor, args); } public <e> list <e> query (metaobject metaexecutor, object [] args)는 sqlexception {mappedstatement ms = (mappedstatement) args [0]; Object ParameterObject = args [1]; Rowbounds rowbounds = (Rowbounds) args [2]; resulthandler resulthandler = (resulthandler) args [3]; BONDSQL BONDSQL = MS.GETBOUNDSQL (ParameterObject); // 키 캐시키 생성을 다시 작성합니다 Cachekey = CreateCachekey (ms, parameterObject, rowbounds, boundsql); Executor Executor = (Executor) MetaExecutor.getoriginalObject (); Return Executor.query (MS, ParameterObject, RowBounds, Resulthandler, Cachekey, BoundSQL); } private cachekey createCachekey (MappedStatement MS, Object ParameterObject, rowbounds rowbounds, boundsql boundsql) {configuration = ms.getConfiguration (); pagesqlid = configuration.getVariables (). getProperty ( "pagesqlid"); if (null == pagesqlid || "".equals (pagesqlid)) {logger.warn ( "속성 pagesqlid가 설정되지 않고 기본값을 사용하지 않습니다.*page $ '"); pagesqlid = defaultPagesQlid; } CACHEKEY CACHEKEY = NEW CACHEKEY (); cachekey.update (ms.getid ()); cachekey.update (rowbounds.getOffset ()); cachekey.update (rowbounds.getLimit ()); List <ParametErmpaping> ParametErmappings = boundsql.getParameterMappings (); // SQL을 자동으로 생성하는 버그를 해결하고 SQL 문이 비어 있으므로 키가 오류를 생성합니다. id = id.substring (id.lastindexof ( ".") + 1); 문자열 newsql = null; try {if ( "select".equals (id)) {newsql = sqlbuilder.buildselectsql (parameterObject); } sqlsource sqlsource = buildsqlsource (configuration, newsql, parameterObject.getClass ()); parameterMappings = sqlsource.getBoundSql (parameterObject) .getParamEterMappings (); cachekey.update (NewsQL); } catch (예외 e) {logger.error ( "CacheKey 오류 업데이트", e); }} else {cachekey.update (boundsql.getSql ()); } metaobject metaobject = metaobject.foroBject (parameterObject, default_object_factory, default_object_wrapper_factory); if (parameterMappings.size ()> 0 && parameterObject! = null) {typeHandlerRegistry TypeHandlerRegistry = Ms.GetConfiguration (). getTypeHandlerRegistry (); if (typehandlerregistry.hastypeHandler (parameterObject.getClass ())) {cachekey.update (parameterObject); } else {for (ParametErmpapping ParametErmping) {String PropertyName = ParametErmpapp.GetProperty (); if (metaobject.hasgetter (propertyname)) {cachekey.update (metaobject.getValue (PropertyName)); } else if (boundsql.hasadditionalParameter (propertyName)) {cachekey.update (boundsql.getAdDitionalParameter (PropertyName)); }}}}} // 페이징 쿼리가 필요한 경우 페이지 매개 변수에 페이지 당 페이지 당 현재 페이지와 페이지 수를 CacheKey에 추가하십시오. (Ms.getId (). matches (pagesqlid) && metaobject.hasgetter ( "page")) {pageparameter page = (pageparameter) metaobeter.getvalue ( "page"); if (null! = page) {cachekey.update (page.getCurrentPage ()); cachekey.update (page.getPagesize ()); }} 반환 캐시 키; } 플러그인 구현 :
public Object Plugin (Object Target) {// 대상 클래스가 CachingExecutor 유형 인 경우 대상 클래스가 래핑됩니다. 그렇지 않으면 대상 클래스가 대상 자체로 직접 돌아와서 대상이 (대상 인스턴스의 CachingExecutor) {return plugin.wrap (target, this); } else {반환 대상; }}