Spring Boot와 MyBatis로 Oracle 데이터베이스 페이징 처리 구현하기

 

Spring Boot와 MyBatis로 Oracle 데이터베이스 페이징 처리 구현하기

Spring Boot와 MyBatis로 Oracle 데이터베이스 페이징 처리 구현하기

Oracle 데이터베이스를 사용하면서 MyBatis를 연동하여 페이징 처리 기능을 구현하고자 할 때, MySQL의 LIMITOFFSET처럼 직관적인 방식이 지원되지 않아 고민되는 경우가 많습니다. 이번 포스팅에서는 Oracle 데이터베이스의 페이징 처리 방법과 함께 MyBatis XML Mapper를 활용한 구현 방안을 구체적으로 설명하겠습니다.


Oracle 데이터베이스 페이징 처리

Oracle은 버전에 따라 페이징 처리 방식이 다릅니다. Oracle 12c 이상에서는 OFFSETFETCH FIRST 구문을 사용하여 간단하게 페이징 처리를 구현할 수 있지만, Oracle 12c 미만에서는 ROWNUM을 활용한 서브쿼리를 작성해야 합니다.

아래는 각 방식에 대한 자세한 설명과 구현 예시입니다.

Oracle 12c 이상: OFFSETFETCH FIRST 사용

Oracle 12c 이상 버전에서는 페이징 처리를 위해 간단한 구문을 제공합니다. MyBatis XML Mapper에 다음과 같은 쿼리를 정의하면 됩니다.

<select id="getChatHistoriesPageable" parameterType="map" resultType="com.example.dto.ChatHistoryDto.List">
    SELECT
        id,
        user_id,
        message,
        created_at
    FROM
        chat_history
    WHERE
        user_id = #{userId}
    ORDER BY
        created_at DESC
    OFFSET #{offset} ROWS FETCH NEXT #{size} ROWS ONLY
</select>

주요 쿼리 설명

  • OFFSET #{offset} ROWS: 페이징 시작 위치를 설정합니다.
  • FETCH NEXT #{size} ROWS ONLY: 지정된 개수만큼 데이터를 반환합니다.

페이징 계산 예시:

  • 페이지 1, 크기 10: offset = 0
  • 페이지 2, 크기 10: offset = 10

Oracle 12c 미만: 서브쿼리와 ROWNUM 사용

12c 미만 버전에서는 ROWNUM을 활용하여 페이징 처리를 구현해야 합니다. MyBatis XML Mapper에 다음과 같이 작성합니다.

<select id="getChatHistoriesPageable" parameterType="map" resultType="com.example.dto.ChatHistoryDto.List">
    SELECT * FROM (
        SELECT
            a.*,
            ROWNUM AS rn
        FROM (
            SELECT
                id,
                user_id,
                message,
                created_at
            FROM
                chat_history
            WHERE
                user_id = #{userId}
            ORDER BY
                created_at DESC
        ) a
        WHERE ROWNUM <= #{offset} + #{size}
    )
    WHERE rn > #{offset}
</select>

주요 쿼리 설명

  • 내부 쿼리에서 ROWNUM으로 행 번호를 계산합니다.
  • 외부 쿼리에서 ROWNUM 값을 기준으로 필터링하여 원하는 페이지 데이터를 반환합니다.


MyBatis Mapper 인터페이스 구현

위 XML Mapper와 연동될 Java 인터페이스를 작성합니다.

package com.example.mapper;

import com.example.dto.ChatHistoryDto;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface ChatHistoryMapper {
    List<ChatHistoryDto.List> getChatHistoriesPageable(@Param("userId") Long userId,
                                                       @Param("offset") int offset,
                                                       @Param("size") int size);
}
  • @Param: MyBatis 쿼리에서 사용할 파라미터를 명시적으로 바인딩합니다.


DTO 클래스 정의

조회된 결과를 매핑할 DTO 클래스를 정의합니다.

package com.example.dto;

import lombok.Data;

import java.time.LocalDateTime;

public class ChatHistoryDto {

    @Data
    public static class List {
        private Long id;
        private Long userId;
        private String message;
        private LocalDateTime createdAt;
    }
}


서비스 레이어 구현

서비스 레이어에서 MyBatis를 호출하고, Spring Data의 Page 객체를 사용하여 페이징 결과를 반환합니다.

package com.example.service;

import com.example.dto.ChatHistoryDto;
import com.example.mapper.ChatHistoryMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class ChatHistoryService {
    private final ChatHistoryMapper chatHistoryMapper;

    public Page<ChatHistoryDto.List> getChatHistories(Long userId, int page, int size) {
        int offset = page * size; // 계산: 페이지 * 페이지 크기
        List<ChatHistoryDto.List> results = chatHistoryMapper.getChatHistoriesPageable(userId, offset, size);

        // 총 개수 조회 (생략 가능: 필요한 경우 totalCount 쿼리 추가)
        int totalCount = 100; // 예시: 별도 쿼리로 계산

        return new PageImpl<>(results, PageRequest.of(page, size), totalCount);
    }
}
  • PageImpl: Spring Data에서 페이징 결과를 반환하는 객체.
  • PageRequest: 페이지 번호와 크기를 전달하는 객체.


컨트롤러 구현

REST API를 호출하는 컨트롤러를 작성합니다.

package com.example.controller;

import com.example.dto.ChatHistoryDto;
import com.example.service.ChatHistoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ChatHistoryController {
    private final ChatHistoryService chatHistoryService;

    @GetMapping("/api/chat-histories")
    public Page<ChatHistoryDto.List> getChatHistories(
            @RequestParam Long userId,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return chatHistoryService.getChatHistories(userId, page, size);
    }
}

API 호출 예:

GET /api/chat-histories?userId=1&page=0&size=10


결론

Oracle 데이터베이스에서 페이징 처리를 구현하려면 사용 중인 Oracle 버전에 따라 적합한 방식을 선택해야 합니다.

  • Oracle 12c 이상: OFFSETFETCH FIRST를 사용하는 간단한 방식.
  • Oracle 12c 미만: ROWNUM과 서브쿼리를 활용한 방식.

Spring Boot와 MyBatis를 함께 사용하면 위 방식들을 손쉽게 구현할 수 있습니다. 이 글이 여러분의 개발에 도움이 되길 바랍니다. 궁금한 점은 댓글로 남겨주세요!

Comments