공공부하자개발 · 영어 학습 노트
자바
실무 확장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 공부하자
홈 › 실무 확장 › 04 / 22

JDBC 기초와 트랜잭션 (H2)

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

2. 핵심 원리

2.1 JDBC 아키텍처 — 인터페이스와 구현의 분리

text
  애플리케이션 코드
      │  java.sql.* 인터페이스만 사용 (Connection, PreparedStatement, ResultSet ...)
      ▼
  DriverManager ──또는── DataSource (풀)
      │  URL 의 "jdbc:h2:" 접두어로 드라이버 선택
      ▼
  org.h2.Driver (구현체)  ←  META-INF/services/java.sql.Driver 에 등록 → ServiceLoader 가 자동 로딩
      │
      ▼
  DB 엔진 (H2 / PostgreSQL / MySQL ...)
구성 요소 역할 비고
DriverManager URL 에 맞는 드라이버에 connect() 위임 JDBC 4 부터 Class.forName 불필요. ServiceLoader 가 자동 등록
DataSource 커넥션을 "얻는 방법"을 추상화. 풀링, 분산 트랜잭션은 이 인터페이스 뒤에서 실무 코드는 항상 DataSource 를 주입받음. 구현은 HikariCP
Connection DB 세션 하나. 트랜잭션 경계(autoCommit, commit, rollback)와 격리 수준을 가짐 비싸다(TCP + 인증 + 세션 메모리). 풀에서 빌려 씀
Statement SQL 문자열을 그대로 실행 DDL, 고정 SQL 에만. 사용자 입력 결합 금지
PreparedStatement ? 자리를 값으로 바인딩해 실행 SQL 과 데이터를 분리 → 인젝션 방어 + 실행 계획 캐시
ResultSet 조회 결과 커서. next() 로 한 행씩 전진 전체를 메모리에 올리지 않음(fetchSize 만큼 가져옴). 열린 동안 커넥션 점유

H2 URL 은 모드에 따라 다릅니다.

URL 의미
jdbc:h2:mem:shop 인메모리. 마지막 커넥션이 닫히면 DB 삭제
jdbc:h2:mem:shop;DB_CLOSE_DELAY=-1 인메모리. JVM 종료까지 유지 (풀에서 커넥션이 전부 반납돼도 데이터 유지)
jdbc:h2:./data/shop 파일 모드. ./data/shop.mv.db 생성
jdbc:h2:tcp://host:9092/shop 서버 모드. 여러 프로세스가 공유

이 레슨은 잔여 파일이 남지 않도록 인메모리 + DB_CLOSE_DELAY=-1 만 씁니다.

2.2 커넥션 비용과 풀

DriverManager.getConnection() 은 매번 새 세션을 만듭니다. 원격 DB 라면 TCP 핸드셰이크 + TLS + 인증 + 세션 초기화로 수십 ms, 인메모리 H2 조차 1~2ms 가 듭니다. 초당 1,000 요청이면 커넥션 생성만으로 CPU 한 코어가 사라집니다.

text
풀 없음:  요청 → [생성 2ms] → 쿼리 0.1ms → [해제] → 요청 → [생성 2ms] → ...
풀 사용:  기동 시 [생성 × 8]  →  요청 → 빌림 0.01ms → 쿼리 0.1ms → 반납 → 요청 → 빌림 ...

풀의 원리는 단순합니다. 미리 만든 커넥션을 큐에 넣어 두고, getConnection() 은 큐에서 꺼내고, close() 는 진짜 닫는 대신 큐에 되돌립니다(변형 4 에서 30줄로 구현). 실무는 HikariCP 를 씁니다 — 누수 감지, 유효성 검사, 타임아웃, 메트릭이 있기 때문입니다. 이 레슨은 H2 내장 JdbcConnectionPool 을 씁니다. 핵심 규칙 두 가지:

  • 커넥션은 짧게 빌리고 바로 반납한다. 외부 API 호출이나 파일 처리 중에 커넥션을 쥐고 있으면 풀이 고갈된다.
  • close() 를 빠뜨리면 풀에서 커넥션이 사라진다(누수). try-with-resources 가 유일하게 안전한 방법.

2.3 Statement vs PreparedStatement — 인젝션과 파싱 캐시

text
Statement:          "SELECT * FROM member WHERE name = '" + input + "'"
  input = ' OR '1'='1  →  SELECT * FROM member WHERE name = '' OR '1'='1'   ← 조건이 항상 참

PreparedStatement:  "SELECT * FROM member WHERE name = ?"   + setString(1, input)
  DB 는 SQL 구조를 먼저 파싱(컴파일)하고, 값은 나중에 '데이터'로 받음 → 따옴표는 그냥 문자

인젝션 방어 외에 성능 이유도 있습니다. DB 는 SQL 을 파싱하고 실행 계획을 세우는데, PreparedStatement 는 같은 SQL 문자열(? 포함)이 반복될 때 그 계획을 재사용합니다. 문자열 결합은 값마다 다른 SQL 이 되어 매번 파싱합니다. 반면 ? 를 쓸 수 없는 자리(테이블명, 컬럼명, ORDER BY 방향)는 화이트리스트로 검증한 뒤 결합해야 합니다.

2.4 ResultSet 타입 매핑

DB 타입 권장 getter 주의
BIGINT / INT getLong / getInt NULL 이면 0 반환. 직후 wasNull() 로 구분
VARCHAR getString NULL → null
DECIMAL getBigDecimal 돈을 getDouble 로 읽지 말 것 (0.1 + 0.2 ≠ 0.3)
DATE getObject(col, LocalDate.class) getDate() 는 java.sql.Date(타임존 함정). JDBC 4.2 부터 java.time 직접 지원
TIMESTAMP getObject(col, LocalDateTime.class) 타임존이 필요하면 OffsetDateTime.class
BOOLEAN getBoolean NULL → false. wasNull 필요
임의 getObject(i) 메타데이터 기반 범용 덤프에

getObject(col, Class<T>) 는 드라이버가 변환을 책임지므로 java.sql.Date → LocalDate 같은 수동 변환 코드가 사라집니다. 바인딩도 setObject(i, LocalDate) 로 대칭입니다.

2.5 트랜잭션 — 시뮬레이션과 실제의 대응

배치 04 시뮬레이션 JDBC 비고
db.begin() conn.setAutoCommit(false) JDBC 에 "begin" 은 없다. autoCommit 을 끄면 다음 SQL 부터 트랜잭션
db.put() ps.executeUpdate() undo 로그 + 행 락
db.get() 같은 커넥션의 SELECT 자기 변경은 보임
db.commit() conn.commit() redo 로그 fsync, 락 해제
db.rollback() conn.rollback() undo 로 원복, 락 해제
세이브포인트 conn.setSavepoint() / conn.rollback(sp) 트랜잭션 안에서 부분 취소
커넥션 끊김 미커밋 자동 롤백 풀 반납 시에도 미커밋이면 롤백됨(HikariCP 기본)

autoCommit 이 true(기본)면 SQL 하나가 트랜잭션 하나입니다. "재고 UPDATE 후 주문 INSERT" 를 autoCommit 상태로 실행하면 UPDATE 는 이미 커밋된 뒤라 INSERT 실패 시 되돌릴 수 없습니다.

2.6 격리 수준

수준 Dirty Read Non-repeatable Read Phantom Read 비고
READ_UNCOMMITTED 발생 발생 발생 미커밋 데이터가 보임. 거의 안 씀
READ_COMMITTED 방지 발생 발생 PostgreSQL, Oracle, H2 기본. 같은 트랜잭션에서 두 번 읽으면 다를 수 있음
REPEATABLE_READ 방지 방지 (MySQL 은 대부분 방지) MySQL InnoDB 기본
SERIALIZABLE 방지 방지 방지 완전 직렬화. 락/재시도 비용 큼

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 로 커넥션 단위로 바꿀 수 있지만, 풀에 반납할 때 원복해야 합니다. 배치는 보통 기본 수준을 쓰고, "읽고-검사하고-갱신" 경합은 SELECT ... FOR UPDATE 로 행을 잠가 해결합니다(예제 5 의 재고).

2.7 배치 실행 — 왕복 횟수를 줄이는 것

text
건별:   ps.executeUpdate() × 100,000  →  DB 왕복 100,000회
배치:   ps.addBatch() × 1000 → ps.executeBatch()  →  왕복 100회 (1000건씩 묶어 전송)

addBatch() 는 드라이버 버퍼에 파라미터 세트를 쌓고 executeBatch() 가 한 번에 보냅니다. 원격 DB 에서 왕복 지연(RTT)이 0.5ms 면 건별 10만 건은 왕복만 50초, 배치는 0.05초입니다. 인메모리 H2 는 왕복이 없어 차이가 작게 나오지만(예제 7), 원리는 같습니다.

MySQL 은 rewriteBatchedStatements=true 옵션을 줘야 드라이버가 INSERT ... VALUES (...),(...),(...) 하나로 재작성해 진짜 빨라집니다 — 옵션 없이는 배치가 건별과 같습니다. 배치 크기는 500~5,000 이 일반적이고, 버퍼가 메모리에 쌓이므로 무한히 키우지 않습니다.

2.8 자원 해제 순서와 예외

text
열기:  Connection → PreparedStatement → ResultSet
닫기:  ResultSet  → PreparedStatement → Connection      (역순. try-with-resources 가 자동으로 보장)

SQLException 은 checked 예외이고, 두 가지 식별자를 가집니다.

식별자 예 용도
getSQLState() 23505 UNIQUE 위반, 23503 FK 위반, 42S02 테이블 없음, 40001 데드락 표준(SQL:2003). 앞 두 자리가 클래스: 23 무결성, 42 문법, 08 커넥션, 40 롤백
getErrorCode() H2 23505, PostgreSQL 없음, Oracle ORA-00001 → 1 벤더 고유

실무 헬퍼는 SQLException 을 런타임 예외로 감싸되 SQLState 를 보존해, 호출자가 "중복 이메일"(사용자 오류)과 "테이블 없음"(프로그램 버그)을 구분하게 합니다. Spring 의 DataAccessException 계층(DuplicateKeyException, DataIntegrityViolationException)이 이 번역을 대신합니다.

핵심 원리
  • 2.1 JDBC 아키텍처 — 인터페이스와 구현의 분리
  • 2.2 커넥션 비용과 풀
  • 2.3 Statement vs PreparedStatement — 인젝션과 파싱 캐시
  • 2.4 ResultSet 타입 매핑
  • 2.5 트랜잭션 — 시뮬레이션과 실제의 대응
  • 2.6 격리 수준
  • 2.7 배치 실행 — 왕복 횟수를 줄이는 것
  • 2.8 자원 해제 순서와 예외
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제