
실무에서 무한스크롤 또는 커서 기반 페이지네이션을 구현할 때, 정렬 기준이 복수 개일 경우 커서 조건도 그에 맞춰 작성해야 한다.
아래는 유저가 진행 중이거나 완료한 미션을 포인트 → 최신순으로 정렬하고 커서 기반 페이징을 적용한 쿼리다.
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 = NULL, cursor_created_at = NULLpoints_awarded, created_at 값을 커서로 전달한다.SQL Injection은 사용자의 입력값을 통해 SQL 쿼리가 조작되는 보안 취약점이다. 주로 로그인, 검색, 게시판 입력 등에서 발생할 수 있다.
SELECT * FROM users WHERE email = '[입력값]' AND password = '[입력값]';
사용자가 아래처럼 입력할 경우 문제가 발생한다.
입력값: ' OR 1=1 --
위 입력은 전체 유저 정보를 가져오게 되며, 인증이 우회된다.
Prepared Statement 사용 (매개변수 바인딩)
입력값을 SQL에 직접 포함하지 않고, 쿼리와 파라미터를 분리하여 처리한다.
```java
String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, email);
```
ORM 사용 (JPA, TypeORM, Prisma 등)
SQL을 직접 작성하지 않고, ORM이 내부적으로 안전하게 쿼리를 구성하게 한다.
ex) repository.findBy({ email })
화이트리스트 기반 필터링 적용
검색 키워드나 정렬 파라미터 등을 사전에 허용한 값만 받아들이도록 처리한다.
데이터베이스에서 두 테이블을 조인할 때는 목적에 따라 JOIN을 선택해야 한다. 실무에서는 다음과 같은 상황에서 많이 사용된다.
| JOIN 종류 | 설명 | 실무 사용 예 |
|---|---|---|
| INNER JOIN | 교집합만 반환 | 유저가 등록한 리뷰만 보여줄 때 |
| LEFT OUTER JOIN | 왼쪽 테이블의 전체 + 오른쪽 일치 데이터 | 가게 목록을 보여줄 때 리뷰가 없더라도 전부 출력 |
| RIGHT OUTER JOIN | 오른쪽 테이블의 전체 + 왼쪽 일치 데이터 | 잘 사용하지 않음 (MySQL에서 비권장) |
| FULL OUTER JOIN | 양쪽 모두의 데이터를 출력 | MySQL은 UNION으로 대체 |
| CROSS JOIN | 모든 조합 반환 (카티션 곱) | 실무에서는 거의 사용하지 않음 |
LEFT JOIN은 필수값이 없는 경우에 자주 사용되며, NULL 여부를 판단해서 조건을 분기하기도 한다.INNER JOIN은 성능이 좋지만 데이터가 누락될 수 있으므로 요구사항에 맞게 선택해야 한다.정렬 기준이 여러 개인 경우 Cursor 기반 페이지네이션은 단일 정렬보다 구현이 까다롭다.
하지만 정렬 기준에 맞는 커서 조건을 잘 설계하면 효율적이고 정확한 페이지네이션이 가능하다.
또한 SQL Injection을 방지하는 방법은 단순히 Prepared Statement 하나로 끝나지 않는다.
입력 검증, ORM 사용, 화이트리스트 필터링 등을 모두 함께 적용해야 한다.
JOIN 또한 단순히 붙이는 연산이 아니라, 데이터 요구사항과 퍼포먼스를 고려한 설계가 중요하다.
실무에서 마주치는 복합 정렬, 페이징, 보안, JOIN 이슈는 꼼꼼하게 설계하고 테스트하는 습관이 필요하다.