#{} 와 ${} 차이값은 바인드 파라미터, 정렬 컬럼만 화이트리스트를 거쳐 문자열 치환합니다.
<select id="findOrders" resultType="Order">
SELECT * FROM orders
WHERE owner_id = #{loginId}
ORDER BY ${sortColumn}
</select>// 매퍼 호출 전 컨트롤러·서비스에서 반드시 검증
if (!Guard.isAllowedSort(sortColumn)) {
throw new IllegalArgumentException("허용되지 않은 정렬 컬럼");
}@RestControllerAdvice 로 에러 노출 차단예외 원문은 로그에만, 응답에는 코드와 메시지만 내려줍니다.
@RestControllerAdvice
class ApiExceptionHandler {
@ExceptionHandler(Exception.class)
ResponseEntity<ApiError> handle(Exception e) {
log.error("처리 실패", e); // 스택트레이스는 로그에만
return ResponseEntity.status(500)
.body(new ApiError("INTERNAL_ERROR", "처리 중 오류가 발생했습니다"));
}
}18 레슨 Cache 를 재사용해 계정별 실패 횟수를 셉니다.
Cache<String, Integer> failCount = new Cache<>(Duration.ofMinutes(10), 10_000);
int count = failCount.get(loginId).orElse(0);
if (count >= 5) {
throw new LockedException("잠시 후 다시 시도하세요");
}
failCount.put(loginId, count + 1, Duration.ofMinutes(10));