JDBC printStackTrace의 함정: 실패했는데 종료코드가 0이었다

printStackTrace는 예외를 찍기만 하고 종료코드 0으로 끝낸다. DB가 죽었는데 CI가 성공으로 보는 이유. JDBC 커넥션 닫는 순서, PreparedStatement를 쓰는 두 가지 이유까지.

조회
목차

MySQL에 테이블을 만들고 자바에서 JDBC로 조회하는 단계. 화면 없이 콘솔 출력만 하는 작업이었다.

커넥션이 뭔지 모르는 채로 커넥션 풀 설명을 읽었다

"커넥션 생성이 느리다"는 문장부터 만났는데, 커넥션이 뭔지 모르면 커넥션 풀이 왜 나왔는지도 알 수 없다.

순서를 거꾸로 잡고 개념 지도를 먼저 그렸다.

DriverManager → Connection → Statement → ResultSet
                                  (닫을 때는 역순)

실패했는데 종료코드가 0이었다

이날의 제일 큰 수확.

MySQL을 끄고 프로그램을 돌렸다. 예외 스택은 화면에 잘 찍혔다. 근데 종료코드를 확인해보니 0이었다.

java 실행 — 예외 스택이 찍히고도 echo $? 는 0

원인은 printStackTrace(). 화면에 찍기만 하고 프로그램은 정상으로 끝낸다. 예외를 잡아서 출력하고 그냥 넘어간 거다.

프론트에서 npm test 실패하면 CI가 멈추는 것과 같은 메커니즘인데, 자바에서 만나니까 못 알아봤다.

try-with-resources 방향을 반대로 알았다

"괄호 안에 넣은 걸 빼면 어떻게 되나요"라는 질문에 두 번 헛답했다.

괄호가 닫아주는 쪽이다. 괄호 안에 넣으면 자동으로 닫아주고, 안 넣으면 내가 닫아야 한다. 반대로 이해하고 있었다.

PreparedStatement를 쓰는 이유는 두 개다

교재에 "매개변수 처리에 특화"라고만 적혀 있어서 무슨 말인지 몰랐는데, 실체는 두 가지였다.

보안?에 들어간 값은 항상 값으로만 취급된다. SQL 문법으로 해석되지 않으니 SQL 인젝션이 안 된다.

성능 — 틀이 고정이라 DB가 한 번 분석한 걸 재사용할 수 있다.

상속 구조를 알면 외울 게 준다

Statement ← PreparedStatement ← CallableStatement

CallableStatementPreparedStatement와 비슷하게 생긴 건 물려받아서다. 따로 외울 게 아니었다.

ResultSet 커서가 첫 행 "앞"에서 시작하는 이유

처음엔 불편하다고 생각했는데 설계 의도가 있었다.

next() 하나가 이동과 존재 확인을 겸한다. 그래서 0건일 때와 여러 건일 때를 같은 패턴으로 처리할 수 있다.

while (rs.next()) {
    // 0건이면 아예 안 들어오고, 여러 건이면 반복
}

셋을 다, 역순으로 닫는 이유

Connection·Statement·ResultSet은 자바 객체이면서 DBMS 쪽 자원(커서·핸들·세션)을 붙들고 있다. 자바의 가비지 컬렉터는 건너편 DB 자원을 모른다.

그래서 직접 닫아야 하고, 꺼낸 것부터 닫는다. 소속 관계의 역순.

프로시저는 예시를 보고서야 이해했다

"DBMS에 저장된 SQL 묶음"이라는 설명으로는 감이 안 왔다. 구체적인 예를 보고 넘어갔다.

글 삭제를 한다고 하면 실제로는 세 가지를 해야 한다.

1. 댓글 삭제
2. 글 삭제
3. 로그 INSERT

이 묶음에 이름을 붙여 DB에 저장해두고 이름으로 부르는 것 — 그게 프로시저다. 단골 카페에서 "늘 마시던 걸로요" 하는 것과 같다.

트리거와 헷갈리기 쉬운데 부르는 사람이 다르다. 프로시저는 내가 부르고, 트리거는 조건이 되면 DBMS가 알아서 실행한다.

AUTO_INCREMENT는 번호를 재사용하지 않는다

3번을 지워도 다음은 4번이다. id에 구멍이 나는 게 정상이지 버그가 아니다.

남길 것

  • 실패했는데 종료코드 0인 게 제일 위험하다. printStackTrace()는 찍기만 하고 성공으로 끝낸다.
  • PreparedStatement는 보안과 성능 둘 다다.
  • DB 자원은 직접, 역순으로 닫는다. GC가 건너편을 모른다.
  • 추상 설명이 안 통하면 구체 예시를 찾는다. 프로시저가 그랬다.

더 봐야 할 것

  • 커넥션 풀 — "커넥션 생성이 느리다"의 해법. 서블릿은 안 끝나는 프로그램인데 요청마다 만들고 닫으면 무슨 일이 생기나
  • JDBC가 전부 인터페이스인 이유 — 구현체는 MySQL 드라이버 안에 있다. 왜 이렇게 설계했는지
  • printStackTrace() 대신 뭘 쓰나 — 로그로 남기기 vs 예외를 위로 다시 던지기

사이트 내 검색

제목·태그·설명·본문으로 발행 글을 찾습니다