Mybatis 1 급 캐 시 와 Spring Framework 를 결합 한 후 실 효 된 소스 코드 탐구

1.다음 사례 에서 두 번 의 조회 콘 솔 을 실행 하면 SQL 조 회 를 한 번 만 출력 합 니 다.

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/hrh/mapper/PersonMapper.xml"/>
    </mappers>
</configuration>

PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hrh.mapper.PersonMapper">
    <resultMap id="BaseResultMap" type="com.hrh.bean.Person">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="BIGINT"/>
    </resultMap>
    <sql id="Base_Column_List">
    id, name, age
    </sql>
    <select id="list" resultType="com.hrh.bean.Person">
        select
        <include refid="Base_Column_List"/>
        from tab_person
    </select>
</mapper>

public interface PersonMapper {
     List<Person> list();
}

String resource = "mybatis-config2.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();//    
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        mapper.list();
        mapper.list();

이러한 상황 이 발생 한 이 유 는 Mybatis 에 1 급 캐 시 가 존재 하기 때 문 입 니 다.아래 debug 는 내부 절 차 를 탐구 합 니 다.

(1)mapper.list()는 MapperProxy\#invoke()에 들 어 갑 니 다.매개 변수 proxy 는 하나의 프 록 시 대상(모든 Mapper 인터페이스 가 하나의 프 록 시 대상 으로 변 환 됩 니 다)이 고 세 션 sqlSession,인터페이스 정보,방법 정 보 를 포함 합 니 다.method 는 목표 방법(현재 실행 하 는 방법)입 니 다.그 안에 속 하 는 어떤 종류(인터페이스),방법 명,반환 유형(List,Map,void 또는 기타),매개 변수 유형 등 이 포함 되 어 있 습 니 다.args 는 매개 변수 입 니 다.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //     methodCache         :     、  (select、update )、    
    //       MapperMethod,        methodCache 
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //    SQL     
    return mapperMethod.execute(sqlSession, args);
  }
  
cacheMapperMethod:MapperMethod 는 방법 명,유형(select,update 등),반환 유형 등 정 보 를 포함 합 니 다.

private MapperMethod cachedMapperMethod(Method method) {
    //     
    MapperMethod mapperMethod = methodCache.get(method);
    //                      
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
(2)MapperMethod\#execute()는 SQL 형식 에 따라 서로 다른 조회 방법 에 들어간다.

public Object execute(SqlSession sqlSession, Object[] args) {
    //    
    Object result;
    //      
    switch (command.getType()) {
      case INSERT: {//    
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {//    
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {//    
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT://    
        //      
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
          //  List   
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
          //  Map   
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
          //       
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
(3)위의 사례 는 selection 문 구 였 고 결 과 는 List 집합 이 었 기 때문에 MapperMethod\#execute Formany()에 들 어 갔다.

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //    
    Object param = method.convertArgsToSqlCommandParam(args);
    //       
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //  list            ,    
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
(4)selectList 는 DefaultSqlSession\#selectList()를 실 행 했 습 니 다.

 public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //SQL     :resource(xxMapper.xml)、id、sql、     
      MappedStatement ms = configuration.getMappedStatement(statement);
      //    
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

(5)다음 캐 시 실행 기 를 호출 하 는 방법:CachingExecutor\#query()

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

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //    
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //        
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
(6)다음 에 BaseExecutor\#query()를 실행 합 니 다.결 과 를 localCache 에 캐 시 하 는 것 을 아래 에서 볼 수 있 습 니 다.

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //        (   0),  <select>   flushCache=true      
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
     //      +1
      queryStack++;
      // localCache     
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //    
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    //         
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //        
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //               
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //     
      localCache.removeObject(key);
    }
    //       
    localCache.putObject(key, list);
    //      
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
2.그러나 Spring Framework+Mybatis 는 상황 이 다 릅 니 다.조회 할 때마다 데이터 베 이 스 를 연결 하여 조회 하고 콘 솔 에서 SQL 을 인쇄 합 니 다.다음 과 같은 사례 가 있 습 니 다.

@Service
public class PersonService  {
    @Autowired
    PersonMapper personMapper;

    public List<Person> getList() {
        personMapper.list();
        personMapper.list();
        return personMapper.list();
    }
}

@Configuration
@ComponentScan("com.hrh")
@MapperScan("com.hrh.mapper")
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setMapperLocations(resolveMapperLocations());
        return factoryBean;
    }

    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<String> mapperLocations = new ArrayList<>();
        mapperLocations.add("classpath*:com/hrh/mapper/*Mapper*.xml");
        List<Resource> resources = new ArrayList();
        if (mapperLocations != null) {
            for (String mapperLocation : mapperLocations) {
                try {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException e) {
                    // ignore
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUsername("xxx");
        driverManagerDataSource.setPassword("xxx");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
        return driverManagerDataSource;
    }
}

  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
        PersonService bean = context.getBean(PersonService.class);
        bean.getList();

아래 debug 가 들 어 가 는 절 차 는 위의(1),(2),(3)와 일치 하지만 네 번 째 단 계 는 SqlSession Template\#selectList()에 들 어 가 는 것 입 니 다.

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
다음 selectList()는 방법 에 의 해 차 단 됩 니 다.method.invoke()는 DefaultSqlSession\#selectList()로 실 행 됩 니 다.다시 위의 네 번 째 단계 로 돌아 가 계속 합 니 다.즉,위의(1)~(6)에 앞 뒤 글 을 삽입 하여 회 의 를 닫 는 작업 을 합 니 다.

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //    
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //      
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);//           
        }
        return result;
        
      } catch (Throwable t) {//            
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //    
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
요약:
Mybatis 의 1 급 캐 시 는 세 션 등급 의 캐 시(단일 스 레 드,특히 계 륵)입 니 다.Mybatis 는 SqlSession 세 션 대상 을 만 들 때마다 데이터베이스 세 션 을 열 었 다 는 뜻 입 니 다.한 세 션 에서 응용 프로그램 은 짧 은 시간 안에 같은 검색 어 를 반복 적 으로 실행 할 수 있 습 니 다.데이터 에 캐 시 하지 않 으 면매번 조회 할 때마다 데이터베이스 조 회 를 실행 해 야 하기 때문에 데이터베이스 자원 의 낭 비 를 초래한다.또한 SqlSession 을 통 해 실 행 된 작업 은 실제로 Executor 에서 데이터베이스 작업 을 수행 하기 때문에 Executor 에서 간단 한 캐 시,즉 1 급 캐 시 를 만 듭 니 다.매번 조회 결 과 를 캐 시 하고 조 회 를 다시 실행 할 때 1 급 캐 시(기본적으로 열 린 것)를 먼저 조회 합 니 다.명중 하면 바로 돌아 갑 니 다.그렇지 않 으 면 데이터 베 이 스 를 조회 하고 캐 시 에 넣 습 니 다.
1 급 캐 시 수명 주 기 는 SqlSession 의 수명 주기 와 같 습 니 다.따라서 Mybatis 와 Spring Framework 의 통합 패키지 에 SqlSession Template 클래스(프 록 시 클래스 로 검색 방법 을 강화 하 였 습 니 다)가 확장 되 었 을 때 모든 조 회 는 SqlSession Template 프 록 시 를 거 쳐 DefaultSqlSession\#selectList()에 들 어 갑 니 다.검색 을 마 친 후 세 션 SqlSession 을 닫 아서 캐 시가 효력 을 잃 었 습 니 다.
근 데 왜 이렇게 해 야 돼 요?
원시 적 인 Mybatis 는 SqlSession 인 터 페 이 스 를 노출 시 켰 기 때문에 close 방법 이 노출 되 어 사용 할 수 있 습 니 다.닫 는 것 과 닫 지 않 는 것 을 선택 할 수 있 습 니 다.그러나 Mybatis 와 Spring Framework 의 통합 패키지 에서 SqlSession 은 Spring Framework 관리 에 맡 겼 고 노출 되 지 않 았 으 며 안정 적 인 결정 을 위해 직접 닫 았 습 니 다.
Mybatis 1 급 캐 시 와 Spring Framework 를 결합 한 후 실 효 된 소스 코드 에 대한 탐 구 를 소개 합 니 다.Mybatis 1 급 캐 시 Spring Framework 의 실효 내용 에 대해 서 는 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부탁드립니다!

좋은 웹페이지 즐겨찾기