${} 를 쓴다@Select("SELECT * FROM member WHERE name = '${name}'") // ❌ 인젝션. 예제 2 에서 전원 노출
@Select("SELECT * FROM member WHERE name = #{name}") // ✅${} 는 컬럼명·테이블명·정렬 방향처럼 ? 바인딩이 불가능한 자리에만, 화이트리스트 검증 후에 씁니다. 코드 리뷰에서 ${ 를 grep 하는 것이 가장 싼 보안 점검입니다.
int updateGrade(long id, Member.Grade grade); // ❌ #{id} → BindingException: Parameter 'id' not found. Available: [arg1, arg0, param1, param2]
int updateGrade(@Param("id") long id, @Param("grade") Member.Grade grade); // ✅컴파일은 되고 실행 시점에 터집니다. -parameters 컴파일 옵션과 useActualParamName(기본 true)이 있으면 이름을 살릴 수 있지만, 빌드 설정에 의존하지 말고 @Param 을 습관으로 붙이세요.
try (SqlSession s = factory.openSession()) {
mapper.insert(m); // ❌ close() 에서 롤백됨. 예제 3 의 s1
}
try (SqlSession s = factory.openSession()) {
mapper.insert(m);
s.commit(); // ✅
}openSession() 기본이 autoCommit=false 입니다. 예외 없이 끝났는데 데이터가 없으면 십중팔구 이것입니다. 반대로 openSession(true) 로 여러 문장을 실행하면 각각 커밋되어 중간 실패 시 반쪽 데이터가 남습니다.
private final SqlSession session = factory.openSession(); // ❌ 스레드 불안전 + 커넥션 영구 점유 + 1차 캐시가 영원히 살아 옛 데이터 반환
private final SqlSessionFactory factory; // ✅ factory 만 공유. 메서드마다 openSession세션은 요청·작업 단위로 열고 닫는 짧은 객체입니다. 웹 애플리케이션에서 세션을 싱글턴으로 두면 동시 요청이 같은 커넥션과 트랜잭션을 공유해 데이터가 섞입니다.
@One 을 목록 조회에 쓴다@Result(property = "member", column = "member_id", one = @One(select = "MemberMapper.findById")) // ❌ 목록 100건 → SQL 101번
// ✅ 변형 2 처럼 JOIN + 점 표기 매핑. @One 은 단건 상세나 지연 로딩이 필요한 곳에만개발 DB 에는 데이터가 10건이라 티가 안 나고, 운영에서 목록이 1000건이 되면 페이지가 수 초씩 걸립니다. LogFactory.useStdOutLogging() 으로 SQL 이 몇 번 나가는지 보는 습관이 N+1 을 잡는 가장 빠른 방법입니다.
try (SqlSession s = factory.openSession(ExecutorType.BATCH)) {
int n = mapper.insertOrder(order); // ❌ n 은 의미 없는 값(BATCH_UPDATE_RETURN_VALUE). order.get("id") 도 아직 null
long id = ((Number) order.get("id")).longValue(); // NPE
s.flushStatements(); // ✅ flush 후에야 실행되고 키가 채워진다
}BATCH 는 SQL 을 쌓아 두다 나중에 보내므로 "지금 실행됐다"고 가정하는 코드가 전부 깨집니다. 생성 키가 필요한 로직은 SIMPLE 세션으로, 대량 적재만 BATCH 세션으로 분리하세요.
<script> 안에서 <, >= 를 그대로 쓴다@Select("<script>SELECT * FROM member WHERE joined_at >= #{from}</script>") // ❌ XML 파싱 오류 (>= 는 어떤 파서는 통과하지만 < 는 확실히 실패)
@Select("<script>SELECT * FROM member WHERE joined_at >= #{from}</script>") // ✅ > < 로 이스케이프. 또는 <![CDATA[ ... ]]><script> 안은 XML 입니다. 비교 연산자가 많은 SQL 은 이스케이프 때문에 읽기 어려워지므로 Provider 로 옮기거나 XML 매퍼의 <![CDATA[ ]]> 를 쓰는 것이 낫습니다.