F-LAB JAVA · 7주차 · Phase 5 · 수동 트랜잭션의 한계
🔧 Phase 5 시작 + 🔄 Part B 시작 — 트랜잭션 추상화의 진화
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
수동 트랜잭션 코드 (tx.begin / try / commit / catch / rollback / finally / close) 는 6주차 ACID 의 자바 코드화로 비즈니스 로직과 인프라 코드 (트랜잭션 관리) 가 한 메서드에 섞여 5주차 SoC (관심사 분리) 를 위반하고 모든 메서드에 같은 패턴이 반복되는 보일러플레이트 폭증을 일으키며, 이 결합을 분리하려는 욕구가 @Transactional 등장의 직접적 동기다.
수동 트랜잭션 은 개발자가 트랜잭션 시작·커밋·롤백을 명시적으로 코딩 하는 방식이다.
6주차에서 학습한 ACID (Atomicity / Consistency / Isolation / Durability) 를 자바로 직접 구현하는 패턴 —tx.begin()(시작) →try(비즈니스 로직) →tx.commit()(성공 시 커밋) →catch(예외) →tx.rollback()(실패 시 롤백) →finally(close) — 가 한 메서드에 모두 있다.
결정적 문제 3가지 — (1) SoC 위반 — 비즈니스 로직 (3 줄) 과 인프라 코드 (15 줄) 가 한 메서드 (5주차 디자인 패턴 위반), (2) 보일러플레이트 폭증 — 모든 메서드에 같은 try/catch/finally 패턴 반복 (1020 메서드 → 같은 코드 ×1020), (3) 실수 위험 — rollback 누락·commit 위치 오류·중첩 트랜잭션 어려움.
5주차 디자인 패턴의 정신 (DI / OCP / 템플릿+전략) 으로 보면 — "변하는 부분 (비즈니스 로직) 과 변하지 않는 부분 (트랜잭션 시작·커밋·롤백) 을 분리 해야 하는데 수동 방식은 분리 X.
이 결합을 풀려는 욕구가 @Transactional 의 등장 동기 — 어노테이션 1줄로 트랜잭션 자동화 + 5주차 패턴의 결정체 + 6주차 ACID 의 추상화 (Phase 7 ★ 깊이).
수동 트랜잭션 = 셰프가 매번 식기 세척:
상황:
- 셰프 (개발자) 의 본업: 요리 (비즈니스 로직)
- 보조 작업: 식기 세척 (트랜잭션 관리)
수동:
매 요리 시:
1. 식기 가져오기 (tx.begin)
2. 요리 (비즈니스 로직)
3. 손님 만족 → 식기 정리 (commit)
4. 사고 → 식기 안 정리 (rollback)
5. 식기 반납 (close)
문제:
- 본업 (요리) 보다 보조 (식기) 더 많음
- 모든 요리에 같은 식기 작업
- 매번 같은 일
이상적 (@Transactional):
- 셰프는 요리만
- 식기 관리는 자동 (도와주는 직원)
- "이 식당은 자동" 선언만
5주차 디자인 패턴 정신:
- 변하는 부분: 요리 (메뉴별)
- 변하지 않는 부분: 식기 작업
- 분리해야
수동의 문제 3가지:
1. SoC 위반 (한 사람이 모두)
2. 보일러플레이트 (같은 일 반복)
3. 실수 위험 (식기 누락 등)
6주차 ACID 의 코드화:
- try { ... commit }
- catch { rollback }
- finally { close }
- = 식기 관리 패턴
@Transactional 예고:
- 어노테이션 1줄
- 셰프는 요리만
- 자동화
ILIC:
- 수동 → 1020 메서드 × 같은 패턴 = 폭증
- @Transactional → 어노테이션만
→ 수동 트랜잭션 = 본업 + 인프라 결합, SoC 위반, 보일러플레이트, @Transactional 동기.
1. 수동 트랜잭션 정의
2. 6주차 ACID 의 코드화
3. tx.begin / commit / rollback
4. 비즈니스 로직 + 인프라 혼재
5. SoC 위반 (5주차)
6. 보일러플레이트 폭증
7. 같은 패턴이 모든 메서드에 반복
8. 트랜잭션 자동화 동기
9. @Transactional 예고
수동 트랜잭션:
개발자가 직접 트랜잭션 시작 / 커밋 / 롤백을
명시적으로 코딩:
- tx.begin()
- tx.commit()
- tx.rollback()
- try / catch / finally
→ "손으로 트랜잭션 관리"
// JDBC 직접 (6주차)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 트랜잭션 시작
// 비즈니스 로직
update(conn, fromId, amount.negate());
update(conn, toId, amount);
conn.commit(); // 성공 시 커밋
} catch (Exception e) {
try {
if (conn != null) conn.rollback(); // 실패 시 롤백
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
} finally {
try {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
} catch (SQLException e) {
// 무시
}
}
}
DataSource dataSource;
interface DataSource { Connection getConnection() throws SQLException; }
void update(Connection conn, Long id, java.math.BigDecimal amount) {}
// JPA 의 수동 트랜잭션
public void transfer(Long fromId, Long toId, BigDecimal amount) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin(); // 트랜잭션 시작
// 비즈니스 로직
Account from = em.find(Account.class, fromId);
Account to = em.find(Account.class, toId);
from.withdraw(amount);
to.deposit(amount);
tx.commit(); // 성공 시 커밋
} catch (Exception e) {
if (tx.isActive()) tx.rollback(); // 실패 시 롤백
throw new RuntimeException(e);
} finally {
em.close(); // 자원 정리
}
}
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
<T> T find(Class<T> c, Object id) { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
class Account {
void withdraw(java.math.BigDecimal amount) {}
void deposit(java.math.BigDecimal amount) {}
}
공통 패턴:
begin / try / commit / catch / rollback / finally / close
- 7 줄 (트랜잭션 관리)
- vs 비즈니스 로직 (3-5 줄)
- 본말 전도
// ILIC 가 수동 트랜잭션이면 (가정)
@Service
public class ShipmentService {
@Autowired EntityManagerFactory emf;
public void processShipment(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// 비즈니스 로직 (3 줄)
Shipment s = em.find(Shipment.class, id);
s.markAsShipped();
em.merge(s);
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw new RuntimeException(e);
} finally {
em.close();
}
}
// 1020 메서드 모두 비슷한 패턴!
}
class Shipment { void markAsShipped() {} }
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
<T> T find(Class<T> c, Object id) { return null; }
<T> T merge(T t) { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
수동 트랜잭션의 정의는?
답:
1. 수동:
패턴:
JDBC / JPA:
부담:
6주차 학습 회상 (ACID):
Atomicity (원자성):
- 모두 성공 / 모두 실패
- 부분 성공 X
Consistency (일관성):
- 트랜잭션 전후 상태 일관
Isolation (격리성):
- 동시 실행 시 영향 X
Durability (지속성):
- commit 후 영구
→ DB 의 핵심 원칙
ACID → 자바 코드:
Atomicity:
- try { ... commit } catch { rollback }
- 모두 성공 (commit) 또는 모두 실패 (rollback)
Consistency:
- 비즈니스 규칙 / 제약
- DB / 애플리케이션 검증
Isolation:
- tx.begin (격리 레벨 명시)
- DB 의 락 / MVCC
Durability:
- commit 시 DB 가 보장
- WAL / fsync
→ 트랜잭션 코드 = ACID 구현
의미:
begin:
- 트랜잭션 경계 시작
- 격리 시작
- 메모리에서 변경 추적
commit:
- 트랜잭션 성공
- 변경 영구화 (Durability)
- 다른 트랜잭션에 visible
rollback:
- 트랜잭션 실패
- 변경 되돌림 (Atomicity)
- 메모리 정리
6주차 인프라 위에:
Phase 4 — Connection Pool (HikariCP)
Phase 5 — DataSource (인터페이스)
Phase 6 — ACID 원칙
Phase 7 — JdbcTemplate (SQL Mapper)
7주차 Part B:
- 위 인프라 위에
- 트랜잭션 추상화
- 수동 → 자동
// ILIC 의 ACID 자바 코드 (수동, 가정)
@Service
public class TransferService {
@Autowired DataSource dataSource;
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // ① ACID 의 begin
// ② 비즈니스 로직 (Atomicity 필요)
updateBalance(conn, fromId, amount.negate());
updateBalance(conn, toId, amount);
conn.commit(); // ③ Atomicity + Durability
// 둘 다 성공 → 모두 반영
} catch (Exception e) {
try {
if (conn != null) conn.rollback(); // ④ Atomicity
// 하나라도 실패 → 모두 취소
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
} finally {
close(conn);
}
}
}
DataSource dataSource;
interface DataSource { Connection getConnection() throws SQLException; }
void updateBalance(Connection conn, Long id, java.math.BigDecimal a) {}
void close(Connection conn) {}
6주차 ACID 의 코드화는?
답:
1. ACID:
자바 코드화:
begin / commit / rollback:
6주차 인프라:
// 전형적 수동 트랜잭션 패턴
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// === 비즈니스 로직 ===
// (이 부분만 매번 다름)
tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
패턴의 7 단계:
① em / conn 획득
② tx 객체
③ tx.begin (또는 setAutoCommit(false))
④ try (비즈니스 로직)
⑤ tx.commit
⑥ catch + rollback
⑦ finally + close
→ 항상 같음 (변하지 않는 부분)
비즈니스 로직만 변함:
매 메서드:
- 위 7 단계 동일
- 비즈니스 로직만 다름
비유:
- "옷 같음 (트랜잭션 패턴)"
- "사람 다름 (비즈니스 로직)"
5주차 패턴:
- 변하는 부분 ↔ 변하지 않는 부분
- 분리해야 (템플릿+전략)
// 단순 메서드도 7 단계 매번
public Shipment get(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Shipment s = em.find(Shipment.class, id); // 1 줄
tx.commit();
return s;
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw new RuntimeException(e);
} finally {
em.close();
}
}
// 비즈니스 로직 1 줄 vs 트랜잭션 관리 ~15 줄
// 본말 전도
class Shipment {}
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
<T> T find(Class<T> c, Object id) { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
// ILIC 의 수동 패턴 (가정)
@Service
public class ShipmentService {
@Autowired EntityManagerFactory emf;
// 모든 메서드 같은 7 단계
public Shipment get(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Shipment s = em.find(Shipment.class, id); // 비즈니스
tx.commit();
return s;
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw new RuntimeException(e);
} finally {
em.close();
}
}
public void update(Shipment s) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
em.merge(s); // 비즈니스
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw new RuntimeException(e);
} finally {
em.close();
}
}
// 같은 패턴 × N 메서드
}
class Shipment {}
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
<T> T find(Class<T> c, Object id) { return null; }
<T> T merge(T t) { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
tx.begin / commit / rollback 패턴은?
답:
1. 패턴:
항상 같음:
비즈니스:
5주차:
한 메서드에 두 관심사:
1. 비즈니스 관심사:
- "Shipment 를 SHIPPED 로"
- 도메인 의미
2. 인프라 관심사:
- 트랜잭션 시작 / 커밋 / 롤백
- 자원 관리
→ 한 메서드에 섞임
코드 비율:
비즈니스 로직: 3-5 줄
인프라 코드 (트랜잭션): 12-15 줄
비율: 인프라 > 비즈니스
→ 본말 전도
가독성 ↓:
메서드 읽을 때:
- "이 메서드 뭐 하는가?"
- 비즈니스 로직 찾기 어려움
- try/catch 사이에 묻힘
의도 파악 ↓
수정 부담 ↑:
비즈니스 로직 수정:
- try/catch 사이 코드 찾기
- 주변 인프라 코드 노이즈
트랜잭션 정책 수정:
- 모든 메서드 수정 필요
- 누락 위험
유지보수 ↓
5주차 정신 위반:
5주차 학습:
- DI (Dependency Injection)
- OCP (Open-Closed Principle)
- SoC (Separation of Concerns)
- 템플릿+전략
수동 트랜잭션은:
- 비즈니스 + 인프라 결합
- SoC 위반
- 5주차 가르침 위반
// ILIC 메서드 (수동) - 두 관심사 혼재 (가정)
public void processShipment(Long id, String reason) {
EntityManager em = emf.createEntityManager(); // 인프라
EntityTransaction tx = em.getTransaction(); // 인프라
try {
tx.begin(); // 인프라
// === 비즈니스 ===
Shipment s = em.find(Shipment.class, id); // 비즈니스
if (!s.canShip()) { // 비즈니스
throw new IllegalStateException(reason); // 비즈니스
}
s.markAsShipped(); // 비즈니스
em.merge(s); // 비즈니스
// === 비즈니스 끝 ===
tx.commit(); // 인프라
} catch (Exception e) { // 인프라
if (tx.isActive()) tx.rollback(); // 인프라
throw new RuntimeException(e); // 인프라
} finally { // 인프라
em.close(); // 인프라
}
}
// 비즈니스 5 줄, 인프라 12 줄
// 비즈니스 의도 묻힘
class Shipment {
boolean canShip() { return false; }
void markAsShipped() {}
}
EntityManagerFactory emf;
class EntityManagerFactory { EntityManager createEntityManager() { return null; } }
class EntityManager {
EntityTransaction getTransaction() { return null; }
<T> T find(Class<T> c, Object id) { return null; }
<T> T merge(T t) { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
비즈니스 로직 + 인프라 혼재 문제는?
답:
1. 혼재:
비율:
가독성:
5주차 정신:
SoC (Separation of Concerns):
관심사 분리:
- 각 코드 = 한 가지 책임
- 다른 관심사는 분리
- 모듈성 ↑
- 유지보수 ↑
5주차 학습:
- 5주차 디자인 패턴의 기본
- SOLID 의 SRP (단일 책임)
수동 트랜잭션 = SoC 위반:
한 메서드:
- 비즈니스 책임
- 트랜잭션 관리 책임
- 자원 관리 책임
→ 3 책임 = SRP 위반
→ 5주차 정신 위반
분리 했을 때 (이상):
비즈니스 코드:
- 도메인 로직만
- 깔끔
- 의도 명확
트랜잭션 관리:
- 별도 위치 (어노테이션 / AOP)
- 자동
- 인프라 책임
→ 분리 = 깔끔
6주차 → 7주차 진화:
6주차 JdbcTemplate:
- 자원 관리만 분리 (Connection)
- 트랜잭션은 여전히 수동
7주차 @Transactional:
- 트랜잭션도 분리
- 어노테이션 1줄
- 완전한 SoC
→ 추상화 진화
5주차 패턴 매칭:
- 템플릿+전략 패턴:
- 변하지 않는 부분 (템플릿)
- 변하는 부분 (전략)
수동 트랜잭션:
- 변하지 않는 부분: 트랜잭션 패턴 (begin/commit/rollback)
- 변하는 부분: 비즈니스 로직
→ 템플릿+전략 적용 가능
→ JdbcTemplate (6주차) 와 같은 사상
// ILIC 의 SoC 분리 (이상)
// Bad (수동 트랜잭션)
public void processShipment(Long id) {
EntityManager em = ...;
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// 비즈니스 로직
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw e;
} finally {
em.close();
}
}
// Good (@Transactional, 미리)
@Transactional
public void processShipment(Long id) {
// 비즈니스 로직만!
Shipment s = repo.findById(id).orElseThrow();
s.markAsShipped();
}
// → 비즈니스만
// → 트랜잭션 분리 (인프라)
// → SoC ✓
class Shipment { void markAsShipped() {} }
EntityManager em;
class EntityManager {
EntityTransaction getTransaction() { return null; }
void close() {}
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
@interface Transactional {}
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
SoC 위반 (5주차) 은?
답:
1. SoC:
수동 = 위반:
분리 시:
5주차 패턴:
보일러플레이트:
같은 코드의 반복:
- 의미 없는 반복
- 같은 패턴
- 시간 낭비
수동 트랜잭션:
- 모든 메서드에 같은 7 단계
- 폭증
메서드별 부담:
1 메서드:
- 비즈니스 3 줄
- 트랜잭션 12 줄
- 총 15 줄
10 메서드:
- 비즈니스 30 줄
- 트랜잭션 120 줄
- 총 150 줄
100 메서드:
- 비즈니스 300 줄
- 트랜잭션 1200 줄
- 총 1500 줄
ILIC 의 규모:
102 테이블 × 평균 10 메서드 = 1020 메서드
수동 트랜잭션:
- 메서드당 트랜잭션 12 줄
- 총 1020 × 12 = 12,240 줄 보일러플레이트
@Transactional:
- 어노테이션 1 줄
- 총 1020 줄
→ 12,000 줄 절약!
코드 품질:
보일러플레이트 ↑:
- 가독성 ↓
- 유지보수 ↓
- 버그 ↑
깔끔 코드:
- 비즈니스만
- 의도 명확
- 안전
변경의 부담:
트랜잭션 정책 변경 시 (예: 격리 레벨):
- 모든 메서드 수정 필요
- 1020 곳 변경
- 누락 위험
- 시간 폭증
@Transactional:
- 어노테이션 옵션만
- 또는 클래스 레벨
// ILIC 의 보일러플레이트 (수동)
// 메서드 1
public void method1(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// 비즈니스 1
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw e;
} finally {
em.close();
}
}
// 메서드 2 (같은 패턴!)
public void method2(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// 비즈니스 2
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw e;
} finally {
em.close();
}
}
// ... 메서드 1020개 모두 같은 패턴
// 시간 / 인지 부담 / 실수 위험 폭증
수동 트랜잭션의 보일러플레이트는?
답:
1. 보일러플레이트:
메서드별:
ILIC:
부담:
반복의 위험:
1. 실수 위험:
- rollback 누락
- close 누락
- 누수
2. 일관성 위험:
- 일부 다르게 작성
- 어떤 메서드는 try-with-resources
- 어떤 메서드는 try/catch/finally
3. 변경 위험:
- 정책 변경 시 모든 메서드 수정
- 누락
// 실수 1: rollback 누락
try {
tx.begin();
// 비즈니스
tx.commit();
} catch (Exception e) {
// tx.rollback(); ← 빠뜨림!
throw e;
}
// 트랜잭션 leak
// 실수 2: close 누락
try {
// ...
tx.commit();
} catch (Exception e) {
tx.rollback();
}
// em.close(); ← 빠뜨림!
// 메모리 누수
// 실수 3: commit 후 코드
try {
tx.begin();
// 비즈니스
tx.commit();
// 이후 작업 (트랜잭션 X)
sendEmail(); // ← 메일 보내고 실패 시?
} catch (Exception e) {
tx.rollback(); // 이미 commit, 의미 X
}
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
}
EntityTransaction tx;
void sendEmail() {}
// 같은 코드 다르게 (일관성 X)
// 메서드 A
try {
tx.begin();
// ...
tx.commit();
} catch (Exception e) {
if (tx.isActive()) tx.rollback();
throw new RuntimeException(e);
} finally {
em.close();
}
// 메서드 B (조금 다름)
tx.begin();
try {
// ...
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
} finally {
if (em.isOpen()) em.close();
}
// 두 메서드 미묘하게 다름
// 일관성 ↓
EntityTransaction tx;
class EntityTransaction {
void begin() {}
void commit() {}
void rollback() {}
boolean isActive() { return false; }
}
EntityManager em;
class EntityManager {
void close() {}
boolean isOpen() { return false; }
}
코드 리뷰 부담:
매 PR:
- 모든 메서드의 트랜잭션 코드 확인
- 누락 / 일관성 / 실수 검사
- 시간 ↑
자동화 시:
- @Transactional 만 확인
- 빠르고 안전
ILIC 의 위험 시나리오 (수동, 가정)
ILIC 의 1020 메서드:
- 모두 트랜잭션 코드 반복
- 일부 미묘하게 다름
- 일부 실수 (rollback 누락 등)
- 운영 사고 위험
박승제 의 코드 리뷰 부담:
- 매 PR 트랜잭션 확인
- 시간 ↑
- 놓침 가능
→ @Transactional 도입 동기
@Transactional 시:
- 어노테이션만 확인
- 누락 X (어노테이션 없으면 명백)
- 일관 (Spring 이 처리)
- 안전
같은 패턴이 모든 메서드에 반복의 폐해는?
답:
1. 실수 위험:
일관성 ↓:
변경 부담:
리뷰 부담:
자연스러운 욕구:
수동 트랜잭션 사용하다 보면:
"트랜잭션을 자동화할 순 없을까?"
"이 패턴 반복 안 했으면..."
"비즈니스만 작성하고 싶다"
→ 자동화 필요
5주차 패턴의 답:
템플릿+전략:
- 변하지 않는 부분 (템플릿)
- 변하는 부분 (전략)
트랜잭션:
- 변하지 않는 부분: begin/commit/rollback
- 변하는 부분: 비즈니스
적용:
- 템플릿: TransactionTemplate (Spring)
- 또는 @Transactional (더 자동)
6주차 ↔ 7주차 같은 사상:
6주차 JdbcTemplate:
- 변하지 않는 부분: Connection / Statement / ResultSet 관리
- 변하는 부분: SQL + RowMapper
- 자원 관리 자동
7주차 @Transactional:
- 변하지 않는 부분: tx.begin/commit/rollback
- 변하는 부분: 비즈니스 로직
- 트랜잭션 자동
→ 같은 정신 (분리 + 자동화)
자동화의 가치:
1. 코드 ↓:
- 보일러플레이트 X
- 비즈니스만
2. 안전 ↑:
- 실수 X (rollback 누락)
- 일관
3. 변경 ↑:
- 정책 변경 쉬움
- 한 곳 (어노테이션 옵션)
4. SoC ✓:
- 비즈니스 / 인프라 분리
5. 5주차 정신:
- 디자인 패턴 결정체
Spring 의 답:
1. TransactionTemplate (수동 + 자동 중간):
- 람다로 비즈니스 전달
- 트랜잭션 자동
2. @Transactional (완전 자동):
- 어노테이션 1 줄
- AOP / 프록시 사용
- 가장 자동
→ @Transactional 표준
// TransactionTemplate (수동과 자동 사이)
@Autowired TransactionTemplate transactionTemplate;
public void process(Long id) {
transactionTemplate.execute(status -> {
// 비즈니스만!
Shipment s = repo.findById(id).orElseThrow();
s.markAsShipped();
return null;
});
// 자동 commit / rollback
}
// 람다 패턴 + 5주차
class TransactionTemplate {
<T> T execute(java.util.function.Function<Object, T> f) { return null; }
}
TransactionTemplate transactionTemplate;
class Shipment { void markAsShipped() {} }
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
// @Transactional (완전 자동)
@Service
public class ShipmentService {
@Autowired ShipmentRepository repo;
@Transactional // 이거 1 줄
public void process(Long id) {
// 비즈니스만!
Shipment s = repo.findById(id).orElseThrow();
s.markAsShipped();
// 자동:
// - tx.begin (전)
// - 메서드 실행
// - 정상 → commit
// - 예외 → rollback
// - 자원 close
}
}
// 어노테이션 1 줄
// SoC 완전
// 보일러플레이트 X
class Shipment { void markAsShipped() {} }
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
@interface Transactional {}
ILIC 의 @Transactional 도입
ILIC = @Transactional 사용 (실제):
- 102 테이블 × 1020 메서드
- 모두 @Transactional 어노테이션
- 비즈니스 로직만
코드 양:
- 수동: 15,000 줄+ 트랜잭션
- @Transactional: ~1,020 줄 (어노테이션만)
- 90% 절약
안정성:
- rollback 누락 X
- 일관 패턴
- 검증 (어노테이션 = AOP)
생산성:
- 비즈니스 집중
- 박승제 + 1 풀스택 = 2 명 운영 가능
- 102 테이블 관리
→ Spring 의 결정적 가치
트랜잭션 자동화의 동기는?
답:
1. 욕구:
5주차 패턴:
6주차 사상:
Spring 답:
Phase 5-7 흐름:
Phase 5 — 수동의 한계
- 5.1 결합 문제 ← 여기
- 5.2 수동의 3 함정
Phase 6 — PlatformTransactionManager
- 6.1 인터페이스 추상화
- 6.2 3 구현체
- 6.3 사용 전후 비교
Phase 7 — @Transactional (★ 모두)
- 7.1 프록시 패턴 ★
- 7.2 동작 원리 ★★★
- 7.3 5가지 함정 ★
→ 진화: 수동 → 추상화 → 자동화
Part B 의 가치:
- 6주차 ACID 의 추상화 (자동)
- 5주차 디자인 패턴의 결정체
- 자바 백엔드의 또 다른 정점
- 면접 단골 (프록시 / AOP / 함정)
특히 Phase 7:
- 7.2 ★★★
- @Transactional 의 동작 원리
- 프록시 + AOP
- 백엔드의 핵심
5주차 + 6주차 + 7주차 Part B:
5주차 (디자인 패턴):
- DI / OCP / 템플릿+전략
- 프록시 패턴
6주차 (DB 접근):
- DataSource / ACID / JdbcTemplate
7주차 Part B:
- DataSource (6) + 디자인 패턴 (5)
- = PlatformTransactionManager 추상화
- + 프록시 (5) + 트랜잭션 (6)
- = @Transactional 동작 원리
→ 모든 학습의 응축
박승제의 학습 의의
ILIC 의 @Transactional:
- 매일 사용 (102 테이블)
- 동작 원리는?
- 함정은?
- 효과적 사용?
Phase 7 학습으로:
- 원리 이해
- 함정 회피
- 최적 활용
→ 운영 안정성
→ 디버깅 능력
→ 면접 대비
면접 단골 (Phase 7):
- @Transactional 동작 원리?
- 프록시 패턴?
- AOP?
- 5가지 함정?
1. private 메서드
2. self-invocation
3. checked exception
4. 트랜잭션 전파
5. readOnly
| Q | 핵심 답변 |
|---|---|
| 수동 트랜잭션? | tx.begin/commit/rollback |
| 패턴? | 7 단계 |
| ACID 코드화? | try/catch/finally |
| 혼재? | 비즈니스 + 인프라 |
| SoC 위반? | 5주차 정신 |
| 보일러플레이트? | 12,000 줄 |
| 실수? | rollback/close 누락 |
| 자동화 동기? | 5주차 정신 |
| Spring 답? | @Transactional |
| Phase 7? | ★ 깊이 |
답:
답:
답:
답:
답:
1. 수동 트랜잭션 = 결합 코드
2. 결정적 문제 3가지
3. 자동화의 동기
이번 Unit에서 수동의 결합을 봤다면, 다음은 3가지 함정 (Phase 5 마지막).
🔧 Phase 5 — 수동 트랜잭션의 한계
✅ Unit 5.1 트랜잭션이 비즈니스 로직과 결합 ← 여기
⏭ Unit 5.2 수동 트랜잭션 3가지 함정 — Phase 5 완주
🗂️ Part A — 데이터 모델링과 ORM
✅ Phase 1 (5)
✅ Phase 2 (2)
✅ Phase 3 (4)
✅ Phase 4 (5)
🔄 Part B — 트랜잭션 추상화의 진화
🔧 Phase 5 (1/2)
총: 17/24 Unit (71%)
🔧 Phase 5 시작 + 🔄 Part B 시작 — 트랜잭션 추상화의 진화