[UMC/8기][Spring Boot] 2주차 시니어 미션

Young Min Kang·2025년 3월 30일

UMC

목록 보기
2/4

📌 MySQL로 구현하는 Cursor 기반 페이징, SQL Injection 방지, JOIN 정리


✅ 1. 내가 진행 중이거나 완료한 미션 조회 쿼리 (Cursor 기반 페이징)

실무에서 무한스크롤 또는 커서 기반 페이지네이션을 구현할 때, 정렬 기준이 복수 개일 경우 커서 조건도 그에 맞춰 작성해야 한다.
아래는 유저가 진행 중이거나 완료한 미션을 포인트 → 최신순으로 정렬하고 커서 기반 페이징을 적용한 쿼리다.

🔍 SQL

SELECT
  um.id AS user_mission_id,
  m.title,
  m.description,
  m.condition,
  um.points_awarded,
  um.status,
  um.has_review,
  m.start_date,
  m.end_date,
  um.created_at
FROM user_missions um
JOIN missions m ON um.mission_id = m.id
WHERE um.user_id = :userId
  AND um.status IN ('IN_PROGRESS', 'COMPLETED_PENDING', 'COMPLETED')
  AND (
    (:cursor_point IS NULL AND :cursor_created_at IS NULL) -- 첫 페이지
    OR (
      (um.points_awarded < :cursor_point)
      OR (um.points_awarded = :cursor_point AND um.created_at < :cursor_created_at)
    )
  )
ORDER BY
  um.points_awarded DESC,
  um.created_at DESC
LIMIT :pageSize;

📌 파라미터 설명

  • :cursor_point – 이전 페이지의 마지막 미션의 포인트
  • :cursor_created_at – 이전 페이지의 마지막 미션의 생성일
  • :pageSize – 가져올 페이지 크기
  • :userId – 현재 로그인한 유저의 ID

📘 사용 방식

  1. 첫 페이지 요청 시
    cursor_point = NULL, cursor_created_at = NULL
  2. 다음 페이지 요청 시
    이전 페이지의 마지막 항목의 points_awarded, created_at 값을 커서로 전달한다.

✅ 2. SQL Injection이란? 그리고 실무에서 어떻게 방지하는가?

SQL Injection은 사용자의 입력값을 통해 SQL 쿼리가 조작되는 보안 취약점이다. 주로 로그인, 검색, 게시판 입력 등에서 발생할 수 있다.

💥 발생 예시

SELECT * FROM users WHERE email = '[입력값]' AND password = '[입력값]';

사용자가 아래처럼 입력할 경우 문제가 발생한다.

입력값: ' OR 1=1 --

위 입력은 전체 유저 정보를 가져오게 되며, 인증이 우회된다.

✅ 실무에서 적용하는 방지 방법

  1. Prepared Statement 사용 (매개변수 바인딩)
    입력값을 SQL에 직접 포함하지 않고, 쿼리와 파라미터를 분리하여 처리한다.

    ```java
    String sql = "SELECT * FROM users WHERE email = ?";
    PreparedStatement stmt = connection.prepareStatement(sql);
    stmt.setString(1, email);
    ```

  2. ORM 사용 (JPA, TypeORM, Prisma 등)
    SQL을 직접 작성하지 않고, ORM이 내부적으로 안전하게 쿼리를 구성하게 한다.
    ex) repository.findBy({ email })

  3. 화이트리스트 기반 필터링 적용
    검색 키워드나 정렬 파라미터 등을 사전에 허용한 값만 받아들이도록 처리한다.


✅ 3. JOIN의 종류와 실무에서의 사용 예

데이터베이스에서 두 테이블을 조인할 때는 목적에 따라 JOIN을 선택해야 한다. 실무에서는 다음과 같은 상황에서 많이 사용된다.

🔗 JOIN 종류

JOIN 종류설명실무 사용 예
INNER JOIN교집합만 반환유저가 등록한 리뷰만 보여줄 때
LEFT OUTER JOIN왼쪽 테이블의 전체 + 오른쪽 일치 데이터가게 목록을 보여줄 때 리뷰가 없더라도 전부 출력
RIGHT OUTER JOIN오른쪽 테이블의 전체 + 왼쪽 일치 데이터잘 사용하지 않음 (MySQL에서 비권장)
FULL OUTER JOIN양쪽 모두의 데이터를 출력MySQL은 UNION으로 대체
CROSS JOIN모든 조합 반환 (카티션 곱)실무에서는 거의 사용하지 않음

📘 실무 팁

  • LEFT JOIN은 필수값이 없는 경우에 자주 사용되며, NULL 여부를 판단해서 조건을 분기하기도 한다.
  • INNER JOIN은 성능이 좋지만 데이터가 누락될 수 있으므로 요구사항에 맞게 선택해야 한다.
  • 조인 시에는 반드시 필요한 컬럼만 SELECT하고, 인덱스 활용 여부를 EXPLAIN으로 확인하는 것이 좋다.

✅ 마무리

정렬 기준이 여러 개인 경우 Cursor 기반 페이지네이션은 단일 정렬보다 구현이 까다롭다.
하지만 정렬 기준에 맞는 커서 조건을 잘 설계하면 효율적이고 정확한 페이지네이션이 가능하다.

또한 SQL Injection을 방지하는 방법은 단순히 Prepared Statement 하나로 끝나지 않는다.
입력 검증, ORM 사용, 화이트리스트 필터링 등을 모두 함께 적용해야 한다.

JOIN 또한 단순히 붙이는 연산이 아니라, 데이터 요구사항퍼포먼스를 고려한 설계가 중요하다.

실무에서 마주치는 복합 정렬, 페이징, 보안, JOIN 이슈는 꼼꼼하게 설계하고 테스트하는 습관이 필요하다.

profile
꾸준히 한걸음씩

0개의 댓글