Contents
1. 트랜잭션(Transaction)1️⃣ 트랜잭션(Transaction)이란?2️⃣ 비유로 이해하기3️⃣ 트랜잭션의 핵심 특징 (ACID 중 실무 감각만)4️⃣ 스프링에서 트랜잭션의 역할5️⃣ 사용하는 방법6️⃣ 트랜잭션이 필요한 이유7️⃣ 필기1. OSIV(Open Session In View)1️⃣ OSIV란?2️⃣ 문제 상황 이해하기3️⃣ 비유로 이해하기4️⃣ OSIV가 하는 일5️⃣ OSIV의 장단점6️⃣ 실무 적용7️⃣ 그림으로 이해하기※ Rest API에서 OSIV = true1️⃣ OSIV = true2️⃣ REST API3️⃣ OSIV = true 일 때 벌어지는 일💠 핵심 요약 (REST API 위험 포인트)※ OSIV = false & @Transactional1️⃣ 핵심 원칙1. 트랜잭션(Transaction)
1️⃣ 트랜잭션(Transaction)이란?
1) 한 줄 정의
“작업 묶음을 하나의 단위로 처리하는 것”
2️⃣ 비유로 이해하기
1) 은행 송금
- 내 계좌에서 돈 차감
- 상대 계좌에 돈 추가
➡️ 이 둘은 항상 같이 성공해야 함
- 차감만 되고 추가가 안 되면 문제
- 추가만 되고 차감이 안 돼도 문제
➡️ 그래서 둘을 하나의 묶음으로 처리 → 트랜잭션
3️⃣ 트랜잭션의 핵심 특징 (ACID 중 실무 감각만)
특징 | 쉬운 설명 |
원자성 | 전부 성공 / 전부 실패 |
일관성 | 데이터가 이상해지면 안 됨 |
격리성 | 동시에 작업해도 서로 방해 X |
지속성 | 성공하면 반드시 저장됨 |
4️⃣ 스프링에서 트랜잭션의 역할
스프링은 이런 걸 자동으로 처리해 줌
- DB 작업 시작
- 중간에 오류 나면 롤백(되돌림)
- 문제 없으면 커밋(저장)
5️⃣ 사용하는 방법
@Transactional
public void transfer() {
// DB 작업들
}“이 메서드 안의 DB 작업은 하나의 트랜잭션으로 처리해줘”
6️⃣ 트랜잭션이 필요한 이유
1) 트랜잭션 없을 때 문제점
- 일부만 저장됨
- 데이터 꼬임
- 복구 어려움
➡️ 실무에서는 거의 필수
7️⃣ 필기

1. OSIV(Open Session In View)
1️⃣ OSIV란?
1) 한 줄 정의
“트랜잭션이 끝난 뒤에도 DB 연결을 열어두는 방식”
2️⃣ 문제 상황 이해하기
스프링 + JPA 환경에서 자주 생기는 에러
LazyInitializationException
왜 생길까?
3️⃣ 비유로 이해하기
1) 상황
- DB에서 회원 조회
- 회원 → 주문 목록 있음 (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️⃣ 그림으로 이해하기

※ 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 직접 반환 안 함
보통 이렇게:
Entity → DTO 변환 → DTO 반환※ 왜 DTO를 쓸까?
- JSON 변환 중 Lazy 접근 없음
- 쿼리 예측 가능
- API 스펙 안정
- 보안 안전
Share article