DEV Community

Composite
Composite

Posted on • Edited on

Mybatis Cursor<T>

만약 MybatisCursor 를 한글로 조회하면 100이면 100 "오라클 커서"가 조회될 것이다.
압도적으로 이런 용도로 많이 쓰기 때문에 아마 대부분 Mybatis 에 Cursor 인터페이스가 있다는 것 조차 모르는 사람들 많을 것이다.

그래서 간단하게 쓴다.
물론 어자피 공식 설명서 봐도 되긴 하는데... 아무도 Cursor 사용법의 짤막한 한줄조차 번역 안되어 있더라...
대단하다...

A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.

뭐 굳이 해석하자면...

CursorList 와 같은 결과물을 제출하지만, 데이터를 Iterator 를 사용해서 열거할 때만 가져온다.

즉, Lazy의 뜻이 아는 개발자라면, 특히 대용량 로우를 불러와야 하는 개발자라면 한줄기 빛이나 다름없다.
물론 기존처럼 ResultHandler 인터페이스를 구현해서 대용량 로우 불러오기를 꾀할 수 있는데, CursorList 대신에 넣으면 된다는 간편한 차이점이 있다는 거.

@Repository
public interface PersonMapper {

    // 그냥 리스트 불러올 때
    List<Person> selectPersonList(Map<String, Object> params);

    // Mybatis 3.2.4 이전까지 대용량 리스트 불러올 때
    void selectPersonListHuge(Map<String, Object> params, ResultHandler handler);

    // Mybatis 3.2.4 이후 대용량 리스트 불러올 때
    Cursor<Person> selectPersonListLazy(Map<String, Object> params);

}
Enter fullscreen mode Exit fullscreen mode

그렇다면 사용법은 어떠한가? Cursor<T> 인터페이스는 Iterable<T> 를 확장하기 때문에, for 문에서 foreach 처럼 작성할 수 있다. 또한, 잊지 말아야 할 건, Closable 인터페이스도 확장하기 때문에, try 문에 선언하거나, 쓰기 싫으면 close() 메소드로 닫아야 한다. 공식 설명서에서 소개한 사용법은 아래와 같다.

try (Cursor<Person> lazyList = mapper.selectPersonListLazy(params)) {
   for (Person person : lazyList) {
      // 여기에 Person 가지고 뭐 할겨?
   }
}
Enter fullscreen mode Exit fullscreen mode

Javadoc 을 참조하면 사실 별 거 없다.
그나마 유용한 메소드가 getCurrentIndex() 메소드인데, 현재 가져온 행 번호를 가져온다. 물론 자바답게 0부터 시작한다.

자, 소개는 여기까지.

아, 이건 내 경험인데, 내잘못인지 버그인지 모르겠지만, jdbc 로깅한답시고 추가한 log4jdbc 하고 연계했더니 Cursor<T> 리턴은 되는데 항상 데이터 반환이 없다. 리스트가 없다고.
어디쪽 문제인지 모르겠지만, log4jdbc 는 더 이상 개발이 없고, mybatis 로깅도 나쁘진 않기 때문에, 신규 프로젝트에 Mybatis 추가할 경우 log4jdbc 연계는 지양하는 게 좋을 것 같다.
혹시 이거 해결한 분 있으면 제보해 주시면 감수광.

2020-03-23 업데이트

Cursor<T> 의 올바른 사용법

위에 log4jdbc 와 충돌한 줄 알았던 내 자신을 반성하는 의미이고, MyBatis 공식 문서에 잘 나와 있지도 않아 해맸는데, 알고보니 트랜잭션 내에 있어야 한다는 것.
즉, Cursor<T> 사용 시, 트랜잭션 내에 있어야 한다는 것이다. Spring 을 예를 들면,

@Transactional // <- 이거 이거 이거 이거 이거 이거 이거 이거 이거 이거
public List<Person> getList() {
    try (Cursor<Person> cursor = mapper.getPersonListByCursor()) {
        for (Person person : cursor) {
            // ...
        }
    }
    return ...
}
Enter fullscreen mode Exit fullscreen mode

이렇게 @Trasactional 어노테이션이라던가, TransactionTemplate 클래스 또는 PlatformTransactionManager.getTransaction(...) 메소드를 통해 스레드 내 트랜잭션을 활성화해야 작동한다는 것이었다...
내가 새로 Spring Webflux 기반의 프로젝트 구성하면서 알게 되었다... 하아...

게다가, 트랜잭션 내에 있어야 작동하는 특성 상, 트랜잭션 밖에 있거나, 트랜잭션 스레드 밖에서 for 문 등으로 열거하는 행위는 안 될 것이다. 예를 들어 Stream 으로 변환해서 쓴다던지... 이런 게 트랜잭션 내에서만 되고 그 밖에서는 안된다는 것이다.
밖으로 빼내려면 List 로 돌리거나, 아예 List로 반환해야 하는 상황이다.
그래서 reactor 와 같이 이용할 때는 이걸 blocking 으로 대응해야 한다는 아쉬움이 남는다.

여담으로, Mybatis 개발자가 R2DBC 대응을 한다고는 했는데... 언제가 될 지는 모른다.
만약 Webflux를 쓴다면 DB 연동의 해답은 아직 없다는 점이 아쉬운 점 되겠다. R2DBC도 아직 베타 단계고...

Top comments (0)