15. 트랜잭션(Transaction)과 OSIV

박은서's avatar
Feb 26, 2026
15. 트랜잭션(Transaction)과 OSIV

1. 트랜잭션(Transaction)

1️⃣ 트랜잭션(Transaction)이란?

1) 한 줄 정의

“작업 묶음을 하나의 단위로 처리하는 것”

2️⃣ 비유로 이해하기

1) 은행 송금

  1. 내 계좌에서 돈 차감
  1. 상대 계좌에 돈 추가
➡️ 이 둘은 항상 같이 성공해야 함
  • 차감만 되고 추가가 안 되면 문제
  • 추가만 되고 차감이 안 돼도 문제
➡️ 그래서 둘을 하나의 묶음으로 처리 → 트랜잭션

3️⃣ 트랜잭션의 핵심 특징 (ACID 중 실무 감각만)

특징
쉬운 설명
원자성
전부 성공 / 전부 실패
일관성
데이터가 이상해지면 안 됨
격리성
동시에 작업해도 서로 방해 X
지속성
성공하면 반드시 저장됨

4️⃣ 스프링에서 트랜잭션의 역할

스프링은 이런 걸 자동으로 처리해 줌
  • DB 작업 시작
  • 중간에 오류 나면 롤백(되돌림)
  • 문제 없으면 커밋(저장)

5️⃣ 사용하는 방법

@Transactional public void transfer() { // DB 작업들 }
“이 메서드 안의 DB 작업은 하나의 트랜잭션으로 처리해줘”

6️⃣ 트랜잭션이 필요한 이유

1) 트랜잭션 없을 때 문제점

  • 일부만 저장됨
  • 데이터 꼬임
  • 복구 어려움
➡️ 실무에서는 거의 필수

7️⃣ 필기

notion image

1. OSIV(Open Session In View)

1️⃣ OSIV란?

1) 한 줄 정의

“트랜잭션이 끝난 뒤에도 DB 연결을 열어두는 방식”

2️⃣ 문제 상황 이해하기

스프링 + JPA 환경에서 자주 생기는 에러
LazyInitializationException
왜 생길까?

3️⃣ 비유로 이해하기

1) 상황

  1. DB에서 회원 조회
  1. 회원 → 주문 목록 있음 (LAZY 로딩)
member.getOrders()

2) 문제 발생 과정

  • 트랜잭션 종료
  • DB 연결 끊김
  • 나중에 orders 접근
➡️ 이미 연결 끊겼는데 데이터 더 가져오려 함 → 에러 (LazyInitializationException)

4️⃣ OSIV가 하는 일

  • DB 연결을 웹 요청 끝날 때까지 유지
    • 컨트롤러에서도 접근 가능
    • 뷰(View)에서도 접근 가능
    • Lazy 로딩 에러 줄어듦

정리

OSIV OFF
OSIV ON
트랜잭션 끝나면 연결 종료
요청 끝날 때까지 연결 유지
Lazy 로딩 자주 에러
Lazy 로딩 편함

5️⃣ OSIV의 장단점

1) 장점

  • Lazy 로딩 에러 줄어듦
  • 코드 단순
  • 개발 편함

2) 단점 (중요)

  • DB 연결 오래 잡음
    • 요청 동안 계속 연결 유지
    • 트래픽 많으면 성능 문제
  • 예상 못한 쿼리 발생
    • 뷰에서 이런 코드:
      • member.getOrders()
        ➡️ 화면 렌더링 중 DB 조회 발생
    • 디버깅 어려움
    • 성능 튀는 원인
  • 설계가 흐려짐
    • 서비스 레이어 책임 약해짐
    • 컨트롤러/뷰가 DB 접근 느낌

6️⃣ 실무 적용

1) 일반적인 전략

  • OSIV 끄는 경우 많음
    • spring.jpa.open-in-view: false
  • 이유
    • 성능 관리 쉬움
    • 쿼리 예측 가능
    • 레이어 역할 명확

2) 대신 이렇게 처리

  • 필요한 데이터 미리 조회
    • fetch join
      또는
      DTO 조회
  • 서비스에서 데이터 완성
    • 컨트롤러/뷰는 가공된 데이터만 사용

7️⃣ 그림으로 이해하기

notion image

※ Rest API에서 OSIV = true

1️⃣ OSIV = true

웹 요청 끝날 때까지 DB 연결 유지
  • 컨트롤러에서도 연결 살아 있음
  • JSON 변환 시점에도 연결 살아 있음

2️⃣ REST API

1) REST API 흐름

Controller → Entity 반환 → JSON 변환 → 응답
⚠️ JSON 변환은 트랜잭션 끝난 뒤에 일어남

2) 문제 상황 (실무에서 실제로 많이 터짐)

  • 엔티티
    • Member { List<Order> orders; // LAZY }
  • 컨트롤러
    • return member;

3) JSON 변환 시점

  • Spring이 내부적으로 아래 작업 수행
member.getOrders()
WHY? JSON 만들려면 필드 접근해야 하니까

3️⃣ OSIV = true 일 때 벌어지는 일

  • DB 연결 살아 있음
  • Lazy 로딩 가능
➡️ 여기서 쿼리가 터짐

1) ❌ 예상 못한 쿼리 폭탄

  • JSON 만드는 과정
    • orders 조회 쿼리 발생
    • orders 안의 다른 연관관계 또 조회
    • 계속 이어짐
➡️ 개발자는 컨트롤러에서 쿼리 안 날렸다고 생각
➡️ 실제로는 응답 직전에 DB 난사

2) ❌ 성능 디버깅 지옥

  • 문제
    • 어디서 쿼리 발생했는지 추적 어려움
    • 컨트롤러? 서비스? JSON 변환?
➡️ 로그 보면 응답 단계에서 쿼리 발생

3) ❌ N+1 문제 폭발

    • 회원 100명 응답
    • 각 회원마다 orders 조회
    • ➡️ 101개 쿼리 발생
      ➡️ 트래픽 많은 API → 바로 성능 장애

4) ❌ DB 커넥션 오래 점유

  • OSIV = true
    • 요청 내내 커넥션 유지
    • 응답 JSON 만드는 동안도 유지
➡️ 느린 API → 커넥션 부족
➡️ 대규모 서비스에서 치명적

💠 핵심 요약 (REST API 위험 포인트)

“엔티티를 그대로 반환하면 JSON 변환 과정에서 Lazy 로딩 쿼리가 터진다.”
“OSIV = true 면 그 쿼리가 실제로 실행된다.”
➡️ 그래서 위험

OSIV = false & @Transactional

1️⃣ 핵심 원칙

1) DB 접근은 Service에서 끝낸다

  • 필요한 데이터 모두 조회
  • Lazy 문제 해결
  • 트랜잭션 안에서 처리

2) Controller는 완성된 데이터만 다룬다

  • DB 접근 X
  • Lazy 로딩 X
  • 안전

3) Entity 직접 반환 안 함

보통 이렇게:
EntityDTO 변환 → DTO 반환

※ 왜 DTO를 쓸까?

  • JSON 변환 중 Lazy 접근 없음
  • 쿼리 예측 가능
  • API 스펙 안정
  • 보안 안전
Share article