본문 바로가기

2.분석 및 설계

[mybatis] Mybatis 내부동작 흐름

들어가기

mybatis에서 내부 구조에 대한 간략한 흐름을 정리해보았다. 예외 처리나 불필요한 코드를 정리했고 중간에 delegate나 단순 호출하는 과정은 생략하였다. 그렇기 때문에 실제 실행 코드와는 차이가 있을 수 있습니다.

작성자: ospace114@empal.com, http://ospace.tistory.com/

selectList() 호출

mybatis에서 가장 처음 시작은 SqlSession에서 시작한다. 일반적으로 조회할 때 사용하는 selectList() 호출이 있다.
"com.tistory.ospace.test.repository.PostRepository" 까지는 mapper의 namespace이고 마지막에 findall은 쿼리 ID이다.

SqlSession sqlSession;
...
List<Object> res = sqlSessin.selectList("com.tistory.ospace.test.repository.PostRepository.findAll");

selectList()에서의 작업은

  1. 설정파일에서 쿼리획득
  2. 획득한 쿼리 실행
<mapper namespace="com.tistory.ospace.test.repository.PostRepository">
    <select id="findAll" resultType="com.tistory.ospace.test.entity.Post">
        SELECT * FROM post
    </select>
</mapper>    

RowBounds은 페이징 처리할 경우 입력되는 인자로 사용하지 않는다. 실제 페이징 처리도 RowBounds을 사용하기 보다는 다른 방식으로 사용하기 때문에 여기서는 기본값인 모든 데이터를 조회한다고 보면된다.

// DefaultSqlSession class
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}

query() 실행

configuration은 실행할 때에 XML 파일에서 쿼리 및 설정 정보를 로딩한다.
getMappedStatement()를 사용해서 쿼리를 획득한다.
MappedStatement는 실행할 쿼리 연산 정보를 가지고 있다.
현재는 별다른 인자가 없기 때문에 RowBounds과 ResultHandler를 기본값을 넣어준다.
executor는 Excutor 객체로 query()를 통해 실제 쿼리 동작을 시킨다.
Executor에 대한 구현은 SimpleExecutor, ReuseExecutor, BatchExecutor, CachingExecutor가 있다.

// CachingExecutor class
List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

query() 실행에서는 가져온 정보를 이용해서 실제 실행할 쿼리를 생성한다. 만약 PreparedStatement라면 넘겨질 파라미터도 생성된다.
먼저 가져온 파라미터로 XML에서 가져온 쿼리 템플릿으로 쿼리를 생성한다.
BoundSql에 사용자 입력에 의해 생성된 동적 쿼리가 저장된다.
만약 CachingExecutor로 실행된다면 CacheKey가 생성된다.
다음 호출과 결과에 대한 캐시 처리를 위해 query()로 실행을 넘긴다.
결국 중요한 부분이 queryFromDatabase()으로 넘어가겠다.

queryFromDatabase() 실행

// SimpleExecutor class
<E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  }

queryFromDatabase()에서 결국 doQuery()를 실행한다.

// SimpleExecutor class
<E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

doQuery()에서는 StatementHandler 객체를 생성하고, 최종 query()를 실행한다.
StatementHandler는 JDBC 연산를 처리하는 역할이다.

query() 실행

// PreparedStatementHandler class
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

query()에서 PreparedStatement을 실행하고 결과를 받아온다.
ResultSetHandler에 의해서 JDBC 값을 변환해서 가져온다.

참고

[1] Concluding Chapter: In-depth Analysis of MyBatis Principle, https://programmer.help/blogs/concluding-chapter-in-depth-analysis-of-mybatis-principle.html, 2022/01/26

반응형

'2.분석 및 설계' 카테고리의 다른 글

의사코드(pseudocode) 사용하기  (0) 2023.10.19
glTF 포멧  (0) 2023.06.08
YUV 포멧  (0) 2021.11.15
Mina로 본 네트웍 프레임워크  (0) 2012.07.27
MFC 메시지맵 구조  (0) 2012.07.27