공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 05 / 22

MyBatis 어노테이션 매퍼로 쿼리 연동

섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

5. 자주 하는 실수 (Tip)

❌ 실수 1: 값 자리에 ${} 를 쓴다

java
@Select("SELECT * FROM member WHERE name = '${name}'")     // ❌ 인젝션. 예제 2 에서 전원 노출
@Select("SELECT * FROM member WHERE name = #{name}")       // ✅

${} 는 컬럼명·테이블명·정렬 방향처럼 ? 바인딩이 불가능한 자리에만, 화이트리스트 검증 후에 씁니다. 코드 리뷰에서 ${ 를 grep 하는 것이 가장 싼 보안 점검입니다.

❌ 실수 2: 파라미터 2개 이상에 @Param 을 빼먹는다

java
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 을 습관으로 붙이세요.

❌ 실수 3: commit() 을 안 부르고 "저장이 안 된다"고 한다

java
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) 로 여러 문장을 실행하면 각각 커밋되어 중간 실패 시 반쪽 데이터가 남습니다.

❌ 실수 4: SqlSession 을 필드에 두고 공유한다

java
private final SqlSession session = factory.openSession();   // ❌ 스레드 불안전 + 커넥션 영구 점유 + 1차 캐시가 영원히 살아 옛 데이터 반환
private final SqlSessionFactory factory;                     // ✅ factory 만 공유. 메서드마다 openSession

세션은 요청·작업 단위로 열고 닫는 짧은 객체입니다. 웹 애플리케이션에서 세션을 싱글턴으로 두면 동시 요청이 같은 커넥션과 트랜잭션을 공유해 데이터가 섞입니다.

❌ 실수 5: @One 을 목록 조회에 쓴다

java
@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 을 잡는 가장 빠른 방법입니다.

❌ 실수 6: BATCH 세션에서 insert 반환값과 생성 키를 바로 쓴다

java
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 세션으로 분리하세요.

❌ 실수 7: <script> 안에서 <, >= 를 그대로 쓴다

java
@Select("<script>SELECT * FROM member WHERE joined_at >= #{from}</script>")     // ❌ XML 파싱 오류 (>= 는 어떤 파서는 통과하지만 < 는 확실히 실패)
@Select("<script>SELECT * FROM member WHERE joined_at &gt;= #{from}</script>")  // ✅ &gt; &lt; 로 이스케이프. 또는 <![CDATA[ ... ]]>

<script> 안은 XML 입니다. 비교 연산자가 많은 SQL 은 이스케이프 때문에 읽기 어려워지므로 Provider 로 옮기거나 XML 매퍼의 <![CDATA[ ]]> 를 쓰는 것이 낫습니다.

자주 하는 실수 (Tip)
  • ❌ 실수 1: 값 자리에 ${} 를 쓴다
  • ❌ 실수 2: 파라미터 2개 이상에 @Param 을 빼먹는다
  • ❌ 실수 3: commit() 을 안 부르고 "저장이 안 된다"고 한다
  • ❌ 실수 4: SqlSession 을 필드에 두고 공유한다
  • ❌ 실수 5: @One 을 목록 조회에 쓴다
  • ❌ 실수 6: BATCH 세션에서 insert 반환값과 생성 키를 바로 쓴다
  • ❌ 실수 7: <script> 안에서 <, >= 를 그대로 쓴다
이전 섹션4 응용 변형 예제5 / 7다음 섹션6 연습 문제