스프링 부트 DB 연결 실패의 주범 driver-class-name 파헤치기

스프링 부트(Spring Boot)로 현대적인 웹 애플리케이션을 개발할 때, 데이터베이스 연동은 프로젝트의 심장과도 같습니다. 우리는 보통 로컬 개발 환경이나 단위 테스트에서는 빠르고 가벼운 H2 인메모리 데이터베이스를 사용하고, 실제 스테이징이나 운영 환경에서는 MySQL, PostgreSQL과 같은 강력한 RDBMS를 사용하는 전략을 흔히 채택합니다. 이 방식은 효율적이지만, 두 개의 다른 데이터베이스 드라이버가 하나의 프로젝트 클래스패스(Classpath)에 공존하게 되면서 예기치 않은 문제와 마주치게 됩니다. "분명 MySQL 접속 정보를 넣었는데 왜 자꾸 H2로 붙으려고 하지?" 와 같은 의문은 바로 이 지점에서 시작됩니다.

이러한 혼란의 중심에는 스프링 부트의 강력한 기능이자 때로는 개발자를 곤경에 빠뜨리는 'DataSource 자동 설정(Auto-configuration)'이 있습니다. 스프링 부트는 클래스패스에 존재하는 JDBC 드라이버를 감지하여 '알아서' 데이터소스를 구성해주는 마법을 부립니다. 하지만 H2 드라이버와 MySQL 드라이버가 동시에 존재할 때, 이 마법은 어떤 드라이버를 선택해야 할지 몰라 망설이거나, 개발자의 의도와는 다른 선택(주로 내장 데이터베이스 우선)을 하게 됩니다. 이 모호성을 해결하고 스프링 부트에게 명확한 지침을 내리는 열쇠가 바로 spring.datasource.driver-class-name 속성입니다.

본 글은 단순히 `driver-class-name` 속성을 설정하는 방법을 넘어, 왜 이 설정이 필요한지에 대한 근본적인 이유를 'H2와 MySQL 동시 사용'이라는 실용적인 시나리오를 통해 파헤칩니다. 스프링 부트의 DataSource 자동 설정 메커니즘이 내부적으로 어떻게 동작하는지 심층 분석하고, MySQL을 포함한 주요 데이터베이스별 정확한 드라이버 클래스 이름과 의존성 설정, 그리고 실무에서 마주칠 수 있는 각종 오류 상황에 대한 해결책까지 총망라하여 제공할 것입니다. 이 글을 통해 여러분은 더 이상 데이터베이스 드라이버 문제 앞에서 당황하지 않고, 어떤 복잡한 환경에서도 자신감 있게 데이터소스를 제어할 수 있는 개발자로 거듭나게 될 것입니다.

1. 스프링 부트의 마법: DataSource 자동 설정의 이해

스프링 부트 애플리케이션에서 데이터베이스와 통신하려면, 가장 먼저 DataSource 객체를 스프링 컨테이너에 빈(Bean)으로 등록해야 합니다. DataSource는 이름 그대로 '데이터의 원천'으로, 데이터베이스와의 물리적인 커넥션을 획득하고 관리하는 역할을 추상화한 자바 표준 인터페이스입니다. 단순히 커넥션을 맺고 끊는 것을 넘어, 효율적인 리소스 관리를 위한 커넥션 풀링(Connection Pooling)과 같은 필수적인 기능들을 내포하고 있습니다.

과거 순수 스프링 프레임워크 시절에는 이 DataSource를 설정하는 것이 꽤나 번거로운 작업이었습니다. 개발자는 XML 설정 파일이나 @Configuration 어노테이션이 붙은 자바 클래스를 통해 직접 DataSource 구현체를 선택하고, 드라이버 클래스 이름, DB 접속 URL, 사용자 계정 정보 등을 일일이 코드로 명시해야 했습니다.

// 과거 스프링 프레임워크에서의 수동 DataSource 설정 예시
@Configuration
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() {
        // DriverManagerDataSource는 스프링이 제공하는 가장 단순한 DataSource 구현체.
        // 실제 운영 환경에서는 커넥션 풀링 기능이 있는 HikariDataSource 등을 사용해야 했다.
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("user");
        dataSource.setPassword("password");
        return dataSource;
    }
}

하지만 스프링 부트는 spring-boot-autoconfigure라는 강력한 모듈을 통해 이러한 상용구 코드를 완전히 제거했습니다. 이 모듈 안에 포함된 DataSourceAutoConfiguration 클래스가 바로 데이터소스 설정 자동화의 마법을 부리는 주인공입니다.

1.1. 클래스패스(Classpath) 기반의 자동 감지 메커니즘

DataSourceAutoConfiguration의 핵심 동작 원리는 매우 직관적이면서도 효과적인 클래스패스 스캐닝(Classpath Scanning)입니다. 애플리케이션이 구동되는 시점에, 스프링 부트는 프로젝트가 의존하고 있는 모든 라이브러리(JAR 파일들)의 목록, 즉 클래스패스를 샅샅이 훑어봅니다.

예를 들어, 우리가 Gradle의 `build.gradle.kts` 파일에 MySQL 데이터베이스 연동을 위한 의존성을 추가했다고 가정해 봅시다.

Gradle (`build.gradle.kts`) 의존성 추가 예시:

dependencies {
    // spring-boot-starter-data-jpa는 내부에 spring-boot-starter-jdbc를 포함하고 있어
    // 데이터베이스 연동에 필요한 기본적인 라이브러리들을 가져온다.
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    
    // MySQL 데이터베이스와 통신하기 위한 공식 JDBC 드라이버.
    // 'runtimeOnly' 스코프는 이 라이브러리가 컴파일 시점에는 필요 없지만,
    // 런타임(실행 시점)에는 반드시 필요함을 의미한다.
    runtimeOnly("com.mysql:mysql-connector-j") 
}

위와 같이 `mysql-connector-j` 의존성을 추가하는 행위는, 해당 라이브러리의 JAR 파일이 우리 애플리케이션의 클래스패스에 포함된다는 것을 의미합니다. 이 JAR 파일 안에는 JDBC 명세를 구현한 핵심 클래스인 com.mysql.cj.jdbc.Driver가 들어있습니다. 스프링 부트의 `DataSourceAutoConfiguration`은 바로 이 특정 클래스의 존재 유무를 감지하는 것입니다. 마치 탐정처럼 "클래스패스에 MySQL 드라이버 클래스가 있군. 이 프로젝트는 MySQL 데이터베이스를 사용하려는 의도가 명백하구나!"라고 추론하는 과정입니다.

1.2. 추론을 통한 지능적인 자동 설정

성공적으로 드라이버의 존재를 감지하고 나면, 스프링 부트의 자동 설정은 한 걸음 더 나아가 다음과 같은 추가적인 작업들을 일사천리로 진행합니다.

  1. 최적의 커넥션 풀(Connection Pool) 선택: 현대적인 웹 애플리케이션에서 커넥션 풀은 선택이 아닌 필수입니다. 매번 데이터베이스 요청이 있을 때마다 커넥션을 새로 생성하고 해제하는 것은 엄청난 비용을 유발하기 때문입니다. 스프링 부트 2.0 버전 이상부터는 성능과 안정성 면에서 가장 뛰어난 평가를 받는 HikariCP를 기본 커넥션 풀로 채택했습니다. 클래스패스에 HikariCP 라이브러리가 존재하면(spring-boot-starter-jdbc에 기본 포함), 스프링 부트는 자동으로 HikariDataSource를 생성하여 DataSource 빈으로 등록합니다.
  2. 핵심 JDBC 속성 추론: 개발자가 application.propertiesapplication.yml 파일에 `spring.datasource.driver-class-name`을 명시적으로 설정하지 않았다면, 앞에서 클래스패스 스캔을 통해 감지한 드라이버(예: `com.mysql.cj.jdbc.Driver`)를 기본값으로 사용합니다. 더 나아가, `spring.datasource.url` 정보만으로도 드라이버를 유추하는 스마트함도 갖추고 있습니다. 예를 들어 URL이 `jdbc:mysql://`로 시작한다면, 클래스패스에서 MySQL 드라이버를 찾아 연결을 시도합니다.

이 모든 과정 덕분에, 개발자는 그저 `application.yml` 파일에 다음과 같이 데이터베이스 접속에 필요한 최소한의 정보만 입력하면 모든 준비가 끝나게 됩니다.

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/my_database?serverTimezone=UTC&characterEncoding=utf8
    username: myuser
    password: mypassword
DataSource 자동 설정의 결과
위 설정만으로도 스프링 부트는 내부적으로 다음과 같은 복잡한 과정을 자동으로 처리합니다.
  • `com.mysql.cj.jdbc.Driver`를 JDBC 드라이버로 사용하는 `DataSource`를 구성합니다.
  • HikariCP를 사용하여 커넥션 풀이 적용된 `HikariDataSource` 인스턴스를 생성합니다.
  • 이 `HikariDataSource` 객체를 스프링 컨테이너에 `dataSource`라는 이름의 빈으로 등록합니다.
  • 이제 다른 컴포넌트(예: `JdbcTemplate`, `JPA EntityManager`)에서 이 `dataSource` 빈을 주입받아 즉시 사용할 수 있습니다.

이것이 바로 우리가 일상적으로 경험하는 스프링 부트의 '마법'이며, 개발자가 비즈니스 로직에만 집중할 수 있도록 도와주는 강력한 생산성의 원천입니다.

2. 자동 설정의 한계와 `driver-class-name` 수동 설정의 필요성

스프링 부트의 DataSource 자동 설정은 매우 편리하지만, 모든 상황을 해결해주는 만병통치약은 아닙니다. 실무 개발 환경은 교과서처럼 단순하지 않으며, 복잡한 요구사항이나 예외적인 환경과 마주했을 때 자동 설정의 '추론' 능력은 한계에 부딪히고, 때로는 개발자의 의도와 다른 방향으로 흘러가 문제를 일으키기도 합니다. 바로 이런 순간에 우리는 자동화의 편리함에서 한 걸음 물러나 spring.datasource.driver-class-name 속성을 통해 우리의 의도를 명확하게 전달해야 합니다.

2.1. 수동 설정이 반드시 필요한 대표적인 시나리오

  • 클래스패스에 두 개 이상의 JDBC 드라이버가 존재할 경우 (H2 + MySQL)

    서론에서 언급했듯, 가장 빈번하게 발생하는 시나리오입니다. 로컬 개발 및 테스트 환경의 생산성을 위해 H2 인메모리 데이터베이스를 사용하고, 통합 테스트 및 운영 환경에서는 MySQL이나 PostgreSQL을 사용하는 것은 매우 일반적인 개발 패턴입니다. 이를 위해 `build.gradle` 파일에는 두 데이터베이스의 드라이버 의존성이 모두 포함될 수 있습니다.

    // build.gradle.kts
    dependencies {
        // ...
        runtimeOnly("com.mysql:mysql-connector-j") // 운영 DB용
        runtimeOnly("com.h2database:h2")           // 테스트 및 로컬 개발용
    }
    

    이 경우, 스프링 부트는 애플리케이션을 시작할 때 `com.mysql.cj.jdbc.Driver`와 `org.h2.Driver`를 모두 발견하게 됩니다. 이때 스프링 부트는 어떤 드라이버를 사용해야 할지 스스로 결정하지 못하는 모호성(ambiguity) 상태에 빠집니다. 대부분의 경우, 스프링 부트는 H2, HSQL, Derby와 같은 내장(embedded) 데이터베이스에 더 높은 우선순위를 부여하도록 설계되어 있습니다. 따라서 application.properties에 MySQL 접속 정보를 명시했음에도 불구하고, 스프링 부트가 H2 데이터베이스에 연결을 시도하며 "No suitable driver found" 오류나 의도치 않은 동작을 유발하는 원인이 됩니다.

    이 문제를 해결하는 가장 확실한 방법은 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 와 같이 사용할 드라이버를 명시적으로 지정하여 스프링 부트의 추론 과정을 생략하게 만드는 것입니다.
  • 특정 버전의 드라이버를 강제로 사용해야 할 경우

    데이터베이스 드라이버도 소프트웨어이므로 버전이 존재하며, 때로는 버전 간에 중대한 변경사항이 발생합니다. 대표적인 예가 MySQL의 Connector/J 드라이버입니다. 5.x 버전과 8.x 버전은 드라이버의 클래스 이름 자체가 다릅니다. (com.mysql.jdbc.Driver vs com.mysql.cj.jdbc.Driver) 만약 프로젝트가 오래된 레거시 데이터베이스 서버와 연동해야 하거나, 특정 데이터베이스 버전에서만 안정적으로 동작하는 구형 드라이버를 반드시 사용해야 한다면, 자동 설정에 의존해서는 안 됩니다. 명시적으로 구버전 드라이버의 클래스 이름을 지정해주어야만 호환성 문제를 피할 수 있습니다.

  • 스프링 부트가 공식적으로 지원하지 않는 드라이버 사용

    Tibero, Cubrid와 같은 국산 데이터베이스나, Presto, Trino와 같은 분산 SQL 쿼리 엔진, 또는 덜 대중적인 데이터베이스들은 스프링 부트의 DataSourceAutoConfiguration에 해당 데이터베이스를 위한 자동 감지 로직이 포함되어 있지 않을 가능성이 높습니다. 이러한 비표준 데이터베이스를 사용하기 위해서는 당연하게도 driver-class-nameurl을 수동으로 정확하게 설정해주어야 합니다.

  • 설정의 명확성 확보 및 팀 컨벤션

    기술적인 제약사항이 없더라도, 코드와 설정의 명확성을 위해 의도적으로 `driver-class-name`을 명시하는 경우도 많습니다. 이는 "마법"에 의존하기보다 설정 파일만 보아도 이 애플리케이션이 어떤 데이터베이스 드라이버를 사용하여 통신하는지 누구나 명확하게 파악할 수 있도록 하기 위함입니다. 이는 특히 여러 개발자가 협업하는 대규모 프로젝트에서 유지보수성을 높이는 매우 좋은 습관이 될 수 있습니다.

2.2. `spring.datasource.driver-class-name` 설정 방법

설정 방법은 매우 간단합니다. 프로젝트의 중앙 설정 파일인 `application.properties` 또는 `application.yml`에 해당 속성을 한 줄 추가하면 됩니다.

application.properties 사용 시:

spring.datasource.url=jdbc:mysql://localhost:3306/my_database
spring.datasource.username=myuser
spring.datasource.password=mypassword
# 자동 설정을 무시하고, 명시적으로 MySQL 8+ 드라이버 클래스를 지정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

application.yml 사용 시:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/my_database
    username: myuser
    password: mypassword
    # 자동 설정을 무시하고, 명시적으로 MySQL 8+ 드라이버 클래스를 지정
    driver-class-name: com.mysql.cj.jdbc.Driver

이렇게 설정하면 스프링 부트의 `DataSourceAutoConfiguration`은 클래스패스를 스캔하여 드라이버를 추론하는 복잡한 과정을 즉시 건너뛰고, 여기에 명시된 값을 최우선으로 사용하여 DataSource를 구성합니다. 이것이 바로 우리가 스프링 부트의 자동 설정 마법을 제어하는 가장 기본적이면서도 강력한 스위치입니다.

3. 주요 데이터베이스별 JDBC 드라이버 설정 총정리

이론적인 배경을 이해했으니 이제 실전으로 들어갈 차례입니다. 실무 개발에서 가장 자주 사용되는 주요 데이터베이스들의 표준 의존성 추가 방법, 정확한 `driver-class-name`, 그리고 일반적인 설정 예시를 한눈에 볼 수 있도록 자세히 정리했습니다. 이 섹션을 북마크해두시면 향후 새로운 데이터베이스를 연동할 때 유용한 참고자료가 될 것입니다.

아래의 모든 예시는 Gradle의 Kotlin DSL (build.gradle.kts)과 application.yml 파일을 기준으로 작성되었습니다. Maven(pom.xml)이나 application.properties를 사용하시는 경우, 형식에 맞게 적절히 변환하여 적용하시면 됩니다.

3.1. MySQL

전 세계적으로 가장 널리 사용되는 오픈소스 관계형 데이터베이스입니다. MySQL 연동 시 가장 중요한 점은 사용하는 서버 버전에 맞는 드라이버를 선택하는 것이며, 특히 Connector/J 5.x 버전과 8.x 버전의 드라이버 클래스 이름이 다르다는 점을 반드시 기억해야 합니다.

항목 설명
의존성 (Gradle) runtimeOnly("com.mysql:mysql-connector-j")
Driver Class (8.x+, 권장) com.mysql.cj.jdbc.Driver
Driver Class (5.x, 레거시) com.mysql.jdbc.Driver
JDBC URL 형식 jdbc:mysql://[HOST]:[PORT]/[DATABASE_NAME]

상세 설명 및 설정 예시:

최신 스프링 부트 프로젝트에서는 MySQL 8.0 이상 버전에 맞춰진 com.mysql.cj.jdbc.Driver를 사용하는 것이 표준입니다. 패키지 경로에 포함된 `cj`는 'Core Java'를 의미하며, 기존 드라이버와 아키텍처가 다름을 나타냅니다. 구버전 드라이버는 더 이상 사용되지 않으므로 가급적 사용을 피해야 합니다.

# application.yml for MySQL 8+
spring:
  datasource:
    # 드라이버 클래스를 명시적으로 지정하여 혼동을 방지
    driver-class-name: com.mysql.cj.jdbc.Driver
    # URL에 타임존과 문자 인코딩 설정은 예기치 않은 오류를 방지하기 위해 매우 중요
    url: jdbc:mysql://localhost:3306/my_app_db?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: db_user
    password: db_password
주의: serverTimezone 파라미터
MySQL Connector/J 6.x 이상부터는 서버의 타임존을 명시적으로 지정하지 않으면 에러가 발생할 수 있습니다. serverTimezone=UTC 또는 serverTimezone=Asia/Seoul 과 같이 서버 환경에 맞는 타임존을 URL에 파라미터로 추가하는 것을 강력히 권장합니다. 이는 개발자 PC와 데이터베이스 서버 간의 시간대 불일치로 인한 TIMESTAMP, DATETIME 타입 데이터 문제를 예방하는 핵심적인 설정입니다.

3.2. MariaDB

MySQL의 초기 핵심 개발자들이 독립하여 만든 오픈소스 데이터베이스로, MySQL과 높은 호환성을 자랑합니다. 드라이버 의존성과 URL 프로토콜이 MySQL과 다르다는 점에 유의해야 합니다.

항목 설명
의존성 (Gradle) runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
Driver Class org.mariadb.jdbc.Driver
JDBC URL 형식 jdbc:mariadb://[HOST]:[PORT]/[DATABASE_NAME]

설정 예시 (`application.yml`):

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    # MariaDB는 공식적으로 'jdbc:mariadb://' 프로토콜을 사용합니다.
    url: jdbc:mariadb://localhost:3306/my_app_db
    username: db_user
    password: db_password
MariaDB는 MySQL과의 호환성을 위해 `jdbc:mysql://` URL도 인식하는 경우가 많지만, 공식 드라이버를 사용하는 만큼 `jdbc:mariadb://` 프로토콜을 사용하여 설정을 명확하게 하는 것이 좋습니다.

3.3. PostgreSQL

강력한 기능, 높은 수준의 SQL 표준 준수, 뛰어난 확장성으로 최근 많은 사랑을 받고 있는 오픈소스 객체-관계형 데이터베이스 시스템입니다.

항목 설명
의존성 (Gradle) runtimeOnly("org.postgresql:postgresql")
Driver Class org.postgresql.Driver
JDBC URL 형식 jdbc:postgresql://[HOST]:[PORT]/[DATABASE_NAME]

설정 예시 (`application.yml`):

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/my_app_db
    username: db_user
    password: db_password

3.4. Oracle Database

엔터프라이즈 환경에서 독보적인 위치를 차지하고 있는 상용 데이터베이스입니다. Oracle JDBC 드라이버(OJDBC)는 이름에 포함된 숫자(예: `ojdbc8`, `ojdbc11`)가 대상 JDK 버전과 밀접한 관련이 있으므로, 프로젝트의 JDK 버전에 맞는 의존성을 선택하는 것이 매우 중요합니다.

항목 설명
의존성 (JDK 11+ 기준) runtimeOnly("com.oracle.database.jdbc:ojdbc11")
Driver Class oracle.jdbc.OracleDriver (또는 oracle.jdbc.driver.OracleDriver)
JDBC URL 형식 jdbc:oracle:thin:@//[HOST]:[PORT]/[SERVICE_NAME_OR_SID]

설정 예시 (`application.yml`):

spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    # Oracle의 표준 thin-style URL 형식입니다.
    # 마지막 부분은 서비스 이름(Service Name) 또는 SID(System ID)가 될 수 있습니다.
    url: jdbc:oracle:thin:@//localhost:1521/XEPDB1
    username: system
    password: oracle_password

3.5. Microsoft SQL Server (MSSQL)

Microsoft가 개발한 Windows 생태계의 대표적인 관계형 데이터베이스 서버입니다. 최신 JDBC 드라이버는 보안 연결을 기본으로 하므로 관련 옵션 설정에 주의가 필요합니다.

항목 설명
의존성 (Gradle) runtimeOnly("com.microsoft.sqlserver:mssql-jdbc")
Driver Class com.microsoft.sqlserver.jdbc.SQLServerDriver
JDBC URL 형식 jdbc:sqlserver://[HOST]:[PORT];databaseName=[DB_NAME]

설정 예시 (`application.yml`):

spring:
  datasource:
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    # 최신 드라이버는 SSL 암호화를 기본으로 요구합니다.
    # 개발 환경에서는 아래 옵션으로 인증서 검증을 건너뛸 수 있습니다.
    url: jdbc:sqlserver://localhost:1433;databaseName=my_app_db;encrypt=true;trustServerCertificate=true;
    username: sa
    password: mssql_password

참고: 운영 환경에서는 `trustServerCertificate=true` 옵션 대신, 실제 서버 인증서를 클라이언트(애플리케이션)의 TrustStore에 추가하여 안전한 SSL/TLS 통신을 구성하는 것이 보안상 올바른 방법입니다.

3.6. H2 (인메모리 데이터베이스)

로컬 개발, 단위 테스트, 통합 테스트, CI/CD 파이프라인 등에서 매우 유용하게 사용되는 순수 자바 기반의 경량 데이터베이스입니다. 별도의 설치 없이 의존성 추가만으로 즉시 사용할 수 있는 점이 가장 큰 장점입니다.

항목 설명
의존성 (Gradle) runtimeOnly("com.h2database:h2")
Driver Class org.h2.Driver
JDBC URL (인메모리) jdbc:h2:mem:[DATABASE_NAME]
JDBC URL (파일 기반) jdbc:h2:file:[PATH_TO_DB_FILE]

설정 예시 (`application.yml`):
H2는 별도 설정이 없으면 스프링 부트가 자동으로 인메모리 모드로 실행하지만, 개발 편의를 위해 웹 콘솔을 활성화하고 명시적으로 설정하는 것이 좋습니다.

spring:
  # H2는 보통 다른 DB와 함께 사용되므로 명시적 설정이 권장됩니다.
  datasource:
    driver-class-name: org.h2.Driver
    # 'testdb'라는 이름의 인메모리 데이터베이스를 사용합니다.
    # 애플리케이션이 종료되면 데이터는 모두 사라집니다.
    url: jdbc:h2:mem:testdb
    username: sa
    # H2의 기본 비밀번호는 보통 비어있습니다.
    password: 
  # H2 데이터베이스 상태를 웹 브라우저로 확인할 수 있는 콘솔 기능 활성화
  h2:
    console:
      enabled: true
      # 접속 경로 설정 (예: http://localhost:8080/h2-console)
      path: /h2-console
위 설정과 함께 애플리케이션을 실행하면 http://localhost:8080/h2-console 경로로 접속하여 쿼리를 실행하고 테이블 데이터를 직접 조회하는 등 매우 편리하게 데이터베이스 상태를 확인할 수 있어 개발 생산성을 크게 향상시킵니다.

4. 심층 분석: 스프링 부트는 내부적으로 어떻게 드라이버를 결정하는가?

그렇다면 우리가 `driver-class-name`을 설정하지 않았을 때, 스프링 부트의 자동 설정은 어떤 로직을 통해 그 많은 데이터베이스 드라이버 중에서 정확한 하나를 찾아내는 것일까요? 그 비밀의 열쇠는 spring-boot-autoconfigure.jar 라이브러리 내부에 존재하는 org.springframework.boot.jdbc.DatabaseDriver 라는 `enum`(열거형) 클래스에 숨겨져 있습니다.

DatabaseDriver enum은 스프링 부트가 인식하는 주요 데이터베이스들에 대한 메타데이터를 미리 정의해 둔 일종의 카탈로그입니다. 각 enum 상수는 다음과 같은 핵심 정보를 담고 있습니다.

  • 데이터베이스의 종류 (예: `MYSQL`, `POSTGRESQL`, `H2`)
  • 기본 드라이버 클래스 이름 (예: `com.mysql.cj.jdbc.Driver`)
  • 유효성 검증을 위한 간단한 쿼리 (Validation Query, 예: `SELECT 1`)
  • JDBC URL 접두사 (예: `jdbc:mysql:`)
  • 드라이버의 존재를 확인하기 위해 사용할 대체 클래스 이름 (드라이버 클래스 자체가 public이 아닌 경우를 대비한 안전장치)

DataSourceAutoConfiguration이 실제로 드라이버를 결정하는 로직은 매우 체계적인 우선순위에 따라 진행됩니다. 그 순서는 다음과 같습니다.

  1. 1순위: 개발자의 명시적 설정 확인 (`spring.datasource.driver-class-name`)
    가장 먼저, 그리고 무조건적으로 `application.properties`나 `application.yml` 파일에 `spring.datasource.driver-class-name` 속성이 설정되어 있는지 확인합니다. 만약 이 값이 존재한다면, 스프링 부트는 더 이상 어떠한 추론 과정도 거치지 않고 이 값을 그대로 사용하여 DataSource를 설정합니다. 이것이 수동 설정이 항상 자동 설정에 우선하는 이유이며, 우리가 의도를 강제할 수 있는 가장 확실한 방법입니다.
  2. 2순위: JDBC URL 기반 추론 (`spring.datasource.url`)
    `driver-class-name` 설정이 없고 `spring.datasource.url` 속성만 존재하는 경우, 스프링 부트는 URL의 내용을 분석합니다. URL의 프로토콜(접두사)을 보고 `DatabaseDriver` enum에 정의된 정보와 매칭을 시도합니다.
    • URL이 `jdbc:postgresql://...`로 시작하면? -> `DatabaseDriver.POSTGRESQL`을 찾아 `org.postgresql.Driver`를 선택합니다.
    • URL이 `jdbc:mariadb://...`로 시작하면? -> `DatabaseDriver.MARIADB`를 찾아 `org.mariadb.jdbc.Driver`를 선택합니다.
    이 방식은 URL만으로도 충분히 데이터베이스 종류를 유추할 수 있다는 점을 이용한 스마트한 기능입니다.
  3. 3순위: 클래스패스 기반 탐색 (가장 일반적인 자동 설정의 핵심)
    만약 `driver-class-name`과 `url` 설정이 모두 없다면, 스프링 부트는 최후의 수단으로 클래스패스 탐색을 시작합니다. 이 과정은 `DatabaseDriver` enum에 정의된 순서대로 진행됩니다.
    (내부적으로는 `org.springframework.util.ClassUtils.isPresent(className, classLoader)` 메소드를 사용하여 특정 클래스가 로드 가능한지 확인합니다.)
    `DatabaseDriver` enum의 탐색 순서는 일반적으로 내장 데이터베이스가 우선됩니다. 예를 들어, 탐색 순서가 H2 -> HSQL -> Derby -> MySQL -> PostgreSQL 이라면...
    • Step 1: 클래스패스에 `org.h2.Driver` 클래스가 있는지 확인합니다. 만약 존재한다면, 데이터베이스를 H2로 최종 결정하고 탐색을 즉시 중단합니다.
    • Step 2: H2 드라이버가 없다면, 다음 순서인 HSQL 드라이버(`org.hsqldb.jdbc.JDBCDriver`)가 있는지 확인합니다.
    • Step 3, 4, ...: 앞선 드라이버들이 없을 때, 비로소 `com.mysql.cj.jdbc.Driver` 클래스가 있는지 확인합니다. 발견되면 MySQL로 결정하고 탐색을 중단합니다.
    • 이 과정을 반복하여 `DatabaseDriver` enum 목록에서 처음으로 발견되는 드라이버를 최종 선택합니다.
    이것이 바로 클래스패스에 H2와 MySQL 드라이버가 함께 있을 때, 명시적인 설정을 하지 않으면 H2가 우선적으로 선택되는 이유입니다. 이 내부 동작 원리를 정확히 이해하면, 왜 의존성 추가만으로 DB 연동이 가능한지, 그리고 왜 가끔씩 의도와 다르게 동작하여 우리를 당황하게 하는지를 명확하게 파악하고 대처할 수 있게 됩니다.

5. 일반적인 오류와 해결 가이드 (Troubleshooting)

JDBC 드라이버 설정은 간단해 보이지만, 사소한 실수로 인해 애플리케이션 구동 실패로 이어지는 경우가 많습니다. 개발자들이 가장 자주 마주치는 대표적인 오류들과 그 원인, 그리고 명확한 해결 방법을 정리했습니다.

오류 1: `java.sql.SQLException: No suitable driver found for [JDBC_URL]`

이 메시지는 JDBC 드라이버 관련 오류 중 가장 대표적이고 포괄적인 오류입니다. 자바의 `DriverManager`가 주어진 JDBC URL을 처리할 수 있는 적절한 드라이버를 클래스패스에서 찾지 못했다는 명백한 신호입니다. 원인은 보통 다음 중 하나입니다.

  • 원인 1: JDBC 드라이버 의존성 누락

    가장 흔한 원인입니다. `build.gradle` 이나 `pom.xml`에 사용하려는 데이터베이스의 JDBC 드라이버 의존성을 추가하는 것을 잊은 경우입니다.

    해결: 3장에서 정리한 내용을 다시 한번 확인하여, 사용하려는 데이터베이스에 맞는 의존성을 정확하게 추가했는지 검토하세요. 의존성을 추가한 후에는 Gradle/Maven 프로젝트를 새로고침하여 라이브러리가 제대로 다운로드되었는지 확인해야 합니다.

  • 원인 2: 잘못된 JDBC URL 형식 또는 오타

    URL의 프로토콜 부분이 틀린 경우입니다. 예를 들어, `jdbc:mysql://`을 `jdb:mysql://`이나 `jdbc:mysgl://`로 잘못 입력하는 사소한 오타가 원인이 될 수 있습니다. 또한 데이터베이스마다 요구하는 URL 형식이 다르므로, 이를 준수하지 않았을 때도 발생합니다.

    해결: `spring.datasource.url` 값이 데이터베이스별 표준 형식에 맞는지 꼼꼼히 확인하세요. 특히 콜론(:), 슬래시(/)의 개수와 위치를 주의 깊게 봐야 합니다.

  • 원인 3: 드라이버 클래스 수동 설정 오류

    `spring.datasource.driver-class-name`을 직접 설정했지만, 클래스 이름을 잘못 입력한 경우입니다. 패키지 경로를 잘못 적거나, 클래스 이름에 오타가 있거나, 대소문자를 틀리게 적는 실수가 여기에 해당합니다. (예: `com.mysql.cj.jdbc.Driver`를 `com.mysql.cj.jdbc.driver`로 적는 경우)

    해결: 3장에서 제공하는 정확한 드라이버 클래스 이름을 그대로 복사하여 붙여넣기하는 것이 가장 안전합니다.

오류 2: `java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver` (또는 다른 드라이버 클래스)

이 오류는 "No suitable driver found"보다 훨씬 더 구체적이고 명확한 원인을 알려줍니다. JVM(자바 가상 머신)이 클래스로더를 통해 지정된 FQCN(Full Qualified Class Name)에 해당하는 .class 파일을 클래스패스 상에서 아예 찾지 못했다는 의미입니다.

  • 원인 1: 의존성 누락 또는 잘못된 스코프(Scope)

    의존성이 없는 것은 물론, 의존성의 스코프가 잘못 지정된 경우에도 발생할 수 있습니다. 예를 들어, Maven/Gradle에서 `test` 스코프(testImplementation)로 JDBC 드라이버 의존성을 추가했다면, 해당 라이브러리는 ./gradlew test와 같이 테스트 코드를 실행할 때만 클래스패스에 포함됩니다. 실제 애플리케이션을 실행(bootRun)할 때는 클래스패스에서 제외되므로 `ClassNotFoundException`이 발생합니다.

    해결: JDBC 드라이버는 컴파일 시점에는 필요 없지만 애플리케이션 실행 시점에는 반드시 필요합니다. 따라서 의존성 스코프를 runtimeOnly로 설정하는 것이 가장 정확하고 권장되는 방법입니다. `implementation` (Maven의 `compile` 포함)으로 설정해도 동작은 하지만, 불필요하게 컴파일 시점의 클래스패스까지 오염시키므로 `runtimeOnly`가 의미상 더 적합합니다.

  • 원인 2: 버전 불일치로 인한 클래스 이름 변경

    MySQL 5.x 버전을 사용하던 프로젝트를 8.x 버전으로 마이그레이션하면서, `driver-class-name`을 예전의 `com.mysql.jdbc.Driver`로 그대로 둔 채 의존성만 최신 `mysql-connector-j`로 변경한 경우입니다. 최신 드라이버 JAR 파일에는 `com.mysql.jdbc.Driver` 클래스가 더 이상 존재하지 않으므로, 당연히 클래스를 찾을 수 없다는 오류가 발생합니다.

    해결: 프로젝트에 추가한 JDBC 드라이버 의존성의 버전과, `application.properties`에 설정한 `driver-class-name`이 서로 호환되는 올바른 조합인지 반드시 확인해야 합니다. 의심스러울 때는 항상 해당 드라이버의 공식 문서를 참조하는 것이 좋습니다.

마치며

스프링 부트의 세계에서 데이터베이스 연동은 `spring.datasource.driver-class-name`이라는 하나의 속성을 중심으로 자동과 수동의 영역이 명확하게 나뉩니다. 대부분의 단순한 시나리오에서 빛을 발하는 스프링 부트의 자동 설정은 클래스패스 스캐닝과 사전 정의된 규칙을 기반으로 동작하는 매우 영리한 메커니즘입니다. 이 덕분에 개발자는 의존성 추가와 최소한의 접속 정보 입력만으로도 강력한 데이터베이스 연동 기능을 즉시 활용할 수 있습니다.

하지만 진정한 전문성은 이 '마법'이 통하지 않는 순간에 드러납니다. 자동화의 편리함 이면에 숨겨진 동작 원리를 이해하고, 그 한계를 명확히 인지하는 것은 더 높은 수준의 개발자로 성장하기 위한 필수적인 역량입니다. H2와 MySQL을 동시에 사용하는 것과 같은 복잡한 상황에서 언제 수동 설정으로 전환해야 하는지 판단하고, 각 데이터베이스에 맞는 정확한 드라이버 클래스 이름과 URL 형식을 자신 있게 사용하는 능력은, 예기치 않은 문제를 신속하게 해결하고 안정적이며 예측 가능한 애플리케이션을 구축하는 데 핵심적인 역할을 합니다.

본문에서 다룬 DataSource 자동 설정의 원리, 수동 설정이 필요한 시나리오, 그리고 주요 데이터베이스별 상세 설정 가이드가 앞으로 여러분의 스프링 부트 프로젝트에서 데이터베이스를 다룰 때 든든한 길잡이가 되기를 바랍니다. 마지막으로, 기술은 끊임없이 변화하므로 가장 정확하고 최신의 정보는 항상 사용하려는 특정 JDBC 드라이버의 공식 문서를 직접 참조하는 것이 최고의 습관이라는 점을 기억하며 이 글을 마칩니다.

Post a Comment