[Spring] Spring Boot MySQL 설정 (커넥션 풀, OSIV, SQL 로깅)

2025. 9. 18. 00:02·공부일기../Spring

 

인트로

application.yml 기초 설정을 이해하고, Docker Compose로 MySQL 환경을 구성하는 방법을 익혔다면
이제 실무에서 마주치게 될 설정들을 다룰 차례다.
 
Part 1에서는 Docker Compose로 MySQL을 3307 포트에 띄우고, Profile로 로컬(3306)과 도커(3307) 환경을 분리했다.
특히 data/mysql 폴더 때문에 데이터베이스 자동 생성이 안 되는 문제도 해결해봤다.
 
이제 Part 2에서는 그 설정을 바탕으로 실제 개발하면서 마주치게 될 성능 최적화와 테스트 환경 설정을 포스팅했다.
특히 OSIV가 뭔지, HikariCP는 왜 쓰는지, 테스트에서는 왜 다른 설정을 쓰는지 등 "왜 이렇게 설정해야 하는지"에 대한 깊이 있는 내용들을 정리해봤다.
 
지금까지 블로그랑 AI에게 물어물어 생각없이 복붙하던 시절은 안뇽..
 
[Docker] Spring Boot에서 Docker Compose로 MySQL 환경 구성하기
[Spring Boot] application.yml 설정파일 마스타~~


 

1. 기본 설정과 이해

1-1. 타임존과 UTF-8 설정

spring:
  datasource:
    url: jdbc:mysql://localhost:3307/SpringDataBase?characterEncoding=UTF-8&serverTimezone=UTC
  jpa:
    properties:
      hibernate.timezone.default_storage: NORMALIZE_UTC
      hibernate.jdbc.time_zone: UTC

 
왜 UTC로 설정할까?

  • 서버가 여러 지역에 있을 때 시간 혼란을 방지
  • 국제 서비스에서는 필수
  • 클라이언트에서 로컬 시간으로 변환해서 표시

URL 파라미터들:

  • characterEncoding=UTF-8: 한글 깨짐 방지
  • serverTimezone=UTC: 서버 타임존 명시적 설정

1-2. DDL 관련 설정

spring:
  jpa:
    generate-ddl: false  # DDL 자동 생성 안함
    hibernate:
      ddl-auto: none  # 테이블 자동 생성 안함

 
ddl-auto 옵션들:

  • create: 기존 테이블 삭제 후 새로 생성
  • create-drop: create + 애플리케이션 종료 시 테이블 삭제
  • update: 기존 테이블과 엔티티 비교해서 변경사항만 반영
  • validate: 엔티티와 테이블이 정상 매핑되는지만 확인
  • none: 아무것도 안 함

실무에서는 왜 none을 쓸까?

  • 개발 초기에는 create나 update를 쓰기도 한다
  • 하지만 실제 운영에서는 none으로 설정하고 migration 도구(Flyway, Liquibase 등)를 쓰는 게 좋다
  • DDL 변경 이력을 명확하게 관리할 수 있고, 롤백도 가능하다

 


 

2. 성능 최적화 설정

2-1. HikariCP 커넥션 풀 최적화

HikariCP는 Java에서 가장 빠르고 가벼운 JDBC 커넥션 풀 라이브러리다. Spring Boot 2.0부터는 기본 커넥션 풀로 채택되었다.
 
커넥션 풀이 뭔가?
데이터베이스에 연결할 때마다 새로 커넥션을 만들면 시간이 오래 걸린다. 그래서 미리 여러 개의 커넥션을 만들어두고 재사용하는 것이 커넥션 풀이다.
 
커넥션 풀 없이 (느림):

요청1 → DB 연결 생성 (느림) → SQL 실행 → 연결 해제
요청2 → DB 연결 생성 (느림) → SQL 실행 → 연결 해제
요청3 → DB 연결 생성 (느림) → SQL 실행 → 연결 해제

 
커넥션 풀 사용 (빠름):

앱 시작: 미리 10개 연결 생성해서 풀에 저장
[커넥션 풀: ●●●●●●●●●●]

요청1 → 풀에서 연결 빌림 → SQL 실행 → 풀에 반납
요청2 → 풀에서 연결 빌림 → SQL 실행 → 풀에 반납  
요청3 → 풀에서 연결 빌림 → SQL 실행 → 풀에 반납

 
왜 HikariCP를 쓸까?

  • 성능: 다른 커넥션 풀보다 월등히 빠르다
  • 안정성: 메모리 사용량이 적고 안정적이다
  • 간단함: 설정이 복잡하지 않다
  • Spring Boot 기본: 별도 설정 없이도 자동으로 사용된다

2-2. OSIV(Open Session In View) - 가장 헷갈리는 설정

spring:
  jpa:
    open-in-view: false  # OSIV 비활성화

 
OSIV는 처음에 이해하기 어려운 개념 중 하나다. 차근차근 설명해보자.

핵심 포인트
OSIV는 영속성 컨텍스트의 생명주기를 View 응답까지 확장하는 것이지, 트랜잭션의 생명주기를 확장하는 것은 아니다.
트랜잭션이 커밋된 후에는 쓰기 작업(수정, 삭제, 추가)은 불가능하고, 읽기 작업만 가능하다.

 
문제 상황 예시:

@Entity
public class User {
    @OneToMany(fetch = FetchType.LAZY)
    private List<Order> orders;
}

@Service
public class UserService {
    @Transactional
    public User getUser(Long id) {
        return userRepository.findById(id); // 트랜잭션 여기서 끝남
    }
}

@Controller
public class UserController {
    public String userPage(Long id, Model model) {
        User user = userService.getUser(id); // 트랜잭션 이미 끝남
        user.getOrders().size(); // 여기서 LazyInitializationException 발생!
        return "user";
    }
}

 
OSIV가 true일 때 (기본값)

HTTP 요청 시작
    ↓
영속성 컨텍스트 생성 + DB 커넥션 획득
    ↓
Controller 실행
    ↓
Service 실행 (@Transactional 시작)
    ↓
Repository에서 데이터 조회
    ↓
Service 끝 (@Transactional 커밋/종료) ← 트랜잭션은 여기서 끝!
    ↓                                   하지만 영속성 컨텍스트는 계속 유지
Controller에서 user.getOrders() 호출 → Lazy Loading 가능! (읽기만 가능)
    ↓
View 렌더링 (Thymeleaf 등)
    ↓
HTTP 응답 완료 ← 여기서 영속성 컨텍스트 종료 + DB 커넥션 해제

 
장점:

  • 지연 로딩을 트랜잭션 범위를 고려하지 않고 간편하게 사용할 수 있다
  • 코딩이 편하다 - 어디서든 user.getOrders()를 호출할 수 있다

 
단점:

  • 커넥션을 너무 오래 점유한다 - API 호출이 10초 걸리면 10초 동안 커넥션을 유지해야 한다
  • 실시간 트래픽이 중요한 서비스에서는 커넥션 부족으로 시스템 장애가 발생할 수 있다
  • View 레이어에서 의도치 않은 쿼리가 발생할 수 있다

 
OSIV가 false일 때

HTTP 요청 시작
    ↓
Controller 실행
    ↓
Service 실행 (@Transactional 시작) ← 여기서 영속성 컨텍스트 생성 + DB 커넥션 획득
    ↓
Repository에서 데이터 조회 (필요한 건 fetch join으로 미리 다 가져와야 함)
    ↓
Service 끝 (@Transactional 종료) ← 영속성 컨텍스트 종료 + DB 커넥션 해제
    ↓
Controller에서 user.getOrders() 호출 → LazyInitializationException 발생!
    ↓
View 렌더링
    ↓
HTTP 응답 완료

 
장점:

  • 커넥션 리소스를 낭비하지 않는다
  • 언제 어떤 쿼리가 실행되는지 명확하게 제어할 수 있다

단점:

  • 모든 지연 로딩을 트랜잭션 안에서 처리해야 한다
  • 필요한 데이터는 fetch join이나 @EntityGraph로 미리 가져와야 한다
@Service
public class UserService {
    @Transactional
    public User getUserWithOrders(Long id) {
        // fetch join으로 orders까지 한 번에 가져옴
        return userRepository.findByIdWithOrders(id);
    }
}

 
언제 사용해야 할까?

OSIV true 사용 시점:

  • 요청이 많지 않은 간단한 서비스
  • 커넥션을 많이 사용하지 않는 내부 관리 시스템
  • 개발 초기 단계에서 빠른 프로토타이핑

OSIV false 사용 시점 (권장):

  • 고객 서비스 실시간 API (예: 채팅, 결제 시스템)
  • 동시 사용자가 많은 서비스
  • MSA 환경에서 커넥션 효율성이 중요한 경우

 
요즘은 대부분 false로 설정하는 추세다. 처음에는 좀 불편하지만, 나중에 성능 문제로 고생하는 것보다는 처음부터 제대로 하는 게 좋다.
 
 


 

3. 환경별 설정 파일 통합 예시

3-1. 개발 환경 (application-local.yml)

# 개발 환경 - 편의성과 디버깅에 중점
spring:
  datasource:
    url: jdbc:mysql://localhost:3307/SpringDataBase?characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 1234
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      maximum-pool-size: 3        # 개발자 혼자 사용 - 적은 커넥션
      connection-timeout: 10000   # 10초 타임아웃
      max-lifetime: 60000         # 1분 후 커넥션 해제

  jpa:
    open-in-view: true            # 개발 편의성 우선 - 지연로딩 자유롭게 사용
    show-sql: false               # 메인에서는 SQL 로그 끔 (너무 많아짐)
    generate-ddl: false
    hibernate:
      ddl-auto: update            # 개발 중에는 엔티티 변경 시 자동 반영
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        timezone.default_storage: NORMALIZE_UTC
        jdbc.time_zone: UTC

logging:
  level:
    org.hibernate.SQL: warn       # 메인에서는 SQL 로그 최소화
    org.hibernate.type.descriptor.sql: warn

3-2. 운영 환경 (application-prod.yml)

# 운영 환경 - 성능과 안정성에 중점
spring:
  datasource:
    url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?characterEncoding=UTF-8&serverTimezone=UTC
    username: ${DB_USERNAME}      # 환경변수로 보안 강화
    password: ${DB_PASSWORD}
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      maximum-pool-size: 16       
      connection-timeout: 30000   # 30초 타임아웃
      max-lifetime: 1800000       # 30분 후 커넥션 해제
      leak-detection-threshold: 60000  # 커넥션 누수 감지 (60초)

  jpa:
    open-in-view: false           # 성능 최적화 - 커넥션 효율적 사용
    show-sql: false               # 운영에서는 SQL 로그 끔
    generate-ddl: false
    hibernate:
      ddl-auto: none              # 운영에서는 DDL 변경 금지
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        timezone.default_storage: NORMALIZE_UTC
        jdbc.time_zone: UTC
        jdbc.batch_size: 20       # 배치 처리 최적화
        order_inserts: true       # INSERT 순서 최적화
        order_updates: true       # UPDATE 순서 최적화

logging:
  level:
    org.hibernate.SQL: error      # 에러만 로깅
    org.hibernate.type.descriptor.sql: error
    com.zaxxer.hikari: info       # HikariCP 상태 모니터링

3-3. 테스트 환경 (src/test/resources/application.yml)

# 테스트 환경 - 디버깅과 데이터 격리에 중점
spring:
  sql:
    init:
      mode: always                # 매 테스트마다 스키마/데이터 초기화

  datasource:
    url: jdbc:mysql://localhost:3307/SpringDataBase?characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 1234
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      maximum-pool-size: 2        # 테스트는 적은 커넥션으로 충분
      connection-timeout: 5000    # 빠른 실패로 테스트 속도 향상

  jpa:
    open-in-view: false           # 테스트에서는 명확한 트랜잭션 경계 확인
    show-sql: true                # 테스트 디버깅을 위한 SQL 로그
    generate-ddl: false
    hibernate:
      ddl-auto: none              # schema.sql로 테이블 관리
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        format_sql: true          # SQL 예쁘게 정렬
        highlight_sql: true       # SQL 문법 강조
        use_sql_comments: true    # SQL에 주석 추가
        timezone.default_storage: NORMALIZE_UTC
        jdbc.time_zone: UTC

logging:
  level:
    org.hibernate.SQL: debug      # SQL 쿼리 상세 로깅
    org.hibernate.type.descriptor.sql: trace  # 파라미터 값까지 로깅

3-4. 환경별 설정 비교표

설정항목개발(local) 운영(prod)테스트 이유 ㅠ
OSIVtruefalsefalse개발 편의성 vs 운영 성능
HikariCP Pool Size3162동시 사용자 수에 따라
ddl-autoupdatenonenone개발 편의성 vs 안정성
SQL 로깅warnerrordebug디버깅 vs 성능
커넥션 타임아웃10초30초5초사용 패턴에 따라
보안하드코딩환경변수하드코딩운영 보안 강화

 
 


 

4. 테스트 환경 설정의 특별함

4-1. 왜 테스트에서만 이런 설정을 쓸까?

운영 환경에서 모든 SQL을 로그로 찍으면:

  • 성능이 떨어진다 (디스크 I/O 증가)
  • 로그 파일이 너무 커진다 (용량 문제)
  • 민감한 데이터가 로그에 노출될 수 있다

하지만 테스트할 때는 오히려 이런 정보가 필요하다:

  • "어? 왜 이 테스트가 실패하지?" → SQL을 보면 원인을 금방 찾을 수 있다
  • N+1 쿼리 문제 발견
  • 의도하지 않은 쿼리가 실행되는지 확인

4-2. SQL 초기화의 의미와 실제 사용법

mode: always 설정은 테스트를 실행할 때마다 data.sql이나 schema.sql 같은 초기화 스크립트를 실행한다는 뜻이다.
파일 구조 테스트 리소스 폴더에 SQL 파일들을 만들어야 한다:

src/test/resources/
├── application.yml
├── schema.sql (테이블 생성)
└── data.sql (초기 데이터 삽입)

 
schema.sql 예시 (테이블 생성)

-- 기존 테이블이 있으면 삭제
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;

-- 사용자 테이블 생성
CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 주문 테이블 생성
CREATE TABLE orders (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

 
data.sql 예시 (초기 데이터)

-- 테스트용 사용자 데이터
INSERT INTO users (name, email) VALUES 
('홍길동', 'hong@example.com'),
('김철수', 'kim@example.com'),
('이영희', 'lee@example.com');

-- 테스트용 주문 데이터
INSERT INTO orders (user_id, product_name, price) VALUES 
(1, '노트북', 1500000.00),
(1, '마우스', 30000.00),
(2, '키보드', 80000.00),
(3, '모니터', 300000.00);

4-3. 실행 순서와 트랜잭션

테스트가 실행될 때마다 다음과 같은 순서로 진행된다:

1. schema.sql 실행 → 테이블 생성 (기존 테이블 삭제 후)
2. data.sql 실행 → 초기 데이터 삽입
3. 테스트 메서드 시작 (@Transactional이 있다면 여기서 트랜잭션 시작)
4. 테스트 코드 실행 (DB 조작)
5. 테스트 메서드 종료 (@Transactional + @Rollback으로 변경사항 롤백)
6. 다음 테스트를 위해 1-2번 과정 반복

 
트랜잭션과 롤백의 중요성

@SpringBootTest
class UserServiceTest {

    @Test
    @Transactional  // 이 어노테이션이 있어야 롤백 가능!
    void 트랜잭션_있는_테스트() {
        User user = new User("홍길동");
        userRepository.save(user);  
        assertEquals(4, userRepository.count()); // 초기 3명 + 새로 추가 1명
        // 테스트 완료 후 @Rollback(true)로 자동 롤백됨
    }

    @Test  
    void 트랜잭션_없는_테스트() {  // @Transactional이 없으면 롤백 안됨!
        User user = new User("김철수");
        userRepository.save(user);  
        assertEquals(4, userRepository.count());
        // 데이터가 실제로 DB에 남아있음 - 다음 테스트에 영향을 줄 수 있음!
    }
}

 
왜 mode: always가 더 확실한 방법인가?
`@Transactional`을 빼먹거나 특별한 이유로 롤백이 안 되는 경우가 있을 수 있다.
그래서 `mode: always` 설정으로 매 테스트마다 스키마와 데이터를 완전히 초기화하는 것이 테스트 간 간섭을 방지하는 가장 확실한 방법이다.
 
트랜잭션 vs SQL 초기화 방식 비교

방식장점단점
@Transactional + @Rollback빠름, 메모리상 롤백실수로 빼먹을 수 있음, @Rollback(false) 때 문제
mode: always (SQL 초기화)확실함, 완전 초기화약간 느림, 디스크 I/O

 
실무에서는 두 방식을 함께 사용하는 것이 일반적이다:

  • 기본적으로 @Transactional로 빠른 롤백
  • mode: always로 확실한 초기화 보장

4-4. 실제 테스트 실행 시 로그 예시

테스트를 실행하면 콘솔에 이런 식으로 SQL이 출력된다:

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.name as name2_0_ 
    from
        users user0_ 
    where
        user0_.name=?
binding parameter [1] as [VARCHAR] - [홍길동]

이렇게 보면 어떤 SQL이 실행되고 파라미터로 어떤 값이 들어가는지 한눈에 알 수 있다.
 
 
 

마무리

Part 2에서는 실제 개발에서 마주치게 될 고급 설정들과 그 이유들을 다뤘다. 특히 OSIV, HikariCP, SQL 로깅 등은 처음에는 복잡해 보이지만, 한 번 이해하고 나면 실무에서 정말 유용한 도구들이다.
가장 중요한 건 "왜 이렇게 설정하는지"를 이해하는 것이다. 그래야 나중에 문제가 생겼을 때 스스로 해결할 수 있고, 프로젝트 특성에 맞게 설정을 조정할 수 있다.
실무에서는 처음에 완벽한 설정을 만들기보다는, 기본 설정으로 시작해서 문제가 생길 때마다 하나씩 개선해 나가는 것이 일반적이다. 개발하다 보면 자연스럽게 "아, 이런 상황에는 이 설정을 바꿔야겠다"라는 감이 생기게 된다.
 
 
 
 

참고자료

공식 문서

  • Spring Boot Reference Documentation - Application Properties
  • Spring Boot Reference - Data Access
  • Spring Data JPA Reference - Web Support
  • HikariCP Configuration
  • Spring Boot Testing Guide

실무 가이드

  • Baeldung - Spring Boot MySQL Configuration
  • Baeldung - Spring Boot Testing
  • Baeldung - HikariCP Configuration

'공부일기.. > Spring' 카테고리의 다른 글

[spring] 언제! 리팩토링해야 할까? 실전 가이드- 코드 품질 문제 (2/3)  (0) 2025.10.04
[spring] 언제! 리팩토링해야 할까? 실전 가이드- 설계원칙 위반 (1/3)  (0) 2025.10.03
[Spring Boot] application.yml 설정파일 마스타~~  (0) 2025.09.14
[Spring-legacy] 이거 @RequestBody Map으로 받아도 되나요?  (1) 2025.08.27
[Redis] Redis 동작원리 : 속도 빠른이유  (4) 2025.08.21
'공부일기../Spring' 카테고리의 다른 글
  • [spring] 언제! 리팩토링해야 할까? 실전 가이드- 코드 품질 문제 (2/3)
  • [spring] 언제! 리팩토링해야 할까? 실전 가이드- 설계원칙 위반 (1/3)
  • [Spring Boot] application.yml 설정파일 마스타~~
  • [Spring-legacy] 이거 @RequestBody Map으로 받아도 되나요?
s0-0mzzang
s0-0mzzang
공부한것을 기록합니다...
  • s0-0mzzang
    승민이의..개발일기..🐰
    s0-0mzzang
  • 전체
    오늘
    어제
    • 전체~ (108)
      • 마음가짐..! (10)
      • 공부일기.. (76)
        • weekly-log (6)
        • Spring (19)
        • Java (18)
        • DataBase (10)
        • git (2)
        • JPA (6)
        • kafka (1)
        • Backend Architecture (3)
        • Troubleshooting (삽질..ㅋ) (2)
        • Cloud (1)
        • Docker (2)
        • 알고리즘 (1)
        • 리액트 (2)
        • Infra (3)
      • 하루일기.. (22)
        • 그림일기 (8)
        • 생각일기 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    항해99
    StringTokenizer
    ERD
    TDD
    ADC 환경
    리팩토링
    BufferedReader
    spring boot
    Paging
    spring
    JPA
    swagger
    SpringBoot
    React
    MySQL
    스프링부트
    인프라 기초
    자바
    다짐
    항해플러스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[Spring] Spring Boot MySQL 설정 (커넥션 풀, OSIV, SQL 로깅)
상단으로

티스토리툴바