JDBC 지원 — 임시 데이터베이스를 컨테이너로 띄우기

JDBC 지원 — 임시 데이터베이스를 컨테이너로 띄우기

테스트에서 진짜 DB와 같은 동작을 확인하고 싶은데 개발자 머신마다 DB를 설치해서 관리하는 건 큰 부담이에요. Testcontainers의 JDBC 지원을 쓰면 JDBC URL만 살짝 고쳐서 임시 데이터베이스를 컨테이너로 띄울 수 있어요. 이 페이지에서는 두 가지 방식, 즉 JDBC URL을 바꾸는 방식과 @Rule/@ClassRule을 쓰는 방식을 살펴볼게요.

출처: 공식문서 — JDBC support

본문

임시 데이터베이스는 두 가지 방법 중 하나로 얻을 수 있어요.

  • 특별히 수정된 JDBC URL 사용: JDBC URL 문자열을 아주 간단히 수정하면, 애플리케이션 코드는 수정할 필요 없이 일회용 대체 데이터베이스를 Testcontainers가 제공해 줘요.
  • JUnit @Rule/@ClassRule: 이 모드는 테스트 전에 컨테이너 안에서 데이터베이스를 시작하고 테스트 후에 내려줘요.

JDBC URL 스킴으로 컨테이너 DB 시작하기

Testcontainers와 알맞은 JDBC 드라이버가 클래스패스에 있기만 하면, 일반 JDBC 연결 URL을 수정해 애플리케이션이 시작될 때마다 새로운 컨테이너화된 DB 인스턴스를 얻을 수 있어요.

참고:

  • 이게 동작하려면 TC가 런타임에 애플리케이션 클래스패스에 있어야 해요
  • Spring Boot(버전 2.3.0 이전)에서는 드라이버를 수동으로 지정해야 해요 spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver

원래 URL: jdbc:mysql://localhost:3306/databasename

다음처럼 jdbc: 뒤에 tc:를 넣어요. 호스트 이름, 포트, 데이터베이스 이름은 무시되므로 그대로 두거나 아무 값으로 바꿔도 돼요.

참고로, 앞으로는 ///(호스트 없는 URI)를 써서 host:port 쌍이 중요하지 않음을 강조할게요. Testcontainers 관점에서 jdbc:mysql:8.0.36://localhost:3306/databasenamejdbc:mysql:8.0.36:///databasename은 같은 URI예요.

주의: JDBC URL 지원을 쓰고 있다면 컨테이너 인스턴스를 직접 만들 필요가 없어요 — Testcontainers가 자동으로 만들어 줘요.

JDBC URL 예시

ClickHouse 사용

jdbc:tc:clickhouse:18.10.3:///databasename

CockroachDB 사용

jdbc:tc:cockroach:v21.2.3:///databasename

CrateDB 사용

jdbc:tc:cratedb:5.2.3:///databasename

DB2 사용

jdbc:tc:db2:11.5.0.0a:///databasename

MariaDB 사용

jdbc:tc:mariadb:10.3.39:///databasename

MySQL 사용

jdbc:tc:mysql:8.0.36:///databasename

MSSQL Server 사용

jdbc:tc:sqlserver:2017-CU12:///databasename

OceanBase 사용

jdbc:tc:oceanbasece:4.2.1-lts:///databasename

Oracle 사용

jdbc:tc:oracle:21-slim-faststart:///databasename

PostGIS 사용

jdbc:tc:postgis:9.6-2.5:///databasename

PostgreSQL 사용

jdbc:tc:postgresql:9.6.8:///databasename

QuestDB 사용

jdbc:tc:questdb:6.5.3:///databasename

TimescaleDB 사용

jdbc:tc:timescaledb:2.1.0-pg13:///databasename

PGVector 사용

jdbc:tc:pgvector:pg16:///databasename

TiDB 사용

jdbc:tc:tidb:v6.1.0:///databasename

Timeplus 사용

jdbc:tc:timeplus:2.3.21:///databasename

Trino 사용

jdbc:tc:trino:352://localhost/memory/default

YugabyteDB 사용

jdbc:tc:yugabyte:2.14.4.0-b26:///databasename

클래스패스 init 스크립트 사용하기

데이터베이스 컨테이너가 시작된 뒤, 코드에 연결이 주어지기 전에 init 스크립트를 실행할 수 있어요. 스크립트는 클래스패스에 있어야 하고, 다음과 같이 참조해요.

jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql

이것은 DB 스키마 설정 등 고정된 스크립트가 있을 때 유용해요.

파일에서 init 스크립트 사용하기

init 스크립트 경로가 file:로 시작하면 파일에서 로드돼요(작업 디렉터리 기준이며, 보통 프로젝트 루트예요).

jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql

init 함수 사용하기

DB 설정에 고정 스크립트를 쓰는 대신, 직접 정의한 Java 함수를 호출하는 것이 유용할 수 있어요. 이는 데이터베이스 스키마 마이그레이션 도구를 트리거하기 위한 의도예요. 그러려면 URL에 TC_INITFUNCTION을 추가해 클래스 이름과 메서드의 전체 경로를 넘겨요.

jdbc:tc:mysql:8.0.36:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction

init 함수는 java.sql.Connection만 유일한 파라미터로 받는 public static 메서드여야 해요. 예를 들어 볼게요.

public class JDBCDriverTest {
    public static void sampleInitFunction(Connection connection) throws SQLException {
        // e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
    }
    ...

데몬 모드로 컨테이너 실행하기

기본적으로 데이터베이스 컨테이너는 마지막 연결이 닫히는 즉시 중지돼요. 하지만 컨테이너를 시작해 두고 명시적으로 중지하거나 JVM이 종료될 때까지 계속 실행해야 하는 경우가 있어요. 그러려면 URL에 TC_DAEMON 파라미터를 추가해요.

jdbc:tc:mysql:8.0.36:///databasename?TC_DAEMON=true

이 파라미터가 있으면 열린 연결이 없어도 데이터베이스 컨테이너가 계속 실행돼요.

tmpfs 옵션으로 컨테이너 실행하기

컨테이너는 데이터를 호스트 메모리에 저장하는 tmpfs 마운트를 가질 수 있어요. DB 테스트 속도를 높이고 싶을 때 유용한데, 컨테이너가 중지되면 데이터가 사라진다는 점을 알아 두세요.

이 옵션을 컨테이너에 넘기려면 URL에 TC_TMPFS 파라미터를 추가해요.

jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw

옵션이 여러 개 필요하면 쉼표로 구분해요 (예: TC_TMPFS=key:value,key1:value1&other_parameters=foo).

tmpfs 마운트에 대한 자세한 내용은 공식 Docker 문서를 참고하세요.

데이터베이스 컨테이너 객체

URL 지원을 쓸 수 없거나 컨테이너를 세밀하게 조정해야 한다면 직접 인스턴스화할 수 있어요.

테스트 클래스에 @Rule이나 @ClassRule을 추가해요. 예를 들어 볼게요.

public class SimpleMySQLTest {
    @Rule
    public MySQLContainer mysql = new MySQLContainer();

이제 테스트 코드(또는 적절한 setup 메서드)에서 이 데이터베이스에 연결하는 데 필요한 정보를 얻을 수 있어요.

  • mysql.getJdbcUrl() — 코드가 연결할 수 있는 JDBC URL을 제공해요
  • mysql.getUsername() — 드라이버에 전달할 사용자 이름을 제공해요
  • mysql.getPassword() — 드라이버에 전달할 비밀번호를 제공해요

@Rule을 쓰면 테스트 메서드마다 격리된 컨테이너를 받아요. @ClassRule을 쓰면 테스트 클래스의 모든 메서드에 대해 격리된 컨테이너 하나를 받아요.

예시/테스트:

더 알아보기