7주차 Unit 7.3 — @Transactional 5가지 함정

Psj·2026년 6월 5일

F-lab

목록 보기
236/239

Unit 7.3 — @Transactional 5가지 함정

F-LAB JAVA · 7주차 · Phase 7 · @Transactional
★ 깊이 파기 — 7주차 마지막 Unit, Phase 7 완주 + Part B 완주 + 7주차 완주
+ 종합 졸업 시험 24문항


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 5가지 함정 개요는?
  • 함정 1 — private 메서드 왜 X?
  • 함정 2 — self-invocation 왜 X?
  • 함정 3 — checked exception 왜 rollback X?
  • 함정 4 — 트랜잭션 전파 (7가지) 는?
  • 함정 5 — readOnly 의 진짜 의미는?
  • 각 함정 해결 방법 은?
  • 종합 졸업 시험 24문항 답변은?
  • 7주차 완주 정리 는?

🎯 핵심 한 문장

@Transactional 의 5가지 함정 — (1) private 메서드 (CGLIB/JDK 동적 모두 가로채기 X) (2) self-invocation (같은 클래스 내 this 호출은 프록시 우회) (3) checked exception (기본 rollback 안 함, rollbackFor 명시 필요) (4) 트랜잭션 전파 7가지 (REQUIRED/REQUIRES_NEW/NESTED 등) (5) readOnly 의 실제 최적화 효과 — 모두 Unit 7.2 의 프록시 동작 원리에서 자연스럽게 파생되는 함정이며 ILIC 같은 실무 시스템의 운영 사고를 방지하는 핵심 지식이다.
@Transactional 의 5가지 함정 은 Unit 7.2 의 동작 원리에서 자연스럽게 파생되며 — 운영 사고와 직결되는 실무 핵심 지식이다.
함정 1 — private 메서드 — CGLIB 의 자식 클래스는 private 메서드를 오버라이드 X, JDK 동적 프록시는 인터페이스의 public 만 가로채기 → @Transactional 무시 → 트랜잭션 X → 운영 사고.
함정 2 — self-invocation — 같은 클래스 안에서 this.method() 호출 시 this 는 진짜 객체 (프록시 X) → 프록시 우회 → @Transactional 무시 → 외부 호출은 OK 지만 내부 호출은 X → 가장 흔한 함정.
함정 3 — checked exception — 기본 rollback 정책은 RuntimeException / Error 만 rollback, Checked Exception 은 commit → 비즈니스 예외가 Checked 면 데이터 불일치 → rollbackFor = Exception.class 명시 필요.
함정 4 — 트랜잭션 전파 7가지 — REQUIRED (기본, 참여) / REQUIRES_NEW (항상 새로) / NESTED (SAVEPOINT) / SUPPORTS / MANDATORY / NOT_SUPPORTED / NEVER — 의미 정확히 알아야 메서드 호출 체인의 트랜잭션 동작 예측 가능.
함정 5 — readOnly@Transactional(readOnly = true) 가 진짜 읽기 전용? — JPA 의 경우 Dirty Checking 비활성화 (성능 최적화) + Hibernate 의 flush mode = MANUAL + DB 수준 강제는 X (코드로 UPDATE 가능하지만 권장 X) — 의도된 사용.
ILIC 의 1020 메서드 운영에서 — 이 5 함정 회피 = 운영 안정성, 면접에서 자주 묻는 시니어 시험.

비유 — 자율주행의 5가지 함정

5가지 함정 = 자율주행의 5가지 함정:

[1] 비밀 통로 (private 메서드):
  - 자율주행 모듈 (프록시) 못 들어옴
  - 운전자 직접 운전 (트랜잭션 X)
  - 사고 위험

[2] 차고 안 운전 (self-invocation):
  - 차고 (같은 클래스) 안에선
  - 자율주행 비활성
  - "왜 트랜잭션 안 되지?"

[3] 약한 사고 (checked exception):
  - 자율주행: "RuntimeException 만 비상 정지"
  - Checked = "괜찮은 사고" 로 인식
  - rollback X

[4] 7 가지 운전 모드 (propagation):
  - REQUIRED (기본 모드)
  - REQUIRES_NEW (긴급 모드)
  - NESTED (서브 운전)
  - ...
  - 정확히 알아야 정확한 동작

[5] 읽기 전용 (readOnly):
  - "관광 모드" (구경만)
  - 실제로는 UPDATE 가능 (운전자 의지)
  - 최적화 (Dirty Checking X)

5 함정 모두 = Unit 7.2 의 동작 원리에서 파생:
  - 프록시 = 1, 2 함정
  - 예외 처리 = 3 함정
  - 트랜잭션 정책 = 4, 5 함정

ILIC 의 운영:
  - 5 함정 회피 = 안전
  - 1020 메서드 검토 시
  - 코드 리뷰 핵심

면접:
  - 5 함정 = 시니어 시험
  - 자주 묻는 주제

→ 5 함정, 프록시 원리에서 파생, 운영 안전, 면접 시니어.


🧭 9개 섹션 로드맵

1. 5가지 함정 개요
2. 함정 1 — private 메서드
3. 함정 2 — self-invocation
4. 함정 3 — checked exception
5. 함정 4 — 트랜잭션 전파 (7가지)
6. 함정 5 — readOnly
7. 종합 졸업 시험 24문항
8. 7주차 완주 정리
9. 8주차 예고

1️⃣ 5가지 함정 개요

1.1 5가지 함정 한눈에

함정원인결과해결
1. private 메서드프록시 가로채기 X@Transactional 무시public 변경 / 별도 클래스
2. self-invocationthis = 진짜 객체프록시 우회별도 클래스 / 자기 주입
3. checked exception기본 rollback X데이터 불일치rollbackFor 명시
4. propagation 오해7 종류 정책의도 다른 동작정확한 이해
5. readOnly최적화 vs 강제 X잘못된 이해의도 이해

1.2 모두 Unit 7.2 에서 파생

모두 Unit 7.2 에서 파생:

  함정 1, 2 (프록시 관련):
    - CGLIB 자식 클래스 한계
    - this = 진짜 객체
    - Unit 7.2 의 프록시 동작 원리에서

  함정 3 (예외):
    - TransactionInterceptor 의 rollbackOn
    - Unit 7.2 의 호출 흐름 단계 7

  함정 4 (전파):
    - createTransactionIfNecessary
    - Unit 7.2 의 propagation 처리

  함정 5 (readOnly):
    - PlatformTransactionManager 의 최적화
    - Unit 7.2 의 TransactionDefinition

→ Unit 7.2 이해 = 함정 자연 회피

1.3 운영 위험 비중

운영 위험 비중:

  함정 1 (private):
    - 발견 쉬움 (IDE 경고)
    - 운영 사고 적음

  함정 2 (self-invocation):
    - 가장 흔한 사고
    - 발견 어려움
    - 가장 위험

  함정 3 (checked):
    - Checked 거의 안 씀 (현대 자바)
    - 가끔 위험

  함정 4 (propagation):
    - 잘못된 동작
    - 자주 발생
    - 디버깅 어려움

  함정 5 (readOnly):
    - 최적화 의도
    - 사고 적음 (잘못 사용은 가능)

1.4 박승제의 가치

박승제의 가치:

  ILIC 의 1020 메서드:
    - 모두 @Transactional
    - 모두 함정 가능

  Unit 7.3 학습 후:
    - 5 함정 회피
    - 코드 리뷰 효율
    - 운영 안정
    - 면접 우위

  시니어 개발자:
    - 동작 원리 (7.2)
    - 함정 회피 (7.3)
    - 둘 다 마스터

1.5 자기 점검 답변

5가지 함정 개요는?

:
1. 5가지:

  • private/self-invocation/checked/propagation/readOnly
  1. 파생:

    • Unit 7.2 원리
  2. 위험:

    • 운영 사고
  3. 회피:

    • 깊은 이해

2️⃣ 함정 1 — private 메서드

2.1 함정 1 — private 메서드

함정 1 — private 메서드:

  @Transactional + private 메서드:
    - CGLIB / JDK 동적 모두 가로채기 X
    - @Transactional 무시
    - 트랜잭션 X

  원인:
    - CGLIB: 자식 클래스 (private 못 봄)
    - JDK 동적: 인터페이스 (private X)

2.2 코드 예시

// ❌ 함정
@Service
public class ShipmentService {
    
    @Transactional
    private void processInternal(Long id) {   // ← private!
        // 트랜잭션 적용 X
        // 프록시 가로채기 X
    }
}

// 외부 호출:
@Autowired ShipmentService service;
service.processInternal(1L);   // ← 어차피 private 라 호출 불가

// 내부 호출:
public void publicMethod() {
    processInternal(id);   // 호출 가능, 하지만 트랜잭션 X
}
@interface Service {}
@interface Transactional {}
@interface Autowired {}
ShipmentService service;
class ShipmentService {
    void processInternal(Long id) {}
    public void publicMethod() {}
}
Long id;

2.3 왜 동작 X?

왜 동작 X?:

  CGLIB 의 자식 클래스:
    class ShipmentService$$EnhancerByCGLIB extends ShipmentService {
        @Override
        public void publicMethod() {
            // 가로채기 OK
        }
        
        // private 메서드는 오버라이드 X!
        // (private 메서드는 상속 X)
        // → 가로채기 불가
    }

  JDK 동적 프록시:
    - 인터페이스의 public 메서드만
    - private 메서드는 인터페이스에 X

→ 둘 다 private 못 봄

2.4 IDE 경고

IDE 경고:

  IntelliJ + Spring:
    - @Transactional + private = 경고
    - "Transactional method called by..."
    - 코드 작성 시 발견

  하지만:
    - 경고 무시 가능
    - 실수 위험

2.5 해결 방법

// 해결 1: public 변경
@Service
public class ShipmentService {
    @Transactional
    public void processInternal(Long id) {   // private → public
        // 트랜잭션 OK
    }
}

// 해결 2: 별도 클래스로 분리
@Service
public class ShipmentService {
    @Autowired ShipmentInternalService internal;
    
    public void publicMethod(Long id) {
        internal.process(id);   // 다른 빈 (프록시 통과)
    }
}

@Service
public class ShipmentInternalService {
    @Transactional
    public void process(Long id) {   // public + 다른 클래스
        // 트랜잭션 OK
    }
}

// 해결 3: AspectJ 모드
// @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
// 바이트코드 위빙 (private 가로채기 가능)
// 복잡 / 거의 안 씀
@interface Service {}
@interface Transactional {}
@interface Autowired {}
class ShipmentInternalService { void process(Long id) {} }
ShipmentInternalService internal;
class ShipmentService {}
Long id;

2.6 ILIC 의 맥락

ILIC 의 함정 1 회피

ILIC 의 정책:
  - 모든 @Transactional 메서드 = public
  - IDE 경고 활용
  - 코드 리뷰 시 확인

  필요 시:
    - 별도 Service 분리
    - 책임 분리 (SoC)

→ 함정 1 거의 발생 X

2.7 자기 점검 답변

함정 1 — private 메서드 왜 X?

:
1. CGLIB:

  • 자식 클래스 오버라이드 X
  1. JDK 동적:

    • 인터페이스 X
  2. 결과:

    • @Transactional 무시
  3. 해결:

    • public / 별도 클래스

3️⃣ 함정 2 — self-invocation

3.1 함정 2 — self-invocation

함정 2 — self-invocation (자기 자신 호출):

  같은 클래스 내 메서드 호출:
    public void outer() {
        this.inner();   // ← 진짜 객체!
        // 프록시 우회
    }

  @Transactional
  public void inner() {
    // 외부에서 호출 시 OK
    // 같은 클래스 내 호출 시 X
  }

→ 가장 흔한 함정
→ 운영 사고 빈도 높음

3.2 왜 X?

왜 X?:

  Spring 의 동작:
    - 빈은 프록시
    - 외부에서 service.method() = 프록시 호출 ✓

  같은 클래스 내:
    public void outer() {
        // this = 진짜 객체 (프록시 X)
        // CGLIB 의 자식 클래스에서 super 호출 시
        // 또는 직접 메서드 호출 시
        this.inner();   // → 진짜 객체.inner()
        // 프록시 X
    }

→ this = 진짜 객체 (영원히)
→ 프록시 우회

3.3 시각화

시각화:

  외부 호출:
    Client → 프록시.outer() ✓
      ↓
      트랜잭션 시작 (프록시)
      ↓
      진짜.outer() (proceed)
        ↓
        this.inner()   ← 진짜.inner() (프록시 X)
        ↓
        @Transactional 무시!
      
      트랜잭션 commit (outer 의 것만)

  결과:
    - outer 는 트랜잭션 ✓
    - inner 의 @Transactional 무시
    - inner 가 propagation = REQUIRES_NEW 라도 X

3.4 코드 예시

// ❌ 함정 사례 (가장 흔한 시나리오)
@Service
public class OrderService {
    
    public void createOrderAndLog(Long id) {
        // 비즈니스 로직
        Order order = createOrder(id);
        
        // 로그는 별도 트랜잭션 (의도)
        this.logAction(order);   // ← self-invocation!
        // 의도: REQUIRES_NEW (새 트랜잭션)
        // 실제: 프록시 우회 → 같은 트랜잭션 / 또는 트랜잭션 X
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(Order order) {
        // 별도 트랜잭션 의도
        // 하지만 self-invocation 으로 우회
        // → 의도 동작 X
    }
}

// 외부 호출:
orderService.createOrderAndLog(1L);
// 1. 프록시.createOrderAndLog (트랜잭션 시작)
// 2. 진짜.createOrderAndLog 실행
// 3. this.logAction(order)  ← 진짜.logAction
// 4. logAction 의 @Transactional 무시 (프록시 우회)
// 5. 같은 트랜잭션 안에서 실행
class Order {}
@interface Service {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
Order createOrder(Long id) { return null; }
OrderService orderService;
class OrderService { void createOrderAndLog(Long id) {} }

3.5 해결 방법 1 — 별도 클래스 (권장)

// 해결 1: 별도 클래스 (권장)
@Service
public class OrderService {
    @Autowired LogService logService;   // 별도 빈
    
    public void createOrderAndLog(Long id) {
        Order order = createOrder(id);
        logService.logAction(order);   // ← 다른 빈 (프록시)
        // 정상 동작 (REQUIRES_NEW)
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(Order order) {
        // 별도 트랜잭션 OK
    }
}

// 장점:
// - 깔끔
// - SoC (책임 분리)
// - 프록시 정상 동작
class Order {}
@interface Service {}
@interface Autowired {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class LogService { void logAction(Order o) {} }
LogService logService;
class OrderService {}
Order createOrder(Long id) { return null; }

3.6 해결 방법 2 — 자기 자신 주입

// 해결 2: 자기 자신 주입 (덜 깔끔)
@Service
public class OrderService {
    @Autowired @Lazy OrderService self;   // 자기 자신 (프록시)
    
    public void createOrderAndLog(Long id) {
        Order order = createOrder(id);
        self.logAction(order);   // ← 프록시 호출!
        // 정상 동작
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(Order order) {
        // 별도 트랜잭션
    }
}

// 장점:
// - 같은 클래스 유지

// 단점:
// - @Lazy 필요 (순환 의존성 방지)
// - 약간 어색
class Order {}
@interface Service {}
@interface Autowired {}
@interface Lazy {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
OrderService self;
class OrderService { void createOrderAndLog(Long id) {} }
Order createOrder(Long id) { return null; }

3.7 해결 방법 3 — ApplicationContext

// 해결 3: ApplicationContext 로 자기 자신 (가능하지만 권장 X)
@Service
public class OrderService {
    @Autowired ApplicationContext ctx;
    
    public void createOrderAndLog(Long id) {
        Order order = createOrder(id);
        
        OrderService self = ctx.getBean(OrderService.class);   // 프록시
        self.logAction(order);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(Order order) {
        // 별도 트랜잭션
    }
}

// 단점:
// - ApplicationContext 의존
// - 테스트 어려움
class Order {}
class ApplicationContext { <T> T getBean(Class<T> c) { return null; } }
@interface Service {}
@interface Autowired {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class OrderService { void createOrderAndLog(Long id) {} }
Order createOrder(Long id) { return null; }
ApplicationContext ctx;

3.8 ILIC 의 맥락

// ILIC 의 함정 2 회피

// ❌ 잘못된 패턴 (가정)
@Service
public class ShipmentService {
    public void processWithLog(Long id) {
        process(id);
        this.logShipment(id);   // ← self-invocation!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logShipment(Long id) { ... }
}

// ✅ 권장 패턴
@Service
public class ShipmentService {
    @Autowired ShipmentLogService logService;
    
    @Transactional
    public void processWithLog(Long id) {
        process(id);
        logService.logShipment(id);   // 별도 빈 (프록시)
    }
}

@Service
public class ShipmentLogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logShipment(Long id) {
        // 별도 트랜잭션 OK
    }
}

// ILIC 의 1020 메서드:
// - 함정 2 빈번 발생 가능
// - 별도 Service 분리 표준
// - 코드 리뷰 핵심
@interface Service {}
@interface Autowired {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class ShipmentLogService { void logShipment(Long id) {} }
ShipmentLogService logService;
class ShipmentService { void process(Long id) {} }

3.9 자기 점검 답변

함정 2 — self-invocation 왜 X?

:
1. this:

  • 진짜 객체 (프록시 X)
  1. 결과:

    • @Transactional 무시
  2. 빈번:

    • 가장 흔한 함정
  3. 해결:

    • 별도 클래스 (권장)

4️⃣ 함정 3 — checked exception

4.1 함정 3 — checked exception

함정 3 — checked exception:

  Spring 의 기본 정책:
    - RuntimeException → rollback
    - Error → rollback
    - Checked Exception → commit (!!)

  의도와 다름:
    - "예외 = rollback" 일반 인식
    - 실제: Checked 는 commit

→ 데이터 불일치 위험

4.2 왜 이렇게?

왜 이렇게?:

  EJB 시대의 관례:
    - Checked = 비즈니스 예외 (정상)
    - RuntimeException = 시스템 예외 (비정상)

  Spring 이 계승:
    - Checked = commit (비즈니스 처리)
    - RuntimeException = rollback (시스템 오류)

  하지만:
    - 현대 자바 = Checked 거의 안 씀
    - 의도와 다름

4.3 코드 예시

// ❌ 함정
@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Long customerId) throws BusinessException {
        // 비즈니스 로직
        Customer c = customerRepo.findById(customerId).orElseThrow();
        
        if (!c.canOrder()) {
            throw new BusinessException("주문 불가");
            // Checked → commit (rollback X!)
            // 위 비즈니스 로직은 DB 에 반영!
        }
        
        // 정상 처리
    }
}

// 결과:
// - BusinessException 발생
// - 하지만 commit
// - 일부 데이터 영속 (이상한 상태)
class Customer { boolean canOrder() { return false; } }
class BusinessException extends Exception { BusinessException(String s) {} }
CustomerRepository customerRepo;
interface CustomerRepository { java.util.Optional<Customer> findById(Long id); }
@interface Service {}
@interface Transactional {}

4.4 해결 방법 — rollbackFor

// 해결: rollbackFor 명시
@Service
public class OrderService {
    
    @Transactional(rollbackFor = Exception.class)   // ← 모든 예외 rollback
    public void createOrder(Long customerId) throws BusinessException {
        Customer c = customerRepo.findById(customerId).orElseThrow();
        
        if (!c.canOrder()) {
            throw new BusinessException("주문 불가");
            // rollbackFor = Exception → rollback ✓
        }
    }
}

// 옵션:
@Transactional(rollbackFor = {BusinessException.class, SqlException.class})
// 또는
@Transactional(noRollbackFor = {SomeOtherException.class})
// (특정 예외는 commit 유지)
class Customer { boolean canOrder() { return false; } }
class BusinessException extends Exception {}
class SqlException extends Exception {}
class SomeOtherException extends Exception {}
@interface Service {}
@interface Transactional {
    Class<? extends Throwable>[] rollbackFor() default {};
    Class<? extends Throwable>[] noRollbackFor() default {};
}
CustomerRepository customerRepo;
interface CustomerRepository { java.util.Optional<Customer> findById(Long id); }

4.5 현대 자바의 권장 패턴

// 권장: RuntimeException 사용
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

@Service
public class OrderService {
    @Transactional   // rollbackFor 불필요 (RuntimeException → rollback)
    public void createOrder(Long customerId) {
        Customer c = customerRepo.findById(customerId).orElseThrow();
        
        if (!c.canOrder()) {
            throw new BusinessException("주문 불가");   // RuntimeException → rollback
        }
    }
}

// 장점:
// - throws 선언 불필요 (call site 깔끔)
// - 기본 rollback (의도와 일치)
// - 현대 자바 표준
class Customer { boolean canOrder() { return false; } }
@interface Service {}
@interface Transactional {}
CustomerRepository customerRepo;
interface CustomerRepository { java.util.Optional<Customer> findById(Long id); }

4.6 ILIC 의 맥락

// ILIC 의 표준 패턴

// ILIC 의 비즈니스 예외 = RuntimeException (권장)
public class ShipmentException extends RuntimeException {
    public ShipmentException(String message) {
        super(message);
    }
}

@Service
public class ShipmentService {
    @Autowired ShipmentRepository repo;
    
    @Transactional   // 기본 (RuntimeException → rollback)
    public void process(Long id) {
        Shipment s = repo.findById(id).orElseThrow();
        
        if (!s.canShip()) {
            throw new ShipmentException("선적 불가");
            // RuntimeException → rollback ✓
        }
        
        s.markAsShipped();
    }
}

// ILIC 의 정책:
// - 비즈니스 예외 = RuntimeException
// - throws 선언 불필요
// - 기본 rollback
// - rollbackFor 거의 명시 X

// 만약 외부 라이브러리의 Checked Exception 처리:
// - try/catch 로 변환
// - 또는 rollbackFor 명시
class Shipment {
    boolean canShip() { return false; }
    void markAsShipped() {}
}
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
@interface Service {}
@interface Autowired {}
@interface Transactional {}

4.7 자기 점검 답변

함정 3 — checked exception 왜 rollback X?

:
1. 기본:

  • RuntimeException 만 rollback
  1. Checked:

    • commit (의도 다름)
  2. EJB 관례:

    • 비즈니스 vs 시스템
  3. 해결:

    • rollbackFor / RuntimeException

5️⃣ 함정 4 — 트랜잭션 전파 (7가지)

5.1 7가지 전파 정책

Propagation설명
REQUIRED (기본)기존 트랜잭션 참여, 없으면 새로
REQUIRES_NEW항상 새 트랜잭션 (기존 보류)
NESTED기존 안에 SAVEPOINT (부분 rollback)
SUPPORTS기존 있으면 참여, 없으면 X
MANDATORY기존 필수 (없으면 예외)
NOT_SUPPORTED기존 보류, 트랜잭션 없이
NEVER기존 없어야 (있으면 예외)

5.2 REQUIRED (기본, 가장 흔함)

@Service
public class OrderService {
    @Autowired LogService logService;
    
    @Transactional   // REQUIRED (기본)
    public void createOrder() {
        // 트랜잭션 T1 시작
        
        logService.log();   // REQUIRED 면 T1 에 참여
        // 같은 트랜잭션
    }
}

@Service
public class LogService {
    @Transactional   // REQUIRED (기본)
    public void log() {
        // T1 에 참여 (새 트랜잭션 X)
    }
}

// 결과:
// - OrderService 와 LogService 가 같은 트랜잭션
// - 하나 실패 → 모두 rollback
@interface Service {}
@interface Autowired {}
@interface Transactional {}
class LogService { void log() {} }
LogService logService;

5.3 REQUIRES_NEW (별도 트랜잭션)

@Service
public class OrderService {
    @Transactional   // T1
    public void createOrder() {
        Order o = saveOrder();   // T1
        
        try {
            logService.log(o);   // T2 (REQUIRES_NEW)
            // T1 보류, T2 시작
            // T2 commit
            // T1 재개
        } catch (Exception e) {
            // T2 rollback
            // T1 은 계속
            log.warn("로그 실패");
        }
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(Order o) {
        // 별도 트랜잭션 (T1 과 독립)
    }
}

// 효과:
// - 로그가 실패해도 메인 트랜잭션 계속
// - 로그 성공 → T2 commit (즉시 DB 반영)
class Order {}
Order saveOrder() { return null; }
@interface Service {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class LogService { void log(Order o) {} }
LogService logService;
org.slf4j.Logger log;

5.4 NESTED (SAVEPOINT)

@Service
public class OrderService {
    @Transactional   // T1
    public void createOrder() {
        Order o = saveOrder();   // T1
        
        try {
            stockService.deduct();   // T1 안의 SAVEPOINT
            // SAVEPOINT 생성
            // 변경 사항이 SAVEPOINT 이후 기록
        } catch (Exception e) {
            // SAVEPOINT 까지만 rollback
            // T1 은 계속
            log.warn("재고 차감 실패");
        }
        
        // T1 commit (메인 데이터)
    }
}

@Service
public class StockService {
    @Transactional(propagation = Propagation.NESTED)
    public void deduct() {
        // T1 안의 부분 트랜잭션 (SAVEPOINT)
    }
}

// 차이 (REQUIRES_NEW vs NESTED):
// - REQUIRES_NEW: 별도 트랜잭션, 즉시 commit 가능
// - NESTED: T1 안, T1 의 commit/rollback 따름
class Order {}
Order saveOrder() { return null; }
@interface Service {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, NESTED }
class StockService { void deduct() {} }
StockService stockService;
org.slf4j.Logger log;

5.5 나머지 4 가지

SUPPORTS:
  - 기존 있으면 참여
  - 없으면 트랜잭션 없이 실행
  - 거의 안 씀

MANDATORY:
  - 기존 트랜잭션 필수
  - 없으면 예외
  - 호출자가 트랜잭션 보장 시

NOT_SUPPORTED:
  - 기존 보류 (suspend)
  - 트랜잭션 없이 실행
  - 외부 API 호출 시 (commit 불필요)

NEVER:
  - 기존 없어야 함
  - 있으면 예외
  - 트랜잭션 없는 작업 강제

5.6 함정 시나리오

// 함정 시나리오: REQUIRES_NEW + self-invocation
@Service
public class OrderService {
    @Transactional
    public void createOrder() {
        this.logAction();   // ← self-invocation!
        // REQUIRES_NEW 의도
        // 실제: 프록시 우회, 같은 트랜잭션
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction() {
        // 의도 동작 X (함정 2 + 함정 4)
    }
}

// 결과:
// - REQUIRES_NEW 동작 X
// - 같은 트랜잭션 안에서 실행
// - 의도와 다름
@interface Service {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class OrderService { void createOrder() {} }

5.7 ILIC 의 맥락

// ILIC 의 propagation 활용

// 메인 비즈니스 (REQUIRED)
@Service
public class ShipmentService {
    @Autowired AuditLogService auditService;
    
    @Transactional   // REQUIRED (기본)
    public void process(Long id) {
        Shipment s = repo.findById(id).orElseThrow();
        s.markAsShipped();
        
        // 감사 로그 (REQUIRES_NEW: 메인 실패해도 로그는 남음)
        try {
            auditService.logAction(id, "SHIPPED");
        } catch (Exception e) {
            log.warn("Audit failed", e);
            // 메인 트랜잭션은 계속
        }
    }
}

@Service
public class AuditLogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAction(Long id, String action) {
        // 별도 트랜잭션
        // 메인이 rollback 해도 로그는 commit
    }
}

// ILIC 의 패턴:
// - 메인 = REQUIRED
// - 로그 / 감사 = REQUIRES_NEW
// - 별도 클래스 분리 (함정 2 회피)
class Shipment { void markAsShipped() {} }
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
@interface Service {}
@interface Autowired {}
@interface Transactional { Propagation propagation() default Propagation.REQUIRED; }
enum Propagation { REQUIRED, REQUIRES_NEW }
class AuditLogService { void logAction(Long id, String s) {} }
AuditLogService auditService;
org.slf4j.Logger log;

5.8 자기 점검 답변

함정 4 — 트랜잭션 전파 (7가지) 는?

:
1. 7가지:

  • REQUIRED/REQUIRES_NEW/NESTED 등
  1. REQUIRED:

    • 기본 (참여)
  2. REQUIRES_NEW:

    • 별도 (즉시 commit)
  3. NESTED:

    • SAVEPOINT (부분)

6️⃣ 함정 5 — readOnly

6.1 함정 5 — readOnly

함정 5 — readOnly:

  @Transactional(readOnly = true):
    - 진짜 읽기 전용?
    - DB 가 강제? X
    - 최적화 효과?

  실제:
    - JPA: Dirty Checking 비활성화
    - Hibernate: FlushMode = MANUAL
    - 성능 최적화
    - 코드로 UPDATE 가능 (강제 X)

6.2 readOnly 의 실제 효과

실제 효과:

  JPA / Hibernate:
    1. Dirty Checking 스킵
       - 영속성 컨텍스트 비교 X
       - 메모리 / CPU 절약

    2. FlushMode = MANUAL
       - flush 자동 X
       - 명시적 flush 만

    3. PlatformTransactionManager 의 hint
       - 일부 DB 에서 최적화

  하지만:
    - DB 강제 X
    - 코드로 UPDATE 가능
    - 의도 위반

6.3 사용 사례

// ✅ 올바른 사용
@Service
public class ShipmentService {
    @Autowired ShipmentRepository repo;
    
    @Transactional(readOnly = true)
    public List<Shipment> findAll() {
        return repo.findAll();
        // 단순 조회
        // Dirty Checking 비활성 (최적화)
    }
    
    @Transactional(readOnly = true)
    public Shipment findById(Long id) {
        return repo.findById(id).orElseThrow();
    }
}

// 효과:
// - 메모리 절약
// - CPU 절약
// - 일부 DB 최적화
class Shipment {}
ShipmentRepository repo;
interface ShipmentRepository {
    java.util.List<Shipment> findAll();
    java.util.Optional<Shipment> findById(Long id);
}
@interface Service {}
@interface Autowired {}
@interface Transactional { boolean readOnly() default false; }

6.4 잘못된 사용

// ❌ 잘못된 사용
@Service
public class ShipmentService {
    @Transactional(readOnly = true)   // 읽기 전용
    public void updateStatus(Long id, String status) {
        Shipment s = repo.findById(id).orElseThrow();
        s.setStatus(status);   // 변경!
        // Dirty Checking 비활성 → UPDATE 안 됨!
        // 또는 명시적 save → UPDATE 됨 (의도 위반)
    }
}

// 함정:
// - readOnly = true 인데 변경 코드
// - 동작 미묘 (Dirty Checking X)
// - 의도와 다름
class Shipment { void setStatus(String s) {} }
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
@interface Service {}
@interface Transactional { boolean readOnly() default false; }

6.5 ILIC 의 표준

// ILIC 의 표준 패턴

// 조회 메서드:
@Transactional(readOnly = true)
public List<Shipment> findAll() {
    return repo.findAll();
}

@Transactional(readOnly = true)
public Page<Shipment> search(Pageable p) {
    return repo.search(p);
}

// 쓰기 메서드:
@Transactional   // readOnly 명시 X (기본 = false)
public void process(Long id) {
    Shipment s = repo.findById(id).orElseThrow();
    s.markAsShipped();
    // Dirty Checking 활성 → UPDATE 자동
}

// 규칙:
// - 조회만 = readOnly = true
// - 변경 = readOnly = false (기본)
// - 혼합 시 = false
class Shipment { void markAsShipped() {} }
class Page<T> {}
class Pageable {}
ShipmentRepository repo;
interface ShipmentRepository {
    java.util.List<Shipment> findAll();
    Page<Shipment> search(Pageable p);
    java.util.Optional<Shipment> findById(Long id);
}
@interface Transactional { boolean readOnly() default false; }

6.6 자기 점검 답변

함정 5 — readOnly 의 진짜 의미는?

:
1. 효과:

  • JPA Dirty Checking 비활성
  1. 강제 X:

    • 코드 변경 가능
  2. 최적화:

    • 메모리 / CPU
  3. 권장:

    • 조회만

7️⃣ 종합 졸업 시험 24문항

7.1 SQL JOIN (Phase 1) — 3 문항

문제 1: INNER JOIN 과 LEFT JOIN 의 차이는?

답: INNER JOIN 은 양쪽 테이블에 일치하는 행만 (교집합), LEFT JOIN 은 왼쪽 테이블의 모든 행 + 오른쪽의 일치하는 행 (없으면 NULL).

문제 2: FULL OUTER JOIN 은 MySQL 에서?

답: MySQL 미지원. UNION 으로 LEFT JOIN + RIGHT JOIN 합쳐 시뮬레이션.

문제 3: JOIN 선택의 기준은?

답: (1) 어떤 데이터 필요? — 교집합 / 한쪽 전체 / 양쪽 전체, (2) NULL 허용?, (3) 성능 영향.

7.2 ORM (Phase 2) — 6 문항

문제 4: 객체-관계 미스매치 5가지는?

답: (1) 입도 (객체 < 행), (2) 상속 (객체 vs 테이블 정규화), (3) 식별성 (==/equals vs PK), (4) 연관 (참조 vs FK), (5) 객체 그래프 탐색.

문제 5: ORM 의 정의?

답: Object-Relational Mapping. 객체와 관계형 DB 의 자동 매핑 기술.

문제 6: SQL Mapper (JdbcTemplate/MyBatis) 의 한계?

답: 매핑 수동, 객체 그래프 어려움, 변경 시 다중 SQL 수정, 미스매치 해결 X.

문제 7: JPA 의 위치는?

답: 애플리케이션 ↔ JDBC 사이 중간 계층. 4 단계: App → JPA → JDBC → DB Driver.

문제 8: Spring Data JPA 의 핵심?

답: Repository 인터페이스 자동 구현 + 메서드 이름 쿼리 + Pageable.

문제 9: Querydsl 의 가치?

답: 타입 안전 동적 쿼리. 컴파일 시점 검증. IDE 자동완성.

7.3 JPA 매핑 (Phase 3-4) — 4 문항

문제 10: @Entity 의 3가지 조건?

답: (1) @Entity 어노테이션, (2) 기본 생성자 필수 (JPA 가 리플렉션으로 객체 생성), (3) final 클래스 / 메서드 X (CGLIB 프록시 생성 위해).

문제 11: IDENTITY 의 단점?

답: persist 즉시 INSERT → JPA 의 쓰기 지연 (배치 INSERT) 불가능. SEQUENCE 는 allocationSize 로 미리 받아 배치 가능.

문제 12: @Enumerated 의 권장 옵션?

답: EnumType.STRING. ORDINAL 은 enum 순서 변경 시 데이터 오염 위험.

문제 13: Spring Boot 의 자동 명명?

답: SpringPhysicalNamingStrategy. camelCase → snake_case (blNo → bl_no).

7.4 수동 트랜잭션 (Phase 5) — 2 문항

문제 14: 수동 트랜잭션의 3가지 함정?

답: (1) rollback/close 누락 (실수), (2) 트랜잭션 경계 모호 (commit 후 외부 작업), (3) 트랜잭션 전파 어려움 (메서드 호출 시).

문제 15: After Commit 패턴?

답: @TransactionalEventListener(phase = AFTER_COMMIT). commit 성공 후만 외부 호출 (이메일/Slack 등). 일관성 보장.

7.5 PlatformTM (Phase 6) — 3 문항

문제 16: PlatformTransactionManager 의 3 메서드?

답: getTransaction(definition) → TransactionStatus, commit(status), rollback(status).

문제 17: 3가지 구현체?

답: DataSourceTransactionManager (JDBC/JdbcTemplate), HibernateTransactionManager (Hibernate 직접, 드묾), JpaTransactionManager (JPA/Spring Data JPA, 표준).

문제 18: JPA + JdbcTemplate 혼용 시 TM 선택?

답: JpaTransactionManager. EntityManagerFactory + DataSource 모두 인식. 같은 Connection / 단일 트랜잭션 보장.

7.6 @Transactional (Phase 7) — 6 문항

문제 19: @Transactional 의 동작 원리?

답: 7 단계 — (1) Client → (2) CGLIB 프록시 → (3) TransactionInterceptor.invoke → (4) PlatformTM.getTransaction (EntityManager 생성 + tx.begin) → (5) invocation.proceed() = 진짜 메서드 → (6) 정상 → commit (em.flush + tx.commit + em.close) → (7) 예외 → rollback.

문제 20: Spring Boot 2.x+ 의 프록시?

답: CGLIB 기본 (proxyTargetClass = true). 인터페이스 유무 무관, 일관성. JDK 동적은 spring.aop.proxy-target-class=false 로 변경.

문제 21: @Transactional 의 5가지 함정?

답: (1) private 메서드 (프록시 가로채기 X), (2) self-invocation (this = 진짜 객체, 프록시 우회), (3) checked exception (기본 rollback X, rollbackFor 명시), (4) propagation 7가지 정확한 이해, (5) readOnly (DB 강제 X, JPA 최적화 효과).

문제 22: self-invocation 해결 방법?

답: (1) 별도 Service 클래스 분리 (권장), (2) @Lazy 로 자기 자신 주입, (3) ApplicationContext 로 자기 빈 호출, (4) AspectJ 모드.

문제 23: REQUIRED vs REQUIRES_NEW?

답: REQUIRED (기본): 기존 트랜잭션 있으면 참여. REQUIRES_NEW: 항상 새 트랜잭션 (기존 보류). 별도 commit 가능.

문제 24: @Transactional(readOnly = true) 효과?

답: JPA Dirty Checking 비활성 (메모리/CPU 절약) + FlushMode = MANUAL + 일부 DB 최적화 hint. DB 강제 X (코드로 UPDATE 가능).

7.7 졸업 시험 통과

졸업 시험 24 문항:

  SQL JOIN: 3 문항 ✓
  ORM: 6 문항 ✓
  JPA 매핑: 4 문항 ✓
  수동 트랜잭션: 2 문항 ✓
  PlatformTM: 3 문항 ✓
  @Transactional: 6 문항 ✓

  통과 기준:
    - 모두 답할 수 있으면 7주차 마스터
    - 면접 합격 가능 수준

→ 7주차 졸업!

8️⃣ 7주차 완주 정리

8.1 7주차 전체 학습 종합

🎯 7주차 — ORM / JPA / 트랜잭션 추상화

🗂️ Part A — 데이터 모델링과 ORM (16 Unit)
  📚 Phase 1 — SQL JOIN (5)
    - Unit 1.1 JOIN 의 필요성
    - Unit 1.2 INNER JOIN
    - Unit 1.3 LEFT/RIGHT JOIN
    - Unit 1.4 FULL OUTER JOIN
    - Unit 1.5 JOIN 선택 가이드 ★

  🔄 Phase 2 — ORM 패러다임 (2)
    - Unit 2.1 객체-관계 미스매치
    - Unit 2.2 ORM 정의

  🌱 Phase 3 — JPA 입문 (4)
    - Unit 3.1 SQL Mapper 한계
    - Unit 3.2 JPA 등장 ★
    - Unit 3.3 JPA 동작 위치
    - Unit 3.4 Spring Data JPA + Querydsl ★

  🏷️ Phase 4 — JPA 엔티티 매핑 (5)
    - Unit 4.1 @Entity
    - Unit 4.2 @Id
    - Unit 4.3 @GeneratedValue ★
    - Unit 4.4 @Column
    - Unit 4.5 자동 매핑

🔄 Part B — 트랜잭션 추상화의 진화 (8 Unit)
  🔧 Phase 5 — 수동 트랜잭션의 한계 (2)
    - Unit 5.1 트랜잭션 결합
    - Unit 5.2 3가지 함정

  🎯 Phase 6 — PlatformTransactionManager (3)
    - Unit 6.1 인터페이스 추상화
    - Unit 6.2 3가지 구현체
    - Unit 6.3 사용 전후 비교

  ✨ Phase 7 — @Transactional (3, 모두 ★)
    - Unit 7.1 프록시 패턴 ★
    - Unit 7.2 동작 원리 ★★★
    - Unit 7.3 5가지 함정 ★ ← 여기

총 24 Unit + 종합 졸업 시험 24문항
모든 ★ 깊이 Unit 7개

8.2 7주차 핵심 메시지

7주차 핵심 메시지:

"SQL 부터 JPA 까지 데이터 모델링과 ORM 의 완전한 그림 +
 수동 트랜잭션의 한계 부터 @Transactional 의 자동화까지 
 트랜잭션 추상화의 진화 = 자바 백엔드의 정수.
 
 5주차 디자인 패턴 (DI/DIP/OCP/프록시/AOP) + 
 6주차 DB 접근 (DataSource/ACID/JdbcTemplate) +
 7주차 (JPA + PlatformTM + @Transactional) 
 = 자바 백엔드의 결정체.
 
 @Transactional 1 줄 안에 모든 학습이 응축되며,
 ILIC 의 1020 메서드가 이 결정체로 안정 운영된다."

8.3 1~7주차 흐름

1~7주차 흐름:

  1주차 OOP →
    2주차 JVM →
      3주차 람다 / 함수형 →
        4주차 동시성 →
          5주차 디자인 패턴 →
            6주차 DB 접근 →
              7주차 ORM + 트랜잭션 추상화 (지금) ✓

  각 주차의 가치:
    1주차: 자바 기본
    2주차: JVM 이해 (성능)
    3주차: 함수형 (현대 자바)
    4주차: 동시성 (서버 핵심)
    5주차: 디자인 패턴 (확장)
    6주차: DB 접근 (영속성)
    7주차: ORM + 트랜잭션 (자동화)

→ 자바 백엔드의 7 기둥

8.4 박승제의 성장

박승제의 성장

학습 시작 (1주차) →
  학습 완주 (7주차):

  ILIC 의 1020 메서드:
    - 모두 @Transactional
    - 모두 자동
    - 박승제 = 비즈니스만

  학습 후의 가치:
    1. 면접 준비:
       - 시니어 시험 합격
       - 5+6+7주차 응축 답변

    2. 운영 안정:
       - 5 함정 회피
       - 트러블슈팅
       - 코드 리뷰

    3. 깊은 이해:
       - 7 단계 흐름
       - 자바 백엔드 정수
       - 자신감

    4. ILIC 의 가치:
       - 박승제 + 1 풀스택
       - 102 테이블 안정 운영
       - 신뢰

→ 자바 백엔드 시니어 마스터

8.5 자바 백엔드의 정수

자바 백엔드의 정수:

  @Transactional 1 줄 안에:
    
    5주차 (디자인 패턴):
      ✓ 프록시 패턴 (CGLIB)
      ✓ DI / DIP / OCP
      ✓ 템플릿+전략 (TransactionTemplate)
      ✓ 데코레이터 (부가 기능)
      ✓ AOP

    6주차 (DB 접근):
      ✓ DataSource (HikariCP)
      ✓ ACID (트랜잭션)
      ✓ JDBC (Connection)
      ✓ JdbcTemplate

    7주차 (ORM + 트랜잭션):
      ✓ JPA (EntityManager)
      ✓ Spring Data JPA
      ✓ Querydsl
      ✓ PlatformTransactionManager
      ✓ @Transactional + AOP/프록시

  → 7 주차 학습의 모든 응축
  → 1 줄 어노테이션의 깊이

8.6 면접 종합 매핑

면접 종합:

  자주 묻는 7주차 Top 10:

  1. @Transactional 동작 원리? (Unit 7.2)
  2. 프록시 패턴? (Unit 7.1)
  3. 5가지 함정? (Unit 7.3)
  4. self-invocation? (Unit 7.3)
  5. propagation 7가지? (Unit 7.3)
  6. JPA vs JdbcTemplate? (Phase 3)
  7. IDENTITY vs SEQUENCE? (Unit 4.3)
  8. PlatformTransactionManager? (Unit 6.1)
  9. Spring Data JPA + Querydsl? (Unit 3.4)
  10. ORM 미스매치? (Unit 2.1)

→ 7주차 완주 = 모두 답변 가능

8.7 자기 점검 답변

7주차 완주 정리는?

:
1. 24 Unit:

  • 모두 완주
  1. ★ 깊이:

    • 7 Unit
  2. 응축:

    • 5+6+7주차
  3. 자산:

    • 자바 백엔드 시니어

9️⃣ 8주차 예고

9.1 7주차 → 8주차

7주차 완주 →
8주차 (예정):

  가능한 8주차 주제:
    - 분산 시스템 (마이크로서비스)
    - 비동기 / 이벤트 기반
    - 메시징 (Kafka / RabbitMQ)
    - 캐싱 (Redis)
    - 성능 / 최적화
    - 보안 (Spring Security)
    - 테스트 (JUnit + Mockito)
    - 또는 다른 주제

→ 박승제 의 우선순위에 따라

9.2 ILIC 와 연계

ILIC 와 연계:

  ILIC 의 다음 단계:
    - 분산 시스템 (멀티 서버)
    - 성능 최적화 (대량 데이터)
    - 캐싱 (Redis)
    - 모니터링 / 옵저버빌리티

  7주차 다음 학습:
    - ILIC 의 실제 도전 과제
    - 시니어 개발자의 영역

9.3 박승제의 다음 자산

박승제의 다음 자산

이미 (7주차 완주):
  - 자바 OOP
  - JVM
  - 함수형
  - 동시성
  - 디자인 패턴
  - DB 접근
  - ORM + 트랜잭션 ✓

다음 (8주차+):
  - 분산 / 마이크로서비스
  - 성능 / 최적화
  - 모니터링
  - 보안 / 테스트

→ 풀스택 시니어의 완성

9.4 7주차의 의의

7주차의 의의:

  7주차 학습 후:
    - 자바 백엔드의 핵심 이해
    - 면접 통과 가능
    - 시니어 개발자

  하지만:
    - 다음 단계 필요 (분산, 성능)
    - 끝없는 학습
    - 깊이 + 폭

→ 7주차 = 시니어로 가는 핵심 관문

9.5 마지막 메시지

마지막 메시지:

  7주차 완주 축하 🎉

  ILIC 의 1020 메서드가
  @Transactional 1 줄로 안정 운영되는 이유는
  7주차 학습의 모든 응축이기 때문.

  5주차 디자인 패턴 +
  6주차 DB 접근 +
  7주차 ORM 과 트랜잭션 추상화 =
  자바 백엔드의 정수.

  박승제 의 자산이며,
  ILIC 의 안정 운영이며,
  면접의 정점이며,
  자바 시니어의 시작이다.

  다음 주차 학습으로 →
  계속 성장 → 시니어 풀스택 마스터.

9.6 자기 점검 답변

7주차 완주의 의의는?

:
1. 자바 백엔드 정수:

  • 5+6+7주차 응축
  1. 면접:

    • 시니어 시험 통과
  2. 운영:

    • ILIC 1020 메서드 안정
  3. 다음:

    • 8주차 (분산 / 성능 / ...)

🎯 핵심 요약 — 3줄 정리

1. 5가지 함정

  • (1) private 메서드 (프록시 가로채기 X) → public 변경 / 별도 클래스
  • (2) self-invocation (this = 진짜 객체) → 별도 클래스 (권장) / @Lazy 자기 주입
  • (3) checked exception (기본 rollback X) → rollbackFor 명시 / RuntimeException 사용 (권장)
  • (4) propagation 7가지 (REQUIRED/REQUIRES_NEW/NESTED/...) → 정확한 이해
  • (5) readOnly (DB 강제 X) → JPA Dirty Checking 비활성 (최적화)

2. 종합 졸업 시험 24문항

  • SQL JOIN (3) + ORM (6) + JPA 매핑 (4) + 수동 트랜잭션 (2) + PlatformTM (3) + @Transactional (6)
  • 모두 답할 수 있으면 7주차 마스터 + 시니어 면접 통과 가능

3. 7주차의 결정체

  • @Transactional 1 줄 안에 5주차 (디자인 패턴) + 6주차 (DB 접근) + 7주차 (ORM + 트랜잭션) 모두 응축
  • ILIC 의 1020 메서드가 안정 운영되는 이유
  • 자바 백엔드 시니어로 가는 핵심 관문

🏆 Phase 7 완주 — @Transactional

✨ Phase 7 — @Transactional (모두 ★ 깊이)
  ✅ Unit 7.1 프록시 패턴 ★
  ✅ Unit 7.2 동작 원리 ★★★
  ✅ Unit 7.3 5가지 함정 ★ ← 여기, Phase 7 완주

→ @Transactional 완전 정복
→ 5가지 함정 마스터
→ 면접 시니어 시험 통과

🏆 Part B 완주 — 트랜잭션 추상화의 진화

🔄 Part B — 트랜잭션 추상화의 진화
  ✅ Phase 5 — 수동 트랜잭션의 한계 (2)
  ✅ Phase 6 — PlatformTransactionManager (3)
  ✅ Phase 7 — @Transactional (3, 모두 ★) ← 완주

→ 트랜잭션 추상화 완전 정복
→ 5+6+7주차 응축 마스터

🎯 7주차 완주 — ORM / JPA / 트랜잭션 추상화

🎯 7주차 — ORM / JPA / 트랜잭션 추상화

🗂️ Part A — 데이터 모델링과 ORM (16/16) ✓
🔄 Part B — 트랜잭션 추상화의 진화 (8/8) ✓

총: 24/24 Unit (100%) 🎉
★ 깊이 Unit: 7 개 ✓
종합 졸업 시험 24문항 ✓

→ 자바 백엔드의 정수 마스터
→ 시니어 시험 통과 가능
→ ILIC 의 1020 메서드 안정 운영

📚 다음으로...

8주차 (예정)

박승제의 다음 학습 주제 — 분산 시스템 / 성능 최적화 / 캐싱 / 메시징 / 모니터링 / 보안 / 테스트 등 시니어 풀스택의 다음 단계.

1~7주차 누적 학습

1주차: OOP
2주차: JVM
3주차: 람다 / 함수형
4주차: 동시성
5주차: 디자인 패턴
6주차: DB 접근
7주차: ORM + 트랜잭션 ← 여기, 완주 ✓

🎯 5+6+7주차 응축 마스터 — 자바 백엔드의 정수

profile
Software Developer

0개의 댓글