F-LAB JAVA · 6주차 · Phase 6 · 트랜잭션과 ACID
💎 Phase 6 시작 — 트랜잭션 ACID, 면접 단골 5개 ★깊이 집중
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
트랜잭션은 여러 작업을 한 단위로 묶어 모두 성공해서 영구 적용 (Commit) 되거나 모두 취소 (Rollback) 되도록 하는 단위로, 계좌 이체에서 "A 출금 + B 입금" 을 하나의 트랜잭션으로 묶어야 둘 중 하나만 일어나는 사고를 막을 수 있다.
트랜잭션 (Transaction) 은 한 단위의 작업으로 취급되는 모든 작업을 묶은 것이다.
대표 예시인 계좌 이체 는 (1) A 계좌에서 출금, (2) B 계좌에 입금 — 두 작업을 하나의 단위로 묶음 이 트랜잭션이다.
트랜잭션의 끝은 두 가지 — Commit (모든 변경을 영구 적용) 또는 Rollback (모든 변경을 취소) 으로, 중간 상태는 절대 외부에 노출되지 않는다.
"단위" 의 크기는 비즈니스 의미에 따라 정한다 — 게시글 작성 시 "게시글 INSERT + 첨부파일 INSERT + 알림 INSERT" 를 하나로 볼지 분리할지는 도메인 요구사항이 결정하며, 너무 길면 락 점유로 동시성 ↓, 너무 짧으면 일관성 위험이라 균형이 필요하다.
트랜잭션 = ATM 이체:
이체 작업 (한 단위):
1. A 계좌 출금 (-10만)
2. B 계좌 입금 (+10만)
→ 둘 다 성공 or 둘 다 실패
성공 (Commit):
- "이체 완료" 인쇄
- 영구 기록
- 누구도 못 되돌림
실패 (Rollback):
- "이체 실패" 인쇄
- A 계좌도 그대로 (출금 취소)
- 둘 다 처음 상태
중간 상태 (X):
- "A 만 출금" 상태 외부에 X
- 트랜잭션 진행 중엔 안 보임
단위 크기:
- "이체" = 출금 + 입금 (2개)
- 너무 크면: "한 달 거래" → 락 길게
- 너무 작으면: "출금" 만 → 일관성 깨짐
JDBC autocommit:
- 자동 commit (각 SQL = 1 트랜잭션)
- 의도적 묶으려면 false
→ 트랜잭션 = 한 단위 작업, Commit/Rollback 으로 끝, 단위는 비즈니스로 결정.
1. 트랜잭션 정의
2. 계좌 이체 예시
3. Commit
4. Rollback
5. 트랜잭션의 끝
6. 게시글 작성 단위
7. "단위" 의 크기
8. JDBC autocommit
9. Spring @Transactional
트랜잭션 (Transaction):
"한 단위의 작업" 으로
취급되는 모든 작업.
- 여러 SQL 을 묶음
- 전부 또는 전무
키워드:
- 단위 (Unit)
- 묶음 (Group)
- 전부 / 전무 (All or Nothing)
- 원자성 (Atomicity, Phase 6.2)
ACID (트랜잭션 4성질):
A - Atomicity (원자성)
C - Consistency (일관성)
I - Isolation (격리성)
D - Durability (지속성)
→ Phase 6.2~6.5 각각 ★깊이
트랜잭션 (ILIC)
ILIC 사례:
- 배송 예약 생성:
1. 예약 INSERT
2. 배송 INSERT (예약 참조)
3. 운임 계산 INSERT
→ 한 트랜잭션
- 모두 성공 or 모두 실패
- 중간 실패 시 전체 취소
- 데이터 일관성
→ ILIC 의 핵심
트랜잭션의 정의는?
답:
1. 정의:
묶음:
All or Nothing:
ACID:
계좌 이체:
A → B 10만원 이체
작업:
1. A 출금 -10만
2. B 입금 +10만
→ 한 트랜잭션
트랜잭션 없으면:
1. A 출금 ✅
2. B 입금 ❌ (장애)
→ A 만 10만 사라짐
→ 누구도 받지 못한 돈
→ 사고
트랜잭션 있으면:
1. A 출금 ✅
2. B 입금 ❌ (장애)
→ 자동 Rollback
→ A 도 원상복구
→ 둘 다 처음 상태
→ 안전
-- 계좌 이체 트랜잭션
BEGIN; -- 시작
UPDATE accounts SET balance = balance - 100000 WHERE id = 'A';
-- 만약 여기서 장애 → 자동 rollback
UPDATE accounts SET balance = balance + 100000 WHERE id = 'B';
COMMIT; -- 끝 (성공)
-- 또는
ROLLBACK; -- 끝 (실패, 취소)
// 계좌 이체 같은 트랜잭션 (ILIC)
@Service
public class ShipmentBookingService {
@Transactional // 트랜잭션 시작
public void createBookingWithShipment(Booking b, Shipment s) {
// 1. 예약 저장
bookingDao.add(b);
// 2. 배송 저장 (예약 참조)
s.setBookingId(b.getId());
shipmentDao.add(s);
// 3. 운임 계산 저장
BigDecimal freight = freightCalculator.calculate(s);
freightDao.save(s.getId(), freight);
// 모두 성공 → commit
// 하나라도 실패 → 전체 rollback
}
BookingDao bookingDao;
ShipmentDao shipmentDao;
FreightCalculator freightCalculator;
FreightDao freightDao;
}
class Booking { Long getId() { return null; } }
class Shipment { Long getId() { return null; } void setBookingId(Long id) {} }
class BookingDao { void add(Booking b) {} }
class ShipmentDao { void add(Shipment s) {} }
class FreightDao { void save(Long id, java.math.BigDecimal f) {} }
class FreightCalculator { java.math.BigDecimal calculate(Shipment s) { return null; } }
계좌 이체 예시의 트랜잭션 단위는?
답:
1. 시나리오:
없으면:
있으면:
SQL:
Commit:
트랜잭션 성공:
- 모든 변경 영구 적용
- 다른 세션도 봄 (격리 후)
- 되돌릴 수 없음
의미:
"확정":
- "이대로 가자"
- 변경 보존
- 트랜잭션 종료
Commit 동작:
1. 모든 변경 디스크 반영
2. 트랜잭션 로그 (WAL)
3. 락 해제
4. 트랜잭션 종료
5. 세션은 다음 트랜잭션 가능
// 명시적 commit
connection.setAutoCommit(false);
// SQL 들
connection.commit(); // 명시적
// 자동 commit (기본)
connection.setAutoCommit(true); // 기본
// 각 SQL = 자동 commit (트랜잭션 짧음)
// Commit (ILIC, Spring @Transactional)
@Service
public class ShipmentService {
@Transactional
public void processBooking(Booking b) {
shipmentDao.add(...);
bookingDao.update(...);
// 메서드 끝 → Spring 이 commit
// (예외 없으면)
}
}
// JDBC 직접 (덜 흔함)
public void processManual() throws SQLException {
try (Connection c = ds.getConnection()) {
c.setAutoCommit(false);
try {
// SQL 들
c.commit(); // 명시적
} catch (Exception e) {
c.rollback();
throw e;
}
}
}
class ShipmentDao { void add(Object o) {} }
class BookingDao { void update(Object o) {} }
class Booking {}
DataSource ds;
interface DataSource { Connection getConnection() throws SQLException; }
Commit 의 의미는?
답:
1. Commit:
의미:
동작:
명시/자동:
Rollback:
트랜잭션 실패/취소:
- 모든 변경 취소
- 처음 상태로
- 마치 없던 일처럼
의미:
"되돌리기":
- "이전 상태로"
- 변경 폐기
- 트랜잭션 종료
Rollback 발생:
1. 명시적 호출 (rollback())
2. 예외 발생 (Spring 자동)
3. 제약 위반 (DB 자동)
4. 연결 끊김
5. 데드락 감지
Rollback 동작:
1. 변경된 모든 데이터 복원
- undo log 사용
2. 트랜잭션 로그
3. 락 해제
4. 트랜잭션 종료
// Rollback (ILIC)
@Service
public class ShipmentService {
@Transactional
public void processBooking(Booking b) {
try {
shipmentDao.add(...);
if (b.invalid()) {
throw new IllegalArgumentException();
// → Spring 자동 rollback (RuntimeException)
}
bookingDao.update(...);
} catch (Exception e) {
// Spring 이 자동 rollback
throw e;
}
}
@Transactional
public void manualRollback(Booking b) {
shipmentDao.add(...);
// 비즈니스 검증
if (b.invalid()) {
// 명시적 rollback 마킹
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
ShipmentDao shipmentDao;
BookingDao bookingDao;
}
class Booking { boolean invalid() { return false; } }
class ShipmentDao { void add(Object o) {} }
class BookingDao { void update(Object o) {} }
class TransactionAspectSupport {
static TS currentTransactionStatus() { return null; }
interface TS { void setRollbackOnly(); }
}
Rollback 의 의미는?
답:
1. Rollback:
의미:
발생:
동작:
트랜잭션의 끝:
1. Commit (성공)
2. Rollback (실패/취소)
→ 두 가지 뿐
중간 상태 X:
트랜잭션 진행 중:
- 외부에 안 보임 (격리)
끝 (commit/rollback):
- 전부 보임 (commit)
- 또는 사라짐 (rollback)
→ 중간 외부 노출 X
상태 다이어그램:
[시작 (BEGIN)]
↓
[진행 중 (Active)]
↓
┌───┴───┐
↓ ↓
[Commit] [Rollback]
(영구) (취소)
강제 종료:
- 연결 끊김 → 자동 rollback
- 세션 강제 종료 (KILL) → rollback
- DB 다운 → rollback
- 타임아웃 → rollback
→ 모호한 상태 X
// 트랜잭션의 끝 (ILIC)
@Service
public class ShipmentService {
@Transactional
public void example(Shipment s) {
// 트랜잭션 시작 (BEGIN)
shipmentDao.add(s); // 진행 중
// 두 갈래:
// 1. 정상 종료 → commit
// 2. 예외 → rollback
// (메서드 끝)
}
// 중간 상태:
// - 다른 세션 X (격리, Phase 6.4)
// - 끝나면 전부 또는 전무
ShipmentDao shipmentDao;
}
class Shipment {}
class ShipmentDao { void add(Shipment s) {} }
트랜잭션의 끝 두 가지는?
답:
1. 두 가지:
중간 X:
다이어그램:
강제:
게시글 작성 트랜잭션:
작업:
1. 게시글 INSERT
2. 첨부 파일 INSERT
3. 알림 INSERT
4. 통계 UPDATE
단위는?
한 트랜잭션:
4 작업 하나로:
- 모두 성공 or 실패
- 일관성 강함
단점:
- 트랜잭션 김
- 락 점유 길어짐
분리:
게시글 + 첨부: 트랜잭션 1
알림: 별도 (비동기)
통계: 별도 (배치)
장점:
- 짧은 트랜잭션
- 동시성 ↑
단점:
- 일시적 불일치 가능
도메인 판단:
비즈니스 요구사항:
- 게시글 없는데 알림? X
- 통계는 즉시? 지연 OK?
→ 도메인이 결정
// 트랜잭션 단위 (ILIC 배송 예약)
@Service
public class BookingService {
// 강한 일관성 (한 트랜잭션)
@Transactional
public void createBooking(Booking b) {
bookingDao.add(b); // 필수
shipmentDao.add(...); // 필수
freightDao.save(...); // 필수
// 모두 한 단위 (이체와 같음)
}
// 분리 (비동기)
@Transactional
public void createBookingWithNotification(Booking b) {
createBooking(b); // 핵심 트랜잭션
// 알림은 비동기 (별도 트랜잭션)
notificationService.sendAsync(b);
// → 알림 실패해도 예약은 commit
}
BookingDao bookingDao;
ShipmentDao shipmentDao;
FreightDao freightDao;
NotificationService notificationService;
}
class Booking {}
class BookingDao { void add(Booking b) {} }
class ShipmentDao { void add(Object o) {} }
class FreightDao { void save(Object o) {} }
class NotificationService { void sendAsync(Booking b) {} }
게시글 작성 시 트랜잭션 단위는?
답:
1. 여러 작업:
한 트랜잭션:
분리:
도메인:
"단위" 의 크기:
너무 큼:
- 락 점유 길게
- 동시성 ↓
- 데드락 위험
너무 작음:
- 일관성 위험
- 부분 적용
→ 균형
크기 결정:
- 비즈니스 단위
- 일관성 요구
- 성능 (락 시간)
- 동시 사용자
짧은 트랜잭션 권장:
- 락 시간 ↓
- 동시성 ↑
- 데드락 ↓
- DB 부하 ↓
→ 가능하면 짧게
트랜잭션 안 비싼 작업 X:
❌ 트랜잭션 안에 외부 API 호출 (느림)
❌ 큰 파일 처리
❌ 메시지 큐 발행 (이벤트는 commit 후)
→ DB 작업만
// 트랜잭션 크기 (ILIC)
// ❌ 너무 큼 (외부 API 포함)
@Transactional
public void badProcess(Booking b) {
bookingDao.add(b);
// 외부 API (느림, 트랜잭션 길어짐!)
externalCustomsApi.notify(b); // 5초 걸림
shipmentDao.add(...);
// 트랜잭션 5초 이상 → 락 길게
}
// ✓ 짧게 (외부 API 분리)
@Transactional
public void goodProcess(Booking b) {
bookingDao.add(b);
shipmentDao.add(...);
// 짧은 트랜잭션
}
@Async // 별도 (트랜잭션 후)
public void notifyCustoms(Booking b) {
externalCustomsApi.notify(b); // 분리
}
// → 트랜잭션 짧게 (DB 만)
ShipmentDao shipmentDao;
BookingDao bookingDao;
ExternalCustomsApi externalCustomsApi;
class Booking {}
class ShipmentDao { void add(Object o) {} }
class BookingDao { void add(Booking b) {} }
class ExternalCustomsApi { void notify(Booking b) {} }
"단위" 의 크기 결정 기준은?
답:
1. 크기:
결정:
짧게:
외부 API:
JDBC autocommit:
Connection 의 모드:
- true (기본): 각 SQL = 자동 commit
- false: 명시적 commit/rollback
→ 트랜잭션 묶음 여부
// autocommit = true (기본)
Connection c = ds.getConnection();
// c.getAutoCommit() == true
c.executeUpdate("INSERT ..."); // 자동 commit
c.executeUpdate("UPDATE ..."); // 자동 commit
// 각각 별 트랜잭션
// autocommit = false (트랜잭션)
c.setAutoCommit(false);
try {
c.executeUpdate("INSERT ...");
c.executeUpdate("UPDATE ...");
c.commit(); // 명시적 commit
} catch (Exception e) {
c.rollback();
throw e;
}
Spring 의 처리:
@Transactional:
- 메서드 시작 시 autoCommit = false
- 메서드 끝 시 commit (또는 rollback)
- 메서드 끝나면 autoCommit 복원
→ 개발자가 직접 안 함
// autocommit (ILIC)
// Spring @Transactional (권장)
@Transactional
public void process(Booking b) {
// Spring 이 자동으로:
// 1. autoCommit = false
// 2. 메서드 실행
// 3. commit (성공) or rollback (예외)
// 4. autoCommit 복원
bookingDao.add(b);
shipmentDao.add(...);
}
// JDBC 직접 (덜 흔함, 부담)
public void manualProcess(Booking b) throws SQLException {
try (Connection c = ds.getConnection()) {
c.setAutoCommit(false);
try {
bookingDao.add(b);
shipmentDao.add(...);
c.commit();
} catch (Exception e) {
c.rollback();
throw e;
} finally {
c.setAutoCommit(true); // 복원
}
}
}
// → Spring 이 위 코드 자동
DataSource ds;
ShipmentDao shipmentDao;
BookingDao bookingDao;
class Booking {}
class ShipmentDao { void add(Object o) {} }
class BookingDao { void add(Booking b) {} }
interface DataSource { Connection getConnection() throws SQLException; }
JDBC autocommit 과의 관계는?
답:
1. autocommit:
true (기본):
false:
Spring:
@Transactional:
Spring 의 선언적 트랜잭션:
- 메서드/클래스에 붙임
- AOP 가 처리
- 코드 깔끔
@Service
public class ShipmentService {
@Transactional // 트랜잭션
public void process(Booking b) {
// 1. AOP 가 트랜잭션 시작 (autoCommit=false)
bookingDao.add(b);
shipmentDao.add(...);
// 2. AOP 가 commit (정상) or rollback (예외)
}
}
@Transactional 옵션:
- propagation: 전파 (REQUIRED 기본)
- isolation: 격리 수준
- timeout: 타임아웃
- readOnly: 읽기 전용
- rollbackFor: 롤백 예외 지정
자동 rollback:
RuntimeException / Error:
- Spring 자동 rollback
Checked Exception:
- 자동 rollback X
- rollbackFor 명시 필요
// Spring @Transactional (ILIC)
@Service
public class ShipmentService {
@Transactional
public void createBooking(Booking b) {
bookingDao.add(b);
shipmentDao.add(...);
// RuntimeException → 자동 rollback
}
@Transactional(readOnly = true)
public List<Shipment> findAll() {
return shipmentDao.findAll();
// 읽기 전용 (최적화)
}
@Transactional(timeout = 30)
public void heavyOperation() {
// 30초 타임아웃
}
@Transactional(rollbackFor = IOException.class)
public void withCheckedException() throws IOException {
// IOException 도 rollback
}
ShipmentDao shipmentDao;
BookingDao bookingDao;
}
class Booking {}
class Shipment {}
class ShipmentDao {
void add(Object o) {}
java.util.List<Shipment> findAll() { return null; }
}
class BookingDao { void add(Booking b) {} }
Spring @Transactional 은?
답:
1. @Transactional:
AOP:
옵션:
자동 rollback:
1. 트랜잭션
2. Commit / Rollback
3. 단위의 크기와 Spring
이번 Unit에서 트랜잭션 기본을 봤다면, 다음은 Atomicity (★ 깊이 파기).
💎 Phase 6 — 트랜잭션과 ACID
✅ Unit 6.1 트랜잭션이란 ← 여기
⏭ Unit 6.2 Atomicity ★깊이
⏭ Unit 6.3 Consistency ★깊이
⏭ Unit 6.4 Isolation ★깊이
⏭ Unit 6.5 Durability ★깊이
⏭ Unit 6.6 Commit 이전 동시성 ★깊이
🧪 Part A (9 Unit) ✅
💾 Part B — DB 접근의 진화
✅ Phase 3 — JDBC (3)
✅ Phase 4 — Connection Pool (4)
✅ Phase 5 — DataSource (4)
💎 Phase 6 — 트랜잭션 ACID (1/6)
총: 21/28 Unit
💎 Phase 6 시작 — 트랜잭션 ACID