10주차 자료의 모든 토픽을 "정리 → 새 주제" 흐름으로 재배열한 학습 경로.
7~9주차에서 다룬 트랜잭션·AOP를 정리하면서, 빈 초기화 함정 과 트랜잭션 격리 수준이라는 두 가지 새 토픽을 추가한다.분량은 8-9주차보다 가볍지만, 면접·실무 직결 함정 두 개가 핵심이다.
[Phase 1] @Import — 설정 클래스 결합
↓
[Phase 2] 스프링 트랜잭션 추상화 정리 (7주차 복습+심화)
↓
[Phase 3] 선언적 vs 프로그래밍 방식 + 프록시 적용 (9주차 복습+심화)
↓
[Phase 4] 빈 라이프사이클의 함정 — @PostConstruct + @Transactional ★
↓
[Phase 5] 트랜잭션 격리 수준 — DB와 Spring의 경계 ★
총 5 Phase × 16 Unit — 일정한 분량의 정리 주차.
★ 표시 Phase는 새로 등장하는 핵심 주제 이며 면접·실무 직결.
| 주차 | 주제 | 핵심 변화 |
|---|---|---|
| 1주차 | OOP·JVM·GC·컬렉션·I/O 개론 | 자바 큰 그림 |
| 2주차 | JVM 내부·바이트코드·G1 GC | "어떻게 돌아가나" |
| 3주차 | 컬렉션·제네릭·함수형 | 자바 표현력 |
| 4주차 | 멀티스레딩·동시성·Executor | 동시성 정복 |
| 5주차 | Atomic + Spring IoC/DI 입문 | 자바 → Spring 다리 |
| 6주차 | 테스트 + 웹 인프라 + DB 접근 진화 | Spring 실전 환경 |
| 7주차 | JPA/ORM + 트랜잭션 추상화 | DB 추상화의 정점 |
| 8주차 | 프록시의 진화 | AOP 메커니즘 이해 |
| 9주차 | Spring AOP 실전 + 트랜잭션 전파 | AOP 실전 활용 |
| 10주차 (지금) | 트랜잭션 정리 + 빈 라이프사이클 함정 + 격리 수준 | 트랜잭션 학습의 마무리 |
| Day | Phase | 학습 목표 |
|---|---|---|
| 1일차 | Phase 1 + 2 | @Import + 스프링 트랜잭션 추상화 정리 |
| 2일차 | Phase 3 | 선언적 vs 프로그래밍 + 프록시 적용 |
| 3일차 | Phase 4 | @PostConstruct + @Transactional 함정 (★) |
| 4일차 | Phase 5 | 트랜잭션 격리 수준 (★) |
| 5일차 | 종합 자기 점검 + 실습 | 전체 정리 |
여유 일정 (7일): Phase 4·5에 각 +1일. 격리 수준은 직접 SQL을 돌리며 시각적으로 체화하는 게 효과적.
목표: 여러 설정 파일로 분리된 빈을 하나로 묶는 작은 Spring 도구를 익힌다.
선수 지식: 5주차 Phase 8 (ApplicationContext)
핵심 개념
문제:
@Import 의 해결:
@Configuration 클래스를 한 곳에서 결합@Configuration
class ServiceConfig {
@Bean
public GreetingService greetingService() {
return new GreetingService();
}
}
@Configuration
class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
@Configuration
@Import({ServiceConfig.class, AppConfig.class}) // 결합
class MainConfig {}
자기 점검
@Import(AspectV1.class) 를 본 맥락은?@Import 와 @ComponentScan 의 차이는? (힌트: 명시적 vs 자동)선수 지식: Unit 1.1
핵심 패턴
환경별 설정 분리:
@Configuration
@Import({DatabaseConfig.class, SecurityConfig.class, CacheConfig.class})
public class ApplicationConfig {}
테스트용 설정 추가:
@SpringBootTest
@Import(TestConfig.class) // 테스트 전용 빈 추가
class IntegrationTest {}
언제 @Component 대신 @Import?:
자기 점검
목표: 7주차에서 다룬 PlatformTransactionManager의 본질을 다시 짚고 Spring Boot의 자동 구성까지 정리한다.
선수 지식: 7주차 Phase 5
핵심 문제
기술마다 트랜잭션 코드가 다름:
connection.setAutoCommit(false) + commit() / rollback()EntityTransaction.begin() + commit() / rollback()결과:
→ 추상화 필요 = PlatformTransactionManager
자기 점검
선수 지식: Unit 2.1
핵심 인터페이스:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
3가지 메서드만:
getTransaction)commit)rollback)구현체 매핑:
| 데이터 접근 기술 | TransactionManager 구현체 |
|---|---|
| JDBC, JdbcTemplate, MyBatis | DataSourceTransactionManager (= JdbcTransactionManager) |
| Hibernate (직접 사용) | HibernateTransactionManager |
| JPA | JpaTransactionManager |
자기 점검
선수 지식: Unit 2.2
핵심 개념
Spring Boot의 자동 구성:
| 의존성 | 자동 등록 |
|---|---|
spring-boot-starter-jdbc | DataSourceTransactionManager |
spring-boot-starter-data-jpa | JpaTransactionManager |
개발자가 할 일:
@Transactional 사용ILIC 사례:
자기 점검
--debug 출력)목표: 9주차에서 본 @Transactional의 본질을 "프록시 도입 전후 코드 비교" 로 다시 한번 못 박는다.
선수 지식: 7주차 Phase 7, 9주차 Phase 10
핵심 개념
| 방식 | 정의 | 장점 | 단점 |
|---|---|---|---|
선언적 (@Transactional) | 어노테이션 한 줄 선언 | 매우 간편 | 함정 多 (internal call 등) |
| 프로그래밍 (TransactionTemplate) | 트랜잭션 코드 직접 작성 | 세밀한 제어 | 비즈니스 로직과 결합 |
프로그래밍 방식의 한계:
"애플리케이션 코드가 트랜잭션이라는 기술 코드와 강하게 결합"
실무 결론:
"선언적 트랜잭션이 거의 표준 — 프로그래밍 방식은 특수 케이스에만"
자기 점검
선수 지식: Unit 3.1
프록시 도입 전 코드:
public class Service {
public void logic() {
// ① 트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition()
);
try {
// ② 비즈니스 로직
bizLogic(fromId, toId, money);
// ③ 성공 시 커밋
transactionManager.commit(status);
} catch (Exception e) {
// ④ 실패 시 롤백
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
}
문제점:
자기 점검
선수 지식: Unit 3.2, 8-9주차 Phase 6
프록시 도입 후 — 비즈니스 코드:
public class Service {
public void logic() {
bizLogic(fromId, toId, money); // 트랜잭션 코드 0
}
}
프록시가 하는 일 (개념적 의사코드):
public class TransactionProxy {
private MemberService target;
public void logic() {
TransactionStatus status = transactionManager.getTransaction(...);
try {
target.logic(); // 실제 서비스 호출
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
}
핵심 통찰:
"트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다.
서비스 계층에는 순수한 비즈니스 로직만 남는다."
자기 점검
목표: 9주차의 internal call 함정과 짝을 이루는 또 하나의 면접 단골 함정을 마스터한다.
선수 지식: 5주차 Phase 8 (빈 생명주기)
핵심 개념
@PostConstruct:
전형적 사용처:
@Component
public class CacheInitializer {
private final CacheService cacheService;
public CacheInitializer(CacheService cacheService) {
this.cacheService = cacheService;
}
@PostConstruct
public void loadCache() {
cacheService.initializeCache(); // 앱 시작 시 한 번만
}
}
유사 도구 비교:
@PostConstruct: 표준 (JSR-250), 단일 빈 생명주기InitializingBean.afterPropertiesSet(): Spring 인터페이스자기 점검
선수 지식: Unit 4.1, 9주차 Phase 7~10
핵심 함정
@Component
public class DataInitializer {
@PostConstruct
@Transactional // ⚠️ 트랜잭션 적용 안 됨!
public void initV1() {
log.info("Hello init @PostConstruct");
// DB 작업 시도... 트랜잭션 없이 실행됨
}
}
왜 안 되는가:
1. 빈이 생성되고 의존성 주입 완료
2. @PostConstruct가 먼저 실행 ← 이 시점!
3. 그 후에 트랜잭션 AOP가 적용 (빈 후처리기)
4. → @PostConstruct 호출 시점에는 프록시가 아직 미존재
5. → @Transactional이 무시됨
중요 — 9주차 Phase 7과의 연결:
AnnotationAwareAspectJAutoProxyCreator)가 빈 후처리기로 동작internal call 함정과의 비교:
| 함정 | 9주차 Internal Call | 10주차 @PostConstruct |
|---|---|---|
| 본질 | 프록시를 거치지 않는 호출 | 프록시가 아직 만들어지지 않음 |
| 시점 | 런타임 (객체 자체 호출) | 초기화 시점 |
| 해결 | 클래스 분리 | ApplicationReadyEvent |
자기 점검
선수 지식: Unit 4.2
핵심 해결
@Component
public class DataInitializer {
@EventListener(value = ApplicationReadyEvent.class)
@Transactional // ✅ 정상 동작
public void init2() {
log.info("Hello init ApplicationReadyEvent");
// DB 작업이 트랜잭션 안에서 실행됨
}
}
왜 되는가:
ApplicationReadyEvent = 컨테이너가 완전히 생성된 후 발행되는 이벤트스프링 부트의 라이프사이클 이벤트 4종:
ApplicationStartingEvent — 애플리케이션 시작ApplicationEnvironmentPreparedEvent — 환경 정보 준비ApplicationContextInitializedEvent — 컨텍스트 초기화ApplicationReadyEvent — 모든 준비 완료 (사용 권장)다른 대안:
CommandLineRunner / ApplicationRunner 인터페이스자기 점검
목표: 6주차 ACID의 'I(Isolation)'를 깊이 파헤친다. 3가지 충돌 → 4가지 격리 수준 매트릭스를 완성한다.
선수 지식: 6주차 Phase 6 (ACID)
핵심 개념
격리 수준 (Isolation Level):
"트랜잭션끼리 얼마나 서로 고립 되어 있는지의 수준"
왜 필요한가:
트레이드오프:
자기 점검
선수 지식: Unit 5.1
핵심 시나리오
"커밋되지 않은 다른 트랜잭션의 데이터를 읽음"
예시:
1. 트랜잭션 A: x = 10 → 100 변경 (커밋 X)
2. 트랜잭션 B: x = 100 조회 ← 잘못된 값
3. 트랜잭션 A: 롤백
4. → B는 존재하지 않은 값을 봤음
ASCII 다이어그램:
Time T1 T2
───── ────────────────── ──────────────
t1 UPDATE x=100
t2 SELECT x → 100 ← Dirty Read!
t3 ROLLBACK
(T2는 환상의 값을 본 것)
자기 점검
선수 지식: Unit 5.2
핵심 시나리오
"같은 트랜잭션 안에서 같은 데이터를 두 번 읽었는데 값이 다름"
원인: 다른 트랜잭션이 수정/삭제
예시:
1. 트랜잭션 A: x 조회 → 10
2. 트랜잭션 B: x = 10 → 50 변경 후 커밋
3. 트랜잭션 A: x 다시 조회 → 50 ← 같은 트랜잭션인데 다름!
언제 문제인가:
자기 점검
선수 지식: Unit 5.3
핵심 시나리오
"같은 조건으로 두 번 조회했는데 결과 행 수가 다름"
원인: 다른 트랜잭션이 삽입(또는 삭제)
예시:
1. 트랜잭션 A: 잔액 ≥ 100만 조건으로 조회 → 5건
2. 트랜잭션 B: 잔액 200만 신규 계좌 INSERT 후 커밋
3. 트랜잭션 A: 같은 조건 다시 조회 → 6건 ← "유령" 행 등장
Non-repeatable Read와의 차이:
자기 점검
선수 지식: Unit 5.2~5.4
핵심 매트릭스:
| 격리 수준 | Dirty Read | Non-repeatable Read | Phantom Read | 기본 채택 DB |
|---|---|---|---|---|
| READ UNCOMMITTED (0) | ❌ | ❌ | ❌ | (표준 인정 X) |
| READ COMMITTED (1) | ✅ | ❌ | ❌ | Oracle, PostgreSQL |
| REPEATABLE READ (2) | ✅ | ✅ | ❌ | MySQL ⭐ |
| SERIALIZABLE (3) | ✅ | ✅ | ✅ | (성능 최저) |
✅ = 방어됨, ❌ = 발생 가능
각 수준 정리:
Level 0 — READ UNCOMMITTED:
Level 1 — READ COMMITTED:
Level 2 — REPEATABLE READ ⭐:
Level 3 — SERIALIZABLE:
Spring에서 격리 수준 지정:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void method() { ... }
자기 점검
★★★ 면접 단골 (반드시):
★★ 매우 권장:
[ ] Phase 1 — @Import (Unit 1.1~1.2)
[ ] Phase 2 — 트랜잭션 추상화 정리 (Unit 2.1~2.3)
[ ] Phase 3 — 선언적 vs 프로그래밍 방식 (Unit 3.1~3.3)
[ ] Phase 4 — @PostConstruct 함정 (Unit 4.1~4.3) ★
[ ] Phase 5 — 트랜잭션 격리 수준 (Unit 5.1~5.5) ★
[ ] 종합 자기 점검 20문항 통과
internal call 함정과 @PostConstruct 함정은 같은 본질의 두 얼굴:
| 9주차 internal call | 10주차 @PostConstruct | |
|---|---|---|
| 본질 | 프록시를 거치지 않는 호출 | 프록시가 아직 만들어지지 않음 |
| 시점 | 런타임, this 호출 시 | 빈 초기화 시점 |
| 증상 | @Transactional 무시 | @Transactional 무시 |
| 해결 | 클래스 분리 | ApplicationReadyEvent |
→ 두 함정은 함께 묶어 외워야 한다 (면접 빈출).
Phase 4 실습:
TransactionSynchronizationManager.isActualTransactionActive() 로 검증Phase 5 실습:
SET TRANSACTION ISOLATION LEVEL 명령으로 수준 변경트랜잭션 학습의 5단계 완결:
→ 이제 트랜잭션의 모든 것을 안다. 11주차부터는 새 영역으로 진입할 시점.