Spring Boot 3 핵심 정리 시리즈 4편. 개발용 H2 인메모리에서 실제 운영 환경 MySQL 로 넘어가는 흐름을 처음 보는 사람도 따라올 수 있게 풀어 갑니다 — MySQL 연결 설정, ddl-auto 5종 옵션, Flyway 로 스키마 버전 관리, 마이그레이션 파일 명명 규칙, TestContainers 로 실제 DB 통합 테스트, @ServiceConnection 자동 설정, 트랜잭션 경계까지. 회사 본사·창고 비유로 풀어쓴 친절한 4편.
이 글은 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 | 시작 시 생성, 종료 시 삭제 | 테스트 |
여기서 정말 중요한 시험 함정 — 운영 환경에서는 절대 create 나 create-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 와 관련된 테스트 어노테이션을 한눈에 정리해 두면 헷갈릴 일이 줄어요.
| 어노테이션 | 로드 범위 | 속도 | 용도 | 데이터베이스 |
|---|---|---|---|---|
@DataJpaTest | JPA 레이어만 | 빠름 | JPA 단위 테스트 | H2 (기본) |
@SpringBootTest | 전체 컨텍스트 | 느림 | 통합 테스트 | 설정에 따름 |
@SpringBootTest + TestContainers | 전체 컨텍스트 | 느림 | 실제 DB 통합 테스트 | 실제 DB |
@WebMvcTest | 웹 레이어만 | 빠름 | 컨트롤러 단위 테스트 | 없음 |
@JdbcTest | JDBC 만 | 빠름 | 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);
}
}
여기서 시험 함정이 하나 있어요. @Transactional 은 RuntimeException(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-dropX) - [ ] 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-auto5종 —none·validate·update·create·create-drop- 운영은 무조건
none또는validate—create·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 — 실제 운영 환경 동일 통합 테스트@Transactional—RuntimeException에서만 자동 롤백 (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차 캐시는 트랜잭션 단위 — 트랜잭션 끝나면 캐시도 비워짐
시리즈 다른 편
같은 시리즈의 다른 글들도 같은 친절 톤으로 묶어 정리되어 있어요.
- 1편 — Spring Boot 입문
- 2편 — Spring MVC REST · MockMVC
- 3편 — Spring Data JPA · 검증
- 4편 — MySQL · Flyway · TestContainers (현재 글)
- 5편 — CSV 업로드 · 페이징 · 동적 쿼리
- 6편 — JPA 관계 매핑 심화
- 7편 — Spring Security · OAuth 2.0 · JWT
- 8편 — RestTemplate · RestClient
- 9편 — Reactive Programming · WebFlux 입문
- 10편 — WebFlux 심화 · MongoDB · WebClient
- 11편 — Cloud Gateway · Maven/Gradle · Buildpack
- 12편 — OpenAPI · Spring AI
- 13편 — Actuator · 관측성
- 14편 — Spring Cache · 이벤트
- 15편 — Docker · Compose · Kubernetes
- 16편 — 마이크로서비스 · Apache Kafka
- 17편 — Spring Professional · 베스트 프랙티스 (완)