JdbcPagingItemReader 내부 동작

Spring Batch에서 페이징 처리를 하려면 바로 JdbcPagingItemReader 가 있습니다. 그런데 이 친구 막상 써보려 하면 쿼리는 누가 짜주고, 페이지는 어떻게 나뉘는지 궁금하지 않을 수 없습니다.

이번 JdbcPagingItemReader 의 동작 원리와 내부 구조를 한 방에 이해할 수 있도록 정리해보겠습니다. 

1. 초기 설정 

afterPropertiesSet() 메서드에서 이 모든 준비가 시작됩니다. 

afterPropertiesSet() 내부에서 queryProvider.init(dataSource)가 호출하면

  • 첫 페이지용 쿼리: generateFirstPageQuery()
  • 이후 페이지용 쿼리: generateRemainingPagesQuery()

우리가 쿼리를 직접 쓰지 않아도, Oracle, MySQL, SQL Server 등에 맞는 SQL을 queryProvider가 알아서 생성해줍니다.
내부적으로는 StringBuilder로 SQL을 조립해 최종 문자열을 만들죠.

쿼리 작성은 queryProvider 가 알아서 다 해주게됩니다. 

페이지 쿼리 생성 로직을 보면, 구현체에 따라 Oracle, MySQL, SQL Server 등 다양한 데이터 소스를 지원
generateRowNumSqlQuery()는 페이지 조건, 정렬 키 조건, ROWNUM 조건 등을 조립해 SQL을 반환
StringBuilder를 사용해 SQL을 동적으로 조립한 뒤, 최종 SQL 문자열을 반환합니다. 이때 쿼리 구성에 필요한 정보는 provider 내부에 저장되어 관리됩니다.

 

2. 데이터 읽기

doReadPage()

데이터를 한 페이지씩 읽을 때 doReadPage()가 실행됩니다. 

여기서 페이지 구분은 어떻게 하느냐?

  • 첫 번째 페이지 → 기본 파라미터로 SQL 실행
  • 두 번째 페이지부터 → 이전 페이지에서 읽은 정렬 키 값을 기준으로 다음 데이터를 가져옵니다.

afterPropertiesSet 에서 가져온 쿼리를 첫 페이지와 이후 페이지 쿼리가 다르게 실행됩니다. 

이 방식이 바로 Keyset Paging입니다. 

예를 들어, 내가 마지막으로 읽은 id가 105니까, 다음 페이지 id > 105 부터 읽어와 입니다.

즉, 첫번째 이후 페이지는 startAfterValues 기반으로 keyset paging 동작으로 내부적으로 현재 정렬 key 값을 기억하고 있습니다. (→ Cursor 방식과 유사하다.)

데이터를 읽어올 때는 정렬 키를 기준으로 페이지를 나누는 방식이라 정확하고 빠르게 다음 페이지로 이동

 

3. 정렬 키 기준 페이징 (PagingRowMapper)

keyset paging이 되려면, 현재 페이지의 마지막 row 에서 정렬 키 값을 기억해둬야겠죠?

keyset paging 을 구현하려면 마지막으로 조회된 정렬 키 값을 추적해야 합니다. 현재 페이지에서 마지막으로 조회된 정렬 키 값을 추적해야 합니다.

PagingRowMapper는 각 Row를 매핑하면서
queryProvider.getSortKeys()에서 정의된 필드들(id, created_at 등)의 값을
startAfterValues에 저장해둡니다.

이 값이 다음 페이지 쿼리에 쓰이는 조건이 됩니다. 

WHERE id > :_id

RowMapper에서 정렬 키를 추출 → 다음 페이지로 넘기는 키로 활용

 

4. 파라미터조합

getParameterMap

쿼리 실행에 필요한 파라미터는 두 가지가 섞여있습니다.

구분 설명 예시
일반 파라미터 상태, 날짜, 사용자 ID 등 필터 조건 status = 'ACTIVE'
정렬 키 페이징 기준 값 id > 105

위에 있는걸 하나의 Map으로 합쳐주는 게 getParameterMap() 입니다. 

이 Map이 NamedParameterJdbcTemplate.query() 로 넘어가서 WHERE status = :status AND id > :_id 같은 쿼리를 실행

5. 정리

JdbcPagingItemReader는 내부적으로 PagingQueryProvider를 통해 페이지 단위 SQL을 생성하고 실행합니다.

1. 첫 페이지는 기본 파라미터로 실행된 SQL:
   SELECT ... FROM table ORDER BY id ASC LIMIT 10

2. 다음 페이지는 마지막 읽은 row의 key를 기억해서 실행된 SQL:
   SELECT ... FROM table WHERE id > ? ORDER BY id ASC LIMIT 10

정렬 키가 중요한 이유는 

JdbcPagingItemReader는 반드시 정렬 키(sort key)를 필요한데, 이 키가 있어야 다음 페이지를 안전하게 이어서 읽을 수 있고, 고정된 결과 순서를 유지할 수 있기 때문입니다.
→ 정렬 키는 실행 중 RowMapper에서 추출되어 다음 SQL 조건으로 쓰이게 됩니다.