JDBC 데이터베이스 연결
JDBC가 무엇인지부터 시작해 Connection·PreparedStatement·ResultSet의 역할을 익히고 회원 게시판을 H2 데이터베이스에 연결합니다.
웹에서 회원가입이나 게시글 등록을 처리해도 서버를 다시 시작할 때 데이터가 사라진다면 실제 서비스로 쓰기 어렵습니다.
데이터를 계속 보관하려면 애플리케이션이 데이터베이스에 SQL을 보내고 결과를 받아야 합니다.
JDBC(Java Database Connectivity)는 이 일을 위한 Java 표준 인터페이스입니다.
처음에는 다음 네 역할만 구분하면 됩니다.
| 이름 | 입문자에게 필요한 뜻 |
|---|---|
| 데이터베이스 | 회원과 게시글을 디스크에 보관하고 SQL로 읽고 쓰는 프로그램 |
| JDBC 드라이버 | Java의 공통 JDBC 호출을 H2·MySQL·PostgreSQL의 통신 방식으로 바꾸는 라이브러리 |
Connection | 애플리케이션과 데이터베이스 사이에 열린 한 연결 |
PreparedStatement / ResultSet | 값을 안전하게 넣은 SQL / 조회 결과를 한 행씩 읽는 객체 |
가장 작은 흐름은 연결 열기 → SQL 준비 → 실행 및 결과 읽기 → 연결 닫기입니다.
아래에서 URL·드라이버·타임아웃을 자세히 다루더라도 이 네 단계로 돌아와 현재 위치를 확인합니다.
JDBC 드라이버 탐색
드라이버는 데이터베이스마다 다르지만 애플리케이션은 같은 JDBC 인터페이스를 사용합니다.
DriverManager.getConnection(...)에 URL을 전달하면, DriverManager가 그 URL을 이해하는 드라이버를 찾아 Connection을 엽니다.
현대 JDBC 드라이버 JAR은 구현을 자동 등록하므로 일반적으로 오래된 예제처럼 Class.forName을 직접 호출하지 않습니다.
package board.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public final class JdbcConnectionProbe {
public DatabaseIdentity connect(
String url,
String username,
String password
) throws SQLException {
var properties = new Properties();
properties.setProperty("user", username);
properties.setProperty("password", password);
properties.setProperty("loginTimeout", "3");
try (var connection = DriverManager.getConnection(url, properties)) {
var metadata = connection.getMetaData();
return new DatabaseIdentity(
metadata.getDatabaseProductName(),
metadata.getDatabaseProductVersion(),
metadata.getDriverName(),
metadata.getDriverVersion(),
connection.getAutoCommit());
}
}
public record DatabaseIdentity(
String product,
String productVersion,
String driver,
String driverVersion,
boolean autoCommit
) {
}
}loginTimeout 속성 이름은 드라이버별 지원 여부가 다를 수 있습니다.
표준 DriverManager.setLoginTimeout도 전역 프로세스 상태라 공유 애플리케이션에서는 신중해야 합니다.
Spring Boot와 연결 풀을 쓸 때는 풀의 연결 타임아웃과 드라이버 네트워크 타임아웃을 각각 설정합니다.
데이터베이스 URL
H2 메모리 URL jdbc:h2:mem:board;DB_CLOSE_DELAY=-1은 프로세스 안의 이름 붙은 데이터베이스를 만들고 마지막 연결 뒤에도 유지하도록 합니다.
PostgreSQL URL은 호스트, 포트, 데이터베이스, TLS 파라미터를 담습니다.
서로 닮아 보여도 옵션 이름과 기본값은 공급자 문서를 확인해야 합니다.
spring.datasource.url=jdbc:h2:mem:board;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.maximum-pool-size=8MODE=PostgreSQL은 H2를 PostgreSQL로 만드는 것이 아닙니다.
일부 문법 호환성을 높일 뿐 트랜잭션 격리, 타입, 인덱스, 쿼리 계획기 차이를 없애지 않습니다.
빠른 리포지토리 테스트에는 유용하지만 실제 데이터베이스 통합 테스트도 유지해야 합니다.
URL을 소스 코드와 로그에 비밀번호까지 붙이지 않습니다.
비밀 정보 관리자 또는 배포 환경에서 인증 정보를 주입하고 구성 덤프에서 가립니다.
URL 파라미터에 토큰이 들어가는 공급자라면 오류 메시지와 메트릭에도 노출되지 않게 합니다.
스키마·연결 메타데이터
회원 게시판의 최소 테이블은 회원별 게시글을 저장합니다.
앞 장에서 만든 실제 게시판 모델을 그대로 저장합니다.
content는 본문 원문이고 길이는 char_length(content)로 파생합니다.
저장소가 길이 숫자만 보관하면 게시글을 다시 표시할 수 없으므로 실습 스키마도 운영 모델과 같은 계약을 사용합니다.
create table members (
id bigint generated by default as identity primary key,
email varchar(255) not null,
password_hash varchar(255) not null,
name varchar(60) not null,
constraint uq_members_email unique (email)
);
create table posts (
id bigint generated by default as identity primary key,
author_id bigint not null,
title varchar(80) not null,
content varchar(5000) not null,
created_on date not null,
version bigint not null default 0,
constraint ck_posts_content
check (char_length(content) between 1 and 5000),
constraint fk_posts_author
foreign key (author_id) references members(id),
constraint uq_posts_author_day_title
unique (author_id, created_on, title)
);애플리케이션 검증과 같은 범위를 DB 제약 조건이 마지막으로 방어합니다.
제약 조건 이름은 예외 변환과 마이그레이션에서 추적 가능하게 안정적으로 정합니다.
식별자 인용과 대소문자 접기 규칙은 데이터베이스마다 달라 원시 SQL 이름 지정 정책을 하나로 고정합니다.
운영 환경 시작에서 매번 schema.sql로 테이블을 생성하기보다 Flyway나 Liquibase 같은 마이그레이션 도구로 버전을 관리합니다.
여러 인스턴스가 동시에 시작할 때 마이그레이션 잠금과 권한을 고려합니다.
이 장의 H2 스키마는 JDBC 동작을 재현하기 위한 작은 실행 환경입니다.
Java 25·H2 연결 확인
package board.jdbc;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class JdbcConnectionProbeTest {
@Test
void H2_driver가_URL을_처리하고_connection을_반환한다()
throws Exception {
var probe = new JdbcConnectionProbe();
var identity = probe.connect(
"jdbc:h2:mem:probe;DB_CLOSE_DELAY=-1",
"sa",
"");
assertThat(identity.product()).isEqualTo("H2");
assertThat(identity.driver()).contains("H2 JDBC Driver");
assertThat(identity.autoCommit()).isTrue();
}
}Java = 25
database product = H2
driver = H2 JDBC Driver
autoCommit = true
connection closed after probe = true
test = PASSED테스트는 제품 버전 문자열 전체에 결합하지 않고 제품과 기본 자동 커밋 계약을 확인합니다.
운영 진단에서는 실제 URL의 호스트를 마스킹한 형태, 드라이버 버전, 풀 이름을 시작 로그 한 번에 남기면 잘못된 데이터베이스 연결을 빠르게 찾을 수 있습니다.
연결 실패 진단
No suitable driver는 클래스 경로에 드라이버가 없거나 URL 접두사를 이해하는 드라이버가 없다는 신호입니다.
인증 실패는 드라이버를 선택하고 서버까지 도달한 뒤 인증 정보가 거절된 경우입니다.
연결 거부와 시간 초과는 호스트·포트·방화벽·DNS·TLS를 확인합니다.
| 실패 신호 | 도달 단계 | 먼저 볼 값 |
|---|---|---|
| 적합한 드라이버 없음 | 로컬 드라이버 선택 | 의존성·URL 접두사 |
| 알 수 없는 호스트 | DNS | 호스트 철자·리졸버 |
| 연결 거부 | TCP 엔드포인트 | 포트·서버 리스너 |
| 인증 실패 | DB 로그인 | 사용자·비밀 정보·역할 |
| TLS 핸드셰이크 | 암호화 전송 | CA·호스트 이름·프로토콜 |
재시도는 원인을 가려서는 안 됩니다.
잘못된 비밀번호를 무한 재시도하면 계정 잠금과 시작 지연을 만듭니다.
일시 네트워크 실패만 제한된 백오프로 재시도하고 구성 오류는 빠르게 실패시킵니다.
연습 문제
애플리케이션 시작에서 기대한 데이터베이스 제품과 스키마 버전을 확인하는 상태 지표를 만드세요.
비밀번호와 전체 URL은 노출하지 않고, 연결 타임아웃과 잘못된 데이터베이스 이름을 서로 다른 실패로 기록하세요.
상태 엔드포인트를 공개에 얼마나 공개할지도 정합니다.
해설 보기
준비 상태는 새 트래픽을 받을 수 있는지, 생존 상태는 프로세스를 재시작해야 하는지 답합니다.
일시 DB 장애를 생존 상태 실패로 만들어 모든 인스턴스를 동시에 재시작하지 않습니다.
package board.jdbc;
public record DatabaseHealth(
Status status,
String product,
int schemaVersion
) {
public enum Status {
READY, DEGRADED, NOT_READY
}
public static DatabaseHealth ready(String product, int version) {
return new DatabaseHealth(Status.READY, product, version);
}
}테스트는 기대 제품, 다른 제품, 연결 타임아웃, 마이그레이션 미완료를 분리합니다.
외부 응답에는 준비 여부만 제공하고 상세 드라이버·스키마 정보는 인증된 운영 엔드포인트나 로그에 제한합니다.
다음 문서에서는 연결 성공 뒤 Connection, PreparedStatement, ResultSet 세 리소스의 소유권과 종료 순서를 다룹니다.