Flyway·TestContainers — 운영 DB 한 번에

2026-05-02AWS SAA-C03 스터디

Spring Boot 3 핵심 정리 시리즈 4편. 개발용 H2 인메모리에서 실제 운영 환경 MySQL 로 넘어가는 흐름을 처음 보는 사람도 따라올 수 있게 풀어 갑니다 — MySQL 연결 설정, ddl-auto 5종 옵션, Flyway 로 스키마 버전 관리, 마이그레이션 파일 명명 규칙, TestContainers 로 실제 DB 통합 테스트, @ServiceConnection 자동 설정, 트랜잭션 경계까지. 회사 본사·창고 비유로 풀어쓴 친절한 4편.

📚 Spring Boot 3 핵심 정리 · 4편 / 14편 — 운영 DB 한 번에

이 글은 Spring Boot 3 핵심 정리 시리즈의 네 번째 편입니다. 1~3 편에서 Spring Boot, Spring MVC, Spring Data JPA 의 기본기를 잡았다면, 4 편에서는 개발용 H2 인메모리 DB 에서 실제 운영 환경 MySQL 로 넘어가는 흐름을 풀어 가요.

운영 환경에서는 H2 같은 메모리 DB 를 못 써요. 데이터가 영구적으로 보관돼야 하고, 여러 서버가 같은 DB 를 공유해야 하고, 스키마 변경도 안전하게 추적돼야 해요. 이 글의 핵심 도구는 세 가지예요 — MySQL 로 운영급 DB 를 띄우고, Flyway 로 스키마 변경 이력을 버전 관리하고, TestContainers 로 진짜 DB 환경에서 통합 테스트를 돌려요.

이 글의 비유는 — MySQL = 회사 본사 자료 보관실, Flyway = 자료실 변경 이력 장부, TestContainers = 임시 모의 자료실(테스트 끝나면 자동 폐기). 이 비유 하나만 잡고 가면 설정과 어노테이션이 한 번에 정리됩니다.

왜 이 단원이 처음엔 어렵게 느껴질까요

이유는 네 가지예요.

첫째, 개발 DB 와 운영 DB 가 다른 게 처음엔 와닿지 않아요. "왜 H2 로 다 끝내지 않고 MySQL 까지 가나" 싶은데, 운영에서는 데이터 영속성·동시성·진짜 SQL 방언 같은 차이가 발목을 잡아요.

둘째, Flyway 의 마이그레이션 개념이 추상적이에요. "스키마 변경을 버전 관리한다" 는 말이 처음엔 코드 버전 관리(Git)와 어떻게 다른지 안 보여요.

셋째, ddl-auto 옵션이 5 개나 있고 이름도 비슷해요. none·validate·update·create·create-drop. 어느 환경에서 어느 옵션을 써야 하는지 한눈에 안 들어오죠.

넷째, TestContainers 가 Docker 위에서 돈다는 게 진입 장벽이에요. Docker 가 안 깔려 있으면 테스트가 못 돌아가는데, 처음 보는 사람한테는 이게 좀 어색해요.

해결법은 한 가지예요. MySQL 을 회사 본사 자료 보관실, Flyway 를 변경 이력 장부, TestContainers 를 임시 모의 자료실로 잡고 흐름을 따라가면 갑자기 명확해집니다. 이 글은 그 비유를 따라 처음부터 풀어 갑니다.

H2 에서 MySQL 로 — 운영 환경으로 넘어가기

Spring Boot 가 빛나는 부분 중 하나가 H2 → MySQL 전환이 의존성 한 줄과 설정 파일 수정으로 끝난다는 점이에요. 코드를 거의 안 바꿔도 됩니다. JPA 추상화 덕분이에요.

회사 비유로 — H2 는 사무실 책상 서랍이에요. 빠르고 편하지만 사무실 문 닫으면 다 사라져요. MySQL 은 본사 자료 보관실이에요. 24 시간 운영되고, 여러 사무실(서버)이 동시에 접근할 수 있고, 재해가 와도 백업으로 복구돼요.

<!-- pom.xml — MySQL 드라이버 의존성 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
# application.properties — MySQL 연결 설정
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/restdb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    database: mysql
    database-platform: org.hibernate.dialect.MySQLDialect
    hibernate:
      ddl-auto: validate  # 운영에서는 validate 또는 none
    show-sql: true
    properties:
      hibernate:
        format_sql: true

여기서 시험 함정이 하나 있어요. spring.jpa.hibernate.ddl-auto 옵션 5 종을 환경별로 정확히 골라야 해요. 잘못 설정하면 운영 DB 가 통째로 날아갑니다.

옵션동작사용 환경
none스키마 관련 작업 없음운영 (Flyway 사용 시)
validate엔티티와 스키마가 일치하는지 검증만운영 검증용
update변경된 부분만 추가 (컬럼 삭제 안 됨)개발 초기
create시작 시 스키마 생성 (기존 데이터 삭제)개발·테스트
create-drop시작 시 생성, 종료 시 삭제테스트

여기서 정말 중요한 시험 함정 — 운영 환경에서는 절대 createcreate-drop 을 쓰면 안 돼요. 한 번이라도 서버를 재시작하는 순간 운영 데이터가 통째로 날아갑니다. 운영은 무조건 none 또는 validate 만, 스키마 변경은 다음에 다룰 Flyway 로만 관리해요.

또 하나의 함정 — update컬럼 삭제를 자동으로 안 해요. 엔티티에서 필드를 지워도 DB 컬럼은 남아 있어요. 이래서 운영에서는 update 도 위험해서 권장하지 않습니다.

Flyway — 자료실 변경 이력 장부

Flyway 는 데이터베이스 스키마 변경을 버전 관리하는 오픈소스 마이그레이션 도구예요. Git 으로 소스 코드를 관리하듯이, Flyway 로 스키마 변경 이력을 관리해요. 팀 여러 명이 작업할 때 각자의 스키마 변경이 충돌 없이 순서대로 적용되도록 보장해 줍니다.

회사 비유로 — Flyway 는 자료실 캐비닛 변경 이력 장부예요. 누가 언제 어떤 캐비닛을 추가했고, 어떤 칸을 늘렸는지 V1, V2, V3 식 버전으로 기록해 둬요. 새 직원이 와도 이 장부만 보면 자료실의 모든 변경 흐름을 따라갈 수 있어요. Flyway 의 동작 원리에 대한 자세한 설명은 Flyway 공식 문서에서 확인할 수 있어요.

Flyway 는 flyway_schema_history 테이블을 통해 어떤 마이그레이션이 이미 적용됐는지 추적해요. 한 번 적용된 마이그레이션 스크립트를 수정하면 체크섬 불일치 오류가 발생하니, 기존 스크립트는 절대 수정하면 안 돼요.

<!-- pom.xml — Flyway 의존성 -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>  <!-- MySQL 지원 -->
</dependency>

마이그레이션 파일 위치와 명명 규칙:

src/main/resources/db/migration/
├── V1__init-mysql-charset.sql
├── V2__add-product-table.sql
├── V3__add-customer-table.sql
└── V4__add-quantity-column.sql

# 명명 규칙: V{버전}__{설명}.sql
# V (대문자) + 버전 번호 + __ (언더스코어 두 개) + 설명 + .sql

여기서 시험 함정이 하나 있어요. 언더스코어가 한 개가 아니라 두 개예요. V2_add-product.sql 은 인식 안 되고, V2__add-product.sql 이 맞아요. 처음에 가장 자주 틀리는 부분이에요.

전형적인 마이그레이션 파일 한 묶음:

-- V1__init-mysql-charset.sql
-- MySQL charset 및 collation 설정
SET NAMES utf8mb4;
SET character_set_client = utf8mb4;

-- V2__add-product-table.sql
CREATE TABLE product (
    id                  VARCHAR(36)     NOT NULL,
    version             INT             NULL,
    product_name        VARCHAR(50)     NOT NULL,
    category            VARCHAR(255)    NOT NULL,
    upc                 VARCHAR(255)    NOT NULL,
    quantity_on_hand    INT             NULL,
    price               DECIMAL(38, 2)  NOT NULL,
    created_date        DATETIME(6)     NULL,
    update_date         DATETIME(6)     NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_0900_ai_ci;

-- V3__add-customer-table.sql
CREATE TABLE customer (
    id              VARCHAR(36)     NOT NULL,
    created_date    DATETIME(6),
    customer_name   VARCHAR(255),
    update_date     DATETIME(6),
    version         INT,
    PRIMARY KEY (id)
) ENGINE=InnoDB;

Flyway 설정 옵션 한 묶음:

# application.yml — Flyway 설정
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration  # 마이그레이션 파일 위치
    baseline-on-migrate: false         # 이미 스키마가 있는 DB에 처음 적용할 때
    validate-on-migrate: true          # 마이그레이션 전 체크섬 검증

여기서 정말 중요한 시험 함정 — 이미 적용된 Flyway 마이그레이션 스크립트를 수정하면 절대 안 됩니다. 파일 내용을 한 글자라도 바꾸면 체크섬이 변경돼서 다음 시작 시 이런 에러가 나요.

ERROR: Detected failed migration to version 2 (add-product-table)
Please restore the file, or, if this migration should be skipped,
use the 'flyway repair' command to update the schema history.

새로운 변경사항은 무조건 새 버전의 마이그레이션 파일을 추가하는 게 정답이에요. 회사 비유로 — 이미 장부에 적힌 내용을 지우개로 지우면 안 되고, 새 페이지에 추가 변경을 기록해야 해요.

> 한 줄 정리 — Flyway = 자료실 변경 이력 장부 / V1__설명.sql 명명 규칙 / 한 번 적용된 파일은 절대 수정 X / 새 변경은 새 버전으로

TestContainers — 임시 모의 자료실

TestContainers 는 테스트에서 실제 Docker 컨테이너로 데이터베이스·메시지 브로커 등을 실행하는 라이브러리예요. H2 인메모리 DB 로는 재현하기 어려운 데이터베이스별 특성(charset·함수·제약조건 등)을 실제 MySQL/PostgreSQL 로 테스트할 수 있어요.

회사 비유로 — TestContainers 는 임시 모의 자료실이에요. 진짜 본사 자료실은 운영 중이라 건드릴 수 없으니, 테스트할 때만 잠깐 똑같이 생긴 모의 자료실을 띄우고, 테스트가 끝나면 자동으로 폐기해요.

장점이 셋 — (1) 운영과 동일한 DB 로 테스트, (2) 매번 깨끗한 환경, (3) 격리(여러 테스트가 서로 간섭 X).

<!-- pom.xml — TestContainers 의존성 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <scope>test</scope>
</dependency>

전통적 패턴 — @DynamicPropertySource 로 컨테이너 정보를 Spring 설정에 반영:

@SpringBootTest
@Testcontainers  // TestContainers 지원 활성화
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ProductRepositoryTest {

    // MySQL 컨테이너 정의
    @Container
    static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:8")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test")
            .withReuse(true);  // 테스트 간 컨테이너 재사용으로 속도 향상

    // 컨테이너의 동적 포트를 Spring 설정에 반영
    @DynamicPropertySource
    static void mySQLProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mySQLContainer::getJdbcUrl);
        registry.add("spring.datasource.username", mySQLContainer::getUsername);
        registry.add("spring.datasource.password", mySQLContainer::getPassword);
    }

    @Autowired
    ProductRepository productRepository;

    @Test
    @Order(1)
    void testGetCount() {
        long count = productRepository.count();
        assertThat(count).isEqualTo(2413L);  // Flyway 로 로드된 데이터 수
    }

    @Test
    @Order(2)
    void testSaveProduct() {
        Product savedProduct = productRepository.save(Product.builder()
                .productName("New Product")
                .category("Books")
                .upc("123456")
                .price(new BigDecimal("9.99"))
                .build());

        productRepository.flush();

        assertThat(savedProduct).isNotNull();
        assertThat(savedProduct.getId()).isNotNull();
    }
}

Spring Boot 3.1+ 부터는 @ServiceConnection 으로 훨씬 간결해졌어요.

@SpringBootTest
@Testcontainers
class ProductRepositoryTest {

    @Container
    @ServiceConnection  // Spring Boot 가 자동으로 DataSource 설정
    static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:8");

    @Autowired
    ProductRepository productRepository;
    // @DynamicPropertySource 불필요!
}

여기서 시험 함정이 하나 있어요. TestContainers 는 Docker 데몬이 실행 중이어야 동작해요. 로컬에서는 Docker Desktop, CI/CD 환경에서는 Docker-in-Docker 또는 TestContainers Cloud 설정이 필요해요. Docker 안 깔려 있으면 테스트가 시작도 안 돼요.

또 하나의 함정 — withReuse(true) 를 활성화하려면 사용자 홈 디렉토리에 ~/.testcontainers.properties 파일에 testcontainers.reuse.enable=true 를 박아야 해요. 안 그러면 매 테스트마다 컨테이너를 새로 만들어 속도가 느려요.

테스트 어노테이션 5 종 비교

JPA 와 관련된 테스트 어노테이션을 한눈에 정리해 두면 헷갈릴 일이 줄어요.

어노테이션로드 범위속도용도데이터베이스
@DataJpaTestJPA 레이어만빠름JPA 단위 테스트H2 (기본)
@SpringBootTest전체 컨텍스트느림통합 테스트설정에 따름
@SpringBootTest + TestContainers전체 컨텍스트느림실제 DB 통합 테스트실제 DB
@WebMvcTest웹 레이어만빠름컨트롤러 단위 테스트없음
@JdbcTestJDBC 만빠름JDBC 단위 테스트H2 (기본)

여기서 정말 중요한 시험 함정 — @DataJpaTest 는 기본적으로 H2 를 쓰고 트랜잭션 롤백합니다. 실제 MySQL 로 테스트하려면 @AutoConfigureTestDatabase(replace = NONE) 을 추가하거나, @SpringBootTest + TestContainers 패턴으로 가야 해요.

@Transactional — 트랜잭션 경계 제대로 잡기

JPA 와 함께 자주 따라오는 어노테이션이 @Transactional 이에요. 메서드 호출 시작 시 트랜잭션을 시작하고, 정상 종료 시 commit, 예외 발생 시 rollback 해요.

회사 비유로 — @Transactional 은 자료실에 들어가서 여러 작업을 한꺼번에 끝낸 후 한 번에 결과를 확정하는 식이에요. 도중에 문제가 생기면 자료실에서 한 모든 작업이 없던 일이 돼요(rollback).

@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    // 조회 전용 — readOnly = true 로 최적화
    @Transactional(readOnly = true)
    public Optional<OrderDTO> getOrder(Long id) {
        return orderRepository.findById(id).map(orderMapper::toDTO);
    }

    // 쓰기 작업 — 기본 @Transactional
    @Transactional
    public OrderDTO createOrder(OrderRequest request) {
        Product product = productRepository.findById(request.getProductId())
                .orElseThrow(NotFoundException::new);

        // 재고 차감
        product.setQuantityOnHand(product.getQuantityOnHand() - request.getQuantity());

        // 주문 생성
        Order order = orderRepository.save(new Order(product, request.getQuantity()));

        return orderMapper.toDTO(order);
    }
}

여기서 시험 함정이 하나 있어요. @TransactionalRuntimeException(unchecked) 에서만 자동 롤백돼요. Exception(checked) 에서는 자동 롤백이 안 됩니다. 확실하게 하려면 @Transactional(rollbackFor = Exception.class) 식으로 명시하는 게 안전해요.

또 하나의 함정 — 같은 클래스 안에서 self-call 하면 트랜잭션이 안 시작돼요. Spring AOP 가 프록시 기반이라 외부 호출만 가로채요. 자기 메서드를 호출하는 건 프록시를 거치지 않아서 트랜잭션이 무시됩니다.

// 잘못된 예 — self-call
@Service
public class OrderServiceImpl {
    public void publicMethod() {
        innerMethod();  // 같은 클래스 self-call → 트랜잭션 시작 안 됨!
    }

    @Transactional
    private void innerMethod() {
        ...
    }
}

// 올바른 예 — 별도 빈으로 분리하거나 외부에서 호출

@Transactional(readOnly = true) 를 잊지 마세요. 조회 전용 메서드에 박으면 Hibernate 가 dirty checking·flush 를 안 해서 성능이 향상돼요.

Flyway 와 TestContainers 의 조합

운영급 테스트의 황금 조합이 TestContainers + Flyway 예요. TestContainers 가 매번 깨끗한 MySQL 컨테이너를 띄우고, Flyway 가 그 안에 운영 스키마를 그대로 깔아요. 그 위에서 통합 테스트를 돌리면 운영과 동일한 환경에서 검증할 수 있어요.

회사 비유로 — 모의 자료실(TestContainers)을 만들고, 본사 캐비닛 구조(Flyway 마이그레이션)을 그대로 복제해서, 거기서 새 절차(테스트)를 시험해 보는 식이에요.

# 테스트용 application-test.yml
spring:
  datasource:
    # @ServiceConnection 이 자동 채움
  jpa:
    hibernate:
      ddl-auto: validate  # Flyway 가 만든 스키마와 엔티티 일치 검증
  flyway:
    enabled: true
    locations: classpath:db/migration

여기서 시험 함정이 하나 있어요. TestContainers 환경에서 ddl-auto=create 를 쓰면 Flyway 와 충돌해요. 둘 다 스키마를 만들려고 들기 때문이에요. TestContainers + Flyway 조합에서는 무조건 ddl-auto=validate 또는 none 이에요.

ApplicationRunner — 시작 시 데이터 로드

운영 DB 가 깨끗한 상태일 때 초기 데이터를 자동으로 로드하고 싶을 때가 있어요. ApplicationRunner 인터페이스를 구현하면 애플리케이션 시작 직후 한 번만 실행돼요.

@Component
@RequiredArgsConstructor
public class BootstrapData implements ApplicationRunner {

    private final ProductRepository productRepository;

    @Override
    @Transactional
    public void run(ApplicationArguments args) throws Exception {
        // 멱등성 보장 — 이미 데이터가 있으면 건너뜀
        if (productRepository.count() > 0) {
            return;
        }
        loadInitialData();
    }

    private void loadInitialData() {
        List<Product> products = List.of(
                Product.builder()
                        .productName("Sample Product 1")
                        .category("Books")
                        .upc("12345678")
                        .price(new BigDecimal("9.99"))
                        .build(),
                Product.builder()
                        .productName("Sample Product 2")
                        .category("Electronics")
                        .upc("87654321")
                        .price(new BigDecimal("29.99"))
                        .build()
        );

        productRepository.saveAll(products);  // 배치 INSERT 가 개별 save 보다 효율적
    }
}

여기서 시험 함정이 하나 있어요. ApplicationRunner 는 멱등성을 보장해야 합니다. 서버 재시작할 때마다 같은 데이터가 또 추가되면 큰일나요. count() > 0 같은 가드 조건을 항상 박아야 해요.

또 하나의 함정 — 개별 save() 대신 saveAll() 을 쓰세요. 배치 INSERT 가 개별 INSERT 보다 훨씬 빨라요. 1 만 건 데이터를 개별 save 로 처리하면 1 만 번의 라운드 트립이 발생해요.

운영 환경 체크리스트

운영에 배포하기 전에 한 번 더 확인할 항목들을 모아 두면 사고가 줄어요.

  • [ ] ddl-auto=none 또는 validate (절대 create·create-drop X)
  • [ ] Flyway 마이그레이션이 모두 적용됐는지 확인
  • [ ] DB 연결 풀 크기 적절히 설정 (spring.datasource.hikari.maximum-pool-size)
  • [ ] DB 인증 정보를 application.yml 에 평문으로 두지 않기 (환경 변수·Vault 사용)
  • [ ] 백업·복구 절차 정리
  • [ ] 모니터링·알림 설정 (Actuator + 다음 편들에서 다룰 영역)
  • [ ] N+1 쿼리 점검 (개발 환경에서 show-sql=true 로 확인)

Flyway 마이그레이션 운영 패턴 5 가지

Flyway 를 안전하게 쓰려면 몇 가지 운영 패턴을 익혀 두면 좋아요.

1. 절대 기존 파일 수정 X

체크섬 불일치 에러가 나요. 새 변경은 새 V 파일로.

2. 베이스라인 설정 (기존 DB 에 처음 도입)

이미 운영 중인 DB 에 Flyway 를 처음 도입할 때는 baseline-on-migrate=true 를 쓰면, 현재 스키마를 V1 으로 인정하고 다음 변경부터 추적해요.

spring:
  flyway:
    baseline-on-migrate: true
    baseline-version: 1

3. 롤백 마이그레이션 (선택)

Flyway 무료 버전은 자동 롤백이 없어요. 문제가 생기면 별도의 V{n+1} 파일로 역방향 변경을 만드는 게 정석이에요.

4. 운영용 단일 마이그레이션 워크플로

브랜치마다 마이그레이션을 만들면 V 번호 충돌이 생겨요. 팀에서 마이그레이션은 main 브랜치에 합쳐질 때 V 번호를 정하는 워크플로가 안전해요.

5. 큰 데이터 변경은 단계 분리

큰 DML 마이그레이션(수백만 건 UPDATE 등)은 한 번에 돌리지 말고 여러 단계로 분리. 마이그레이션 중 락이 오래 걸리면 운영이 멎어요.

영속성 컨텍스트와 1차 캐시

JPA 에는 1 차 캐시(영속성 컨텍스트) 라는 개념이 있어요. 같은 트랜잭션 안에서 같은 ID 의 엔티티를 여러 번 조회해도 DB 에 한 번만 가요. 두 번째부터는 1 차 캐시에서 꺼내옵니다.

@Transactional
public void someMethod() {
    Product p1 = productRepository.findById(id).orElseThrow();
    Product p2 = productRepository.findById(id).orElseThrow();
    // p1 == p2 (같은 객체) — DB 쿼리는 1번만 실행
}

회사 비유로 — 자료실에 한 번 가서 가져온 문서는 책상 위에 두고, 같은 문서를 또 보고 싶을 때 자료실에 다시 안 가요. 책상 위(1 차 캐시)에서 바로 봅니다.

여기서 시험 함정이 하나 있어요. 1 차 캐시는 트랜잭션 단위예요. 트랜잭션이 끝나면 캐시도 비워져요. 메서드를 벗어나서 다른 트랜잭션에서 같은 ID 를 조회하면 DB 에 다시 갑니다.

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 4 편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • MySQL = 본사 자료 보관실 / Flyway = 변경 이력 장부 / TestContainers = 임시 모의 자료실
  • H2 → MySQL 전환 — 의존성 추가 + datasource·dialect 설정 변경으로 끝
  • ddl-auto 5종none·validate·update·create·create-drop
  • 운영은 무조건 none 또는 validatecreate·create-drop 절대 X
  • update 도 운영 X — 컬럼 삭제가 자동으로 안 됨
  • Flyway 명명 규칙V{버전}__{설명}.sql (언더스코어 두 개)
  • 마이그레이션 위치 — src/main/resources/db/migration/
  • 이미 적용된 마이그레이션 파일 절대 수정 X — 체크섬 불일치 에러
  • 새 변경은 새 V 번호로 파일 추가
  • 기존 DB 에 Flyway 처음 도입 — baseline-on-migrate=true
  • TestContainers 는 Docker 데몬 필수 — 로컬은 Docker Desktop, CI 는 별도 설정
  • TestContainers 컨테이너 재사용 — withReuse(true) + ~/.testcontainers.properties 설정
  • @ServiceConnection (Spring Boot 3.1+)@DynamicPropertySource 없이 자동 설정
  • TestContainers + Flyway 조합 — ddl-auto=validate 만 (충돌 방지)
  • @DataJpaTest — JPA 레이어만, 트랜잭션 자동 롤백, H2 기본
  • 실제 DB 로 @DataJpaTest 쓰려면 @AutoConfigureTestDatabase(replace = NONE) 추가
  • @SpringBootTest + TestContainers — 실제 운영 환경 동일 통합 테스트
  • @TransactionalRuntimeException 에서만 자동 롤백 (checked exception 은 명시 필요)
  • @Transactional(rollbackFor = Exception.class) — 모든 예외에서 롤백
  • Self-call 시 트랜잭션 안 됨 — Spring AOP 프록시 한계
  • 조회 전용 — @Transactional(readOnly = true) 로 성능 최적화
  • ApplicationRunner — 시작 시 한 번만 실행, 멱등성 보장 필수 (count() > 0 가드)
  • 대량 INSERT — save() 대신 saveAll() 로 배치 INSERT
  • JPA 1차 캐시 = 영속성 컨텍스트 — 같은 트랜잭션 내 같은 ID 조회는 1번만 DB 호출
  • 1차 캐시는 트랜잭션 단위 — 트랜잭션 끝나면 캐시도 비워짐

시리즈 다른 편

같은 시리즈의 다른 글들도 같은 친절 톤으로 묶어 정리되어 있어요.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!