목적: F-lab 1~14주차 자료에서 의도적으로 반복(개론 → 내부 → 전체지도)되던 주제들을
중복 제거 + 의존성 순서 재배열하여, 웹개발 초보가 앞에서부터 한 줄로 따라갈 수 있는 단일 학습 경로로 통합한 문서.핵심 서사: 기술 진화 단원은 모두 "로우레벨의 불편함 → 그래서 하이레벨 기술이 등장" 구조로 서술한다.
즉 왜 이게 생겼는지를 먼저 고통으로 느낀 뒤, 그 고통을 없애는 도구로 다음 단계를 만난다.확장 메모: 이후 추가될 주차는 PART 22 이후로 이어 붙인다. 현재 v3는 자바 언어 → 동시성 → 스프링(IoC/DI·AOP) → DB 접근 → JPA 심화 → DB 이론·운영 → Spring MVC → 분산 시스템·캐싱·메시징·MSA → Spring Security → 테스트 → HTTP·네트워크·DevOps·Observability까지 커버한다.
[자바 언어]
PART 1 객체지향(OOP) 기초
↓
PART 2 JVM 메모리 모델과 실행 원리
↓
PART 3 GC (가비지 컬렉션)
↓
PART 4 문자열과 컬렉션
↓
PART 5 제네릭 · 비교 · 함수형
↓
PART 6 I/O와 직렬화
[동시성]
PART 7 멀티스레딩과 동시성
[스프링 진입]
PART 8 객체 설계의 진화 → IoC/DI
↓
PART 9 테스트와 웹 인프라
↓
PART 10 DB 접근의 진화 (JDBC → JdbcTemplate)
↓
PART 11 ORM/JPA와 트랜잭션 추상화
[스프링 심화]
PART 12 프록시의 진화와 Spring AOP
↓
PART 13 트랜잭션 심화 (전파 · 격리 · 라이프사이클 함정)
↓
PART 14 JPA 심화 (영속성 컨텍스트 · 연관관계 · N+1)
[데이터베이스]
PART 15 데이터베이스 펀더멘털 (모델링 · 정규화 · 인덱스 · 분산이론)
↓
PART 16 데이터베이스 운영 (타입 · HA · 확장 · SQL 고급)
[웹 계층]
PART 17 Spring MVC 내부와 REST API
[시스템 확장]
PART 18 분산 시스템 · 캐싱(Redis) · 메시징(Kafka) · MSA
[보안 · 검증 · 운영]
PART 19 Spring Security (인증 · 인가 · JWT · OAuth2)
↓
PART 20 테스트 심화 (Mockito · Testcontainers · TDD)
↓
PART 21 HTTP · 네트워크 · DevOps · Observability
| 영역 | 로우레벨(고통) | 중간 | 하이레벨(해결) |
|---|---|---|---|
| 메모리 전달 | C 포인터 직접 조작 | — | 자바 값 복사(참조값 복사) |
| 메모리 회수 | 참조 카운팅(순환 참조 누수) | Mark-Sweep/Compact | Generational → G1 → ZGC |
| 문자열 합치기 | String + (객체 폭증) | — | StringBuilder |
| 데이터 검색 | 순차 탐색 O(n) | 정렬+이진탐색 | 해시 O(1) |
| I/O | 1바이트 Stream, Blocking | NIO Channel+Buffer | Non-blocking + Selector |
| 스레드 | new Thread() 직접 생성 | Runnable 분리 | Executor 풀 → CompletableFuture |
| 동기화 | synchronized(블로킹) | volatile(가시성만) | Atomic/CAS(논블로킹) |
| 객체 생성·연결 | DAO가 모든 책임 떠안음 | 상속·디자인패턴 | 인터페이스+DI → Spring IoC |
| DB 통신 | 소켓으로 DB 프로토콜 직접 | JDBC 표준 | JdbcTemplate → JPA |
| 커넥션 | 매 요청 새 연결 | — | Connection Pool / DataSource |
| 트랜잭션 | Connection 수동 commit/rollback | PlatformTransactionManager | @Transactional(AOP) |
| 부가 관심사(로깅·트랜잭션) | 메서드마다 try-catch 반복 | 템플릿 메서드 → 전략 → 템플릿 콜백 | AOP @Around |
| 프록시 생성 | 수동 프록시 클래스 100개 | Reflection → JDK 동적 프록시 / CGLIB | ProxyFactory → @Aspect |
| 트랜잭션 중첩 | Connection 수동으로 묶기 | 논리/물리 트랜잭션 분리 | @Transactional 전파 옵션 |
| 영속성 관리 | DAO가 SQL·매핑 수동 | SQL Mapper(MyBatis) | JPA 영속성 컨텍스트(변경 감지) |
| 연관 조회 | N+1 쿼리 폭발 | fetch join | @BatchSize / EntityGraph |
| 데이터 검색(DB) | Full Scan O(n) | 정렬 + 이진 탐색 | B-tree 인덱스 O(logN) |
| DB 확장 | 단일 서버(Scale-Up 한계) | Replication(읽기 분산) | Cluster / Sharding(Scale-Out) |
| 일관성 모델 | ACID 강한 일관성 | — | BASE 최종 일관성(NoSQL) |
| 요청 라우팅 | URL마다 Servlet 폭증 | Front Controller | DispatcherServlet(+HandlerMapping/Adapter) |
| 횡단 관심사(웹) | 모든 Servlet에 공통 코드 중복 | Filter / Interceptor | AOP(메서드 단위) |
| DB 부하 | 매 요청 DB 직격 | 로컬 캐시 | 분산 캐시(Redis) + 캐싱 패턴 |
| 서비스 간 호출 | 동기 호출 체인(강결합·전파) | 비동기 + 큐 | 이벤트 스트리밍(Kafka) |
| 분산 트랜잭션 | 2PC(블로킹·SPOF) | — | Saga + Outbox(최종 일관성) |
| 시스템 구조 | Monolith(전체 배포) | Modular Monolith | MSA(Bounded Context) |
| 인증 상태 | 서버 Session(Stateful) | Session in Redis | JWT(Stateless) + OAuth2 |
| 테스트 환경 | 수동/전체 컨텍스트(느림) | H2 + 슬라이스 | Testcontainers(운영 동일) |
| HTTP 전송 | HTTP/1.1(HOL·헤더 중복) | HTTP/2(멀티플렉싱) | HTTP/3(QUIC) |
| 배포·확장 | 수동 배포·단일 서버 | Docker·Compose | Kubernetes(HPA·무중단) |
| 운영 가시성 | 사후 로그 grep | Monitoring(지표) | Observability(3 Pillars) |
목표: "왜 자바는 객체지향인가"에 직접 답하고, 상속·다형성·추상화·SOLID까지 OOP의 뼈대를 손에 익힌다.
로우레벨의 불편함: 절차지향(C)에서는 데이터(구조체)와 그 데이터를 다루는 함수가 분리되어 있다. 데이터가 어디서 어떻게 바뀌는지 추적이 어렵고, 현실의 "자동차는 속도를 가지며 가속한다" 같은 모델이 코드에서 흩어진다.
해결: 객체지향은 상태(필드) + 행동(메서드)을 하나의 객체로 묶는다. 현실을 그대로 모델링할 수 있고, 데이터 변경 경로가 객체 안으로 캡슐화된다.
자기 점검
자기 점검
[접근제어자] [반환타입] 메서드명(매개변수), 반환은 return타입... 변수명: 개수가 가변일 때, 내부에서는 배열로 다룬다. 반드시 매개변수 목록의 마지막에 위치.자기 점검
void log(String... args) 와 void log(String[] args)의 차이는?extends로 부모의 필드/메서드 상속super())를 호출super(...)를 호출해야 한다.Animal a = new Dog(); a.eat(); → 실행되는 건 Dog.eat()instanceof로 실제 타입 확인 후 캐스팅 (Java 16+ 패턴 매칭: if (a instanceof Dog d) { d.bark(); })자기 점검
instanceof 검사가 왜 필요한가?추상클래스
abstract, 단 구현된 메서드도 포함 가능인터페이스
public static final 상수Java 8 default & static 메서드 (진화의 한 장면)
default 메서드로 인터페이스에 구현을 담아 하위 호환을 유지. → 추상클래스와 경계가 흐려짐.선택 가이드
| 항목 | 추상클래스 | 인터페이스 |
|---|---|---|
| 다중 상속 | ❌ | ✅ |
| 구현 메서드 | ✅ | Java 8+ default만 |
| 필드 | 자유 | public static final |
| 생성자 | ✅ | ❌ |
OOP 문법은 알지만 "잘 짠 코드"를 모르는 단계에서, 변경에 강한 코드의 5계명.
User / UserRepository / EmailService로 분리.if-else 타입 분기 → 인터페이스 다형성. (전략 패턴의 뿌리)Penguin extends Bird에서 fly()를 막으면 위반.Worker{work,eat} → Workable/Eatable 분리.자기 점검
목표: "이 변수는 메모리 어디에 저장될까?"에 즉답하고, 메서드 호출 한 줄이 JVM 안에서 어떻게 실행되는지 바이트코드까지 추적한다.
| 변수 종류 | 저장 영역 | 생성/소멸 |
|---|---|---|
| 지역 변수 (Local) | Stack | 메서드 호출 / 종료 |
| 인스턴스 변수 (Instance) | Heap (객체와 함께) | new / GC 회수 |
| 클래스 변수 (static) | Method Area | 클래스 로딩 / JVM 종료 |
new로 만든 것) — GC 대상Method Area의 3개 존 (심화)
Method Area
├─ Class Metadata Zone : 클래스명, 부모, 메서드 시그니처, 필드 선언 정보 ("무엇이 있는가")
├─ Static Zone : static 메서드/필드의 실제 바이트코드
└─ Non-Static Zone : 인스턴스 메서드의 실제 바이트코드
main()이 Static Zone에 있어야 객체 없이 실행 가능StackOverflowError로우레벨부터: C는
*로 메모리 주소를 직접 다룬다.int *p = &a;에서&a는 a의 주소,*p는 역참조한 실제 값. 포인터를 잘못 다루면 메모리 사고가 난다.
Pass by value vs Pass by reference
자바는 Pass by value만 존재.
static void modify(MyObject obj) {
obj.value = 20; // ✅ 같은 객체 필드 변경 → 호출자에 반영
obj = new MyObject(30); // ❌ 지역변수 obj가 새 객체 가리킴 → 호출자 무관
}
자기 점검
arg2 = arg1(메서드 안)이 호출자에 영향 못 주는 이유를 스택 프레임으로 설명하라.메서드 호출의 2단계:
1. Class Metadata Zone에서 시그니처 확인 ("무엇을 호출")
2. Static/Non-Static Zone에서 실제 바이트코드 찾아 실행 ("실제 코드 어디")
→ "무엇을" 과 "어디에" 가 분리되어 있어 다형성/오버라이딩이 가능.
호출 경로
new 연산자가 실제로 하는 일new 없이 객체 만드는 법: Reflection, clone, 역직렬화 (PART 5, 6과 연결)바이트코드: .java → javac → .class. JVM이 이해하는 어셈블리 비슷한 명령어, 플랫폼 독립적("Write Once, Run Anywhere"). 확인: javap -c MyClass
상수 풀(Constant Pool): 컴파일 시점에 클래스 파일 내부에 생성. 문자열 상수·클래스/필드/메서드 참조 저장. 바이트코드의 #숫자 = 상수 풀 인덱스.
심볼 참조(Symbolic Reference)
4: new #7 // class org/example/CalHap (#7 = 상수 풀 7번 = 클래스 심볼)
7: dup // 스택의 객체 참조 복제 (생성자 호출 후에도 참조 필요)
10: invokespecial #9 // 생성자 호출
invokestatic(static), invokespecial(생성자/private), invokevirtual(인스턴스 다형성) 구분실습: javap -c(바이트코드), javap -v(상수 풀까지)
목표: 객체가 어디에 저장되는지 본 다음, 그것을 자동으로 회수하는 GC의 원리·알고리즘·종류 진화를 이해하고 운영 환경에서 선택·튜닝할 수 있게 된다.
참조 카운팅의 치명적 한계 (왜 JVM은 안 쓰나)
Root → A ⇄ B)에서 Root가 끊겨도 A↔B 카운터가 0이 안 됨 → 메모리 누수. + 카운터 갱신 비용.| 알고리즘 | 핵심 | 단점 |
|---|---|---|
| Reference Counting | 카운트 0이면 회수 | 순환 참조 누수 |
| Mark-and-Sweep | Root 추적 마킹 후 비마킹 제거 | Compaction 없어 단편화 |
| Mark-and-Compact | Sweep 후 살아남은 객체 모음 | Compact 오버헤드 |
| Generational (실제) | Young/Old 분리 관리 | 구조 복잡 |
진화 서사: 메모리가 커지고 코어가 늘면서, GC도 "한 스레드로 다 멈추고 청소"에서 "정지시간을 예측·통제"하는 방향으로 발전.
| GC | 등장 | 특징 | 적합 |
|---|---|---|---|
| Serial GC | 초기 | 싱글 스레드 | CPU 1개, 작은 메모리 |
| Parallel GC | Java 7~8 default | 멀티 스레드 GC | 처리량 우선 |
| CMS | (Java 9 deprecated) | STW 최소화 | 응답성 |
| G1 GC | Java 9+ default | Region 단위 | 대부분의 서버 |
| Z GC | Java 11+ | STW 10ms 이하 | 초저지연 |
내 GC 확인: java -XX:+PrintCommandLineFlags -version
기존: [ Eden | Survivor | Old ] ← 크기·위치 고정
G1 : [E][ ][S][O][ ][O][E]... ← 같은 크기, 역할 동적실습: java -XX:+UseG1GC, java -XX:+PrintGCDetails
목표: 데이터를 보관·조작하는 자료구조를 용도에 맞게 선택한다. 전체 지도를 잡고, 핵심 구현체는 내부 구조까지 코드로 검증한다.
"hello"는 String Constant Pool에 저장, 같은 값 리터럴은 재사용(a == b가 true)new String("hello")는 강제로 Heap에 새 객체자기 점검
String a="abc"; String b=new String("abc");일 때 a.equals(b)와 a==b의 결과는?String + String을 반복하면 불변이라 매번 새 객체 생성 → 루프에서 객체 폭증.append()가 새 객체를 안 만듦.Collection 인터페이스를 List/Set/Queue가 상속, Map은 별도(key-value)Collection ─┬─ List ──┬─ ArrayList
│ ├─ LinkedList
│ └─ Vector
├─ Set ──┬─ HashSet
│ ├─ TreeSet
│ └─ LinkedHashSet
└─ Queue ─┬─ LinkedList
├─ ArrayDeque
└─ PriorityQueue
Map (Collection X) ─┬─ HashMap
├─ LinkedHashMap
├─ TreeMap
├─ HashTable
└─ ConcurrentHashMap
| 구현체 | 내부 | Thread Safe | 비고 |
|---|---|---|---|
| ArrayList | 배열(1.5배 확장) | ❌ | 가장 많이 씀 |
| LinkedList | 이중 연결 리스트 | ❌ | Queue도 구현 |
| Vector | 배열(=ArrayList) | ✅(synchronized) | 느려서 거의 안 씀 |
new ArrayList<>(1000))이 효율적인 이유.| 작업 | ArrayList | LinkedList |
|---|---|---|
| 위치 찾기 | O(1) 인덱스 접근 | O(n) 순차 탐색 |
| 실제 삽입/삭제 | O(n) 메모리 복사 | O(1) 참조 변경 |
→ 데이터가 많을수록 ArrayList의 복사 비용 폭증. "위치를 알 때" LinkedList 삽입은 진짜 O(1).
| 구현체 | 특징 | 내부 |
|---|---|---|
| HashSet | 순서 X, 중복 X | HashMap 기반 (hashCode+equals로 중복 검사) |
| TreeSet | 자동 정렬, 중복 X | Red-Black Tree (삽입 O(log n)) |
| LinkedHashSet | 삽입 순서 유지 | HashMap + LinkedList |
| 동작 | 안전(실패 시 false/null) | 강제(실패 시 예외) |
|---|---|---|
| 삽입 | offer() | add() |
| 조회 | peek() | element() |
| 제거 | poll() | remove() |
| 구현체 | 순서 | null | Thread Safe |
|---|---|---|---|
| HashMap | ❌ | 키 1개, 값 다수 | ❌ |
| LinkedHashMap | ✅ 삽입순 | HashMap과 동일 | ❌ |
| TreeMap | ✅ 키 정렬 | 값만 | ❌ |
| HashTable | ❌ | ❌ | ✅ (메서드 동기화) |
| ConcurrentHashMap | ❌ | ❌ | ✅ (락 단위 최적화) |
put/get/remove 모두 O(log n).충돌 해결 1: 체이닝(자바 HashMap 채택)
충돌 해결 2: 오픈 어드레싱
LoadFactor 0.75: 배열의 75%가 차면 2배 확장 + rehash. 작으면 메모리 낭비, 크면 충돌 증가.
Collection이 iterator() 제공. hasNext()/next()/remove()로 내부 구조와 무관하게 동일 코드 순회.for-each는 내부적으로 Iterator 사용. 순회 중 컬렉션 수정 시 ConcurrentModificationException.목표: 타입 안전성을 지키는 제네릭, 객체 정렬 기준, 함수를 값으로 다루는 함수형까지 자바의 표현력을 완성한다.
String[]을 Object[]로)이지만 제네릭은 불공변 — List<String>은 List<Object>가 아니다(타입 안전성 때문).List<?>: 어떤 타입이든 OK, 꺼낸 원소는 Object로만, add 불가.? extends T (Producer, 읽기): Box<? extends Fruit>는 꺼내기 안전, 넣기 불가.? super T (Consumer, 쓰기): Box<? super Fruit>는 넣기 안전, 꺼낼 땐 Object.PECS 원칙: 꺼낸다(Produce) → extends, 넣는다(Consume) → super. 양쪽 다면 T 자체.
<, >로 비교 불가(어떤 필드 기준인지 모호).compareTo(T o) 하나. 음수/0/양수. 클래스의 기본 정렬(natural ordering). TreeSet/TreeMap/Collections.sort()가 자동 사용.compare(o1, o2). 여러 정렬 기준 가능, 클래스 수정 권한 없어도 가능.Comparator<Member> byAge = (a, b) -> a.age - b.age;
Comparator<Member> byName = Comparator.comparing(m -> m.name);
list.sort(byAge.reversed());
| Comparable | Comparator | |
|---|---|---|
| 위치 | 클래스 내부 | 외부 |
| 메서드 | compareTo | compare |
| 기준 수 | 1개(기본) | 무제한 |
Object 상속). 접근: 생성자·필드·메서드·어노테이션, Class<?> API.Class<?> clazz = Class.forName("org.example.Member");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method m = clazz.getMethod("hap", int.class, int.class);
m.invoke(obj, 1, 2);
함수형 인터페이스: 추상 메서드가 정확히 1개. @FunctionalInterface로 강제 검증. 람다로 인스턴스화.
| 인터페이스 | 시그니처 | 용도 |
|---|---|---|
Function<T,R> | R apply(T) | 변환 |
Predicate<T> | boolean test(T) | 조건 |
Consumer<T> | void accept(T) | 소비 |
Supplier<T> | T get() | 공급 |
람다: 익명 함수의 간결 표현(익명 클래스의 발전형). 외부 지역변수 캡처는 사실상 final.
스트림 (I/O 스트림과 이름만 같고 완전히 다름)
filter, map, sorted, distinct, limit, skipforEach, collect, count, reduce, anyMatchsList.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
Map<Integer, List<String>> byLen =
sList.stream().collect(Collectors.groupingBy(String::length));
목표: 파일·네트워크와 데이터를 주고받는 추상화를, 로우레벨 스트림의 불편함에서 출발해 NIO·직렬화까지 이해한다.
| 구분 | IO (1.0~) | NIO (1.4+) | NIO.2 (7+) |
|---|---|---|---|
| 단위 | 스트림(1바이트) | 채널+버퍼(블록) | 채널+버퍼 |
| 방향 | 단방향 | 양방향 | 양방향 |
| Blocking | 항상 | Non-blocking 가능 | Non-blocking 가능 |
| 파일 API | File | FileChannel | Files(static), Path |
File → Files/Path 진화: File.delete()는 false만 반환(이유 모름). Files.delete(path)는 정확한 예외 throw, 모든 메서드 static.read()가 데이터 올 때까지 스레드 정지, close()로만 빠져나옴).System.in(InputStream)은 read()로 1바이트씩. 영어 1byte는 OK지만 한글은 2~3byte(UTF-8)라 깨짐.FileInputStream: read()는 EOF에서 -1 반환(그래서 반환 타입이 byte가 아닌 int). 사용 후 close 필수.byte[] 버퍼로 한 번에 여러 바이트(함정: 마지막에 버퍼가 다 안 차면 읽은 수 n까지만 처리).FileOutputStream: write(int), new FileOutputStream(path, true)는 이어쓰기.FileReader(직접) vs InputStreamReader(보조 스트림, 인코딩 명시 가능).FileWriter로 한글 쓰기 가능.try (Resource r = ...)로 자동 close. 여러 자원은 선언 역순으로 닫힘. AutoCloseable 구현 필요.int/double/String)을 타입별로 저장/읽기(writeInt, readUTF...). CSV 파싱 불필요.Serializable(마커 인터페이스) 구현.ObjectOutputStream.writeObject() / ObjectInputStream.readObject()transient: 직렬화 제외 필드. 비밀번호·토큰·임시 캐시는 transient 필수(안 붙이면 바이트 스트림에 평문 노출 위험).serialVersionUID: 버전 식별자. 명시 안 하면 컴파일러가 자동 생성하는데, 클래스 변경 시 UID가 바뀌어 역직렬화 시 InvalidClassException. → 운영 시스템에서 반드시 명시.목표: 여러 스레드가 동시에 움직이는 세계의 모든 것. 면접·실무에서 가장 자주 등장. 로우레벨(직접 스레드)에서 고통을 느끼고 하이레벨(Executor·CompletableFuture)로 올라간다.
멀티스레드 관점 변수 안전성: "스레드 안전한가?" ≈ "공유되는가?"
| 변수 | 위치 | 안전성 |
|---|---|---|
| 지역 변수 | Stack(스레드별) | 안전(공유 X) |
| 인스턴스 변수 | Heap | 공유 → 동기화 필요 |
| static 변수 | Method Area | 공유 → 동기화 필요 |
| Blocking | Non-Blocking | |
|---|---|---|
| Sync | 전통 IO (가장 단순/비효율) | NIO Polling |
| Async | Future.get() | CompletableFuture/Callback (가장 효율적) |
start()는 새 스레드, run() 직접 호출은 그냥 메서드 호출.| 문제 | 정의 | 예시 |
|---|---|---|
| 가시성(Visibility) | 한 스레드 변경이 다른 스레드에 안 보임 | runFlag=false 했는데 무한 루프 |
| 원자성(Atomicity) | 동시 수정이 서로 덮어씀 | count++가 일부 손실 |
count++는 사실 읽기 → +1 → 쓰기 3단계라 원자적이지 않다.진화 서사: 가장 안전하지만 느린 synchronized → 가시성만 싼값에 주는 volatile → 락 없이 원자성을 얻는 Atomic.
synchronized (둘 다 해결, 대신 느림)
Class 객체. 블록은 범위 최소화 가능.volatile (가시성만, 원자성 ❌)
count++는 여전히 위험.volatile boolean shutdown).Atomic + CAS (락 없이 원자성, 빠름)
AtomicInteger.incrementAndGet() — 락 없이 원자적.| 도구 | 가시성 | 원자성 | 방식 | 성능 |
|---|---|---|---|---|
| synchronized | ✅ | ✅ | 락(블로킹) | 가장 느림 |
| volatile | ✅ | ❌ | 메모리 동기화 | 빠름 |
| Atomic | ✅ | ✅ | CAS(논블로킹) | 빠름 |
park()/parkNanos()/unpark(). 너무 저수준이라 직접 안 씀, ReentrantLock의 내부 도구.try-finally로 unlock 필수(중간 예외 시 unlock 누락하면 데드락).tryLock()(즉시 실패 false), tryLock(time, unit)(시간 한정). 락을 못 얻으면 포기·재시도 → 데드락 회피.wait()는 락을 반납하고 WAITING, notify()는 깨움. if가 아니라 while로 조건 재검사(Spurious wakeup). notifyAll() 권장.interrupt()(신호 발송, 플래그 true), isInterrupted()(확인만), interrupted()(static, 확인 후 false). blocking 메서드 중 인터럽트 시 InterruptedException(플래그 자동 false).로우레벨의 불편함: 스레드 1개 ≈ 1MB + OS 시스템 콜. "1000번 호출에 1000개 생성"은 비현실적. 무한 생성 시 자원 고갈, Runnable은 반환값·체크 예외 불가.
해결 = 스레드 풀 + 반환 가능한 작업 인터페이스 = Executor.
newFixedThreadPool(n)(일반 서버), newCachedThreadPool()(짧은 작업 다수, 트래픽 폭주 시 위험), newSingleThreadExecutor()(순서 보장), newScheduledThreadPool(n)(주기 실행).Callable과 Future (Runnable 한계 극복)
| Runnable | Callable | |
|---|---|---|
| 메서드 | void run() | V call() throws Exception |
| 반환값 | ❌ | ✅ |
| 체크 예외 | ❌ | ✅ |
Future: submit() 반환, get()으로 결과 회수(블로킹). cancel(mayInterruptIfRunning).invokeAll(모두 완료 대기) / invokeAny(첫 완료 채택).shutdown()(진행/큐 마무리) vs shutdownNow()(인터럽트·큐 포기). 운영 종료는 shutdown() 후 awaitTermination.thenApply/thenAccept/thenCombine 체이닝, 결과 준비 시 콜백 자동 실행(논블로킹).CompletableFuture.supplyAsync(() -> 5)
.thenApply(x -> x * 2)
.thenCombine(other, Integer::sum)
.thenAccept(System.out::println);parallelStream()의 내부 엔진.compute()에서 작으면 직접, 크면 left.fork() → right.compute() → left.join().디버깅 도구: jstack <pid>(스레드 덤프), VisualVM.
목표: "DAO 코드 한 줄이 어떻게 Spring의 IoC/DI까지 진화하는지" 한 클래스의 리팩토링 여정으로 OOP 원칙과 Spring의 본질을 동시에 익힌다. 여기서부터 PART 1의 SOLID가 실제로 살아 움직인다.
public void add(User user) throws ... {
Class.forName("com.mysql.jdbc.Driver"); // ① 드라이버 로딩
Connection c = DriverManager.getConnection( // ② 접속 정보
"jdbc:mysql://localhost/toby", "root", "*****");
PreparedStatement ps = c.prepareStatement( // ③ SQL
"insert into users(id,name,password) values (?,?,?)");
ps.setString(1, user.getId()); // ④ 바인딩
ps.executeUpdate(); // ⑤ 실행
ps.close(); c.close(); // ⑥ 자원 해제
}
"관심이 같은 것끼리 모으고, 다른 것은 떨어뜨려라" (= SRP)
getConnection()을 private 메서드로 → 중복 제거, DB 정보 변경 시 한 곳만.getConnection()을 추상 메서드로. 변하지 않는 흐름은 부모, 변하는 부분은 자식(NUserDao, DUserDao).JdbcTemplate의 이름 유래)getConnection)을 서브클래스에 위임. (BeanFactory의 "Factory")public interface ConnectionMaker { Connection makeConnection(); }
public class UserDao {
private ConnectionMaker connectionMaker; // 인터페이스에만 의존
public UserDao(ConnectionMaker cm) { this.connectionMaker = cm; } // 외부 주입
public void add(User u) { Connection c = connectionMaker.makeConnection(); ... }
}
new NConnectionMaker()로 자기가 결정·생성.| 라이브러리 | 프레임워크 | |
|---|---|---|
| 흐름 제어 | 내 코드 | 프레임워크 |
| 호출 방향 | 내 코드 → 라이브러리 | 프레임워크 → 내 코드 |
@Configuration
public class DaoFactory {
@Bean public UserDao userDao() { return new UserDao(connectionMaker()); }
@Bean public ConnectionMaker connectionMaker() { return new DConnectionMaker(); }
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = ctx.getBean("userDao", UserDao.class);
@Bean 호출해 생성·의존 주입.getBean()을 100번 호출해도 객체 1개. 모든 빈은 기본 싱글톤. 단 싱글톤 빈은 stateless여야 안전(인스턴스 변수에 요청별 데이터 보관 ❌ → PART 7 동시성과 직결).목표: PART 8에서 만든 IoC/DI 코드를 테스트로 검증하고, Spring MVC 학습 전 웹 백엔드 인프라 용어를 정리한다.
main() 검증은 매번 사람이 눈으로 판단·반복 → 비효율. 자동화 단위 테스트 = 코드가 코드를 검증. 조건: 자동화·격리·빠름·반복 가능.assertThat(actual, is(expected)) — 자연어처럼 읽힘. (nullValue, containsString, greaterThan 등)@Test 메서드마다 새 인스턴스 생성 → 테스트 간 독립성 보장. @BeforeEach(각 테스트 전) vs @BeforeAll(전체 1회).@BeforeEach로 매번 새로 생성, 시작 상태 보장(deleteAll() 등).@Transactional, @Sql로 격리.웹서버 vs WAS
| 웹서버 | WAS | |
|---|---|---|
| 역할 | 정적 자원(HTML/CSS/이미지) | 동적 자원(서블릿/JSP) |
| 예시 | Apache, Nginx | Tomcat, Jetty, Undertow |
[클라이언트] ↔ [Nginx 정적] ↔ [Tomcat 동적] ↔ [DB]서블릿/JSP: 서블릿=자바로 HTTP 요청 처리하는 클래스. JSP=HTML 안에 자바, 컴파일 시 서블릿으로 변환. 진화: 서블릿만(가독성 최악) → JSP만(유지보수 지옥) → MVC(JSP=View, 서블릿=로직) → 현대는 Thymeleaf 등.
SSR vs CSR
| SSR | CSR | |
|---|---|---|
| 화면 생성 | 서버에서 HTML 완성 | 브라우저(JS) |
| 초기 로딩 | 빠름 | 느림 |
| SEO | 좋음 | 까다로움 |
| 예시 | JSP, Thymeleaf, Next.js | React/Vue SPA |
JAR vs WAR
| JAR | WAR | |
|---|---|---|
| 포함 | Class, 라이브러리 | + JSP/Servlet/WEB-INF |
| 실행 | java -jar(JRE) | 외부 WAS 필요 |
목표: "DB 코드 한 줄이 어떻게 JdbcTemplate까지 진화하는지" — DB 접근 자체의 추상화 여정. PART 8의 DAO 진화에 이어지는 두 번째 추상화 스토리.
Socket socket = new Socket("localhost", 3306); // MySQL 프로토콜 직접...DriverManager vs HikariCP vs DBCP2)마다 사용법이 달라 변경 시 애플리케이션 코드가 다 바뀜.javax.sql.DataSource 표준 인터페이스(핵심 메서드 getConnection() 하나). 구현체: HikariDataSource, BasicDataSource, DriverManagerDataSource.@Autowired private DataSource dataSource; // 인터페이스 의존
Connection c = dataSource.getConnection(); // 어떤 구현체든 OK@Autowired private JdbcTemplate jdbcTemplate;
List<Customer> list = jdbcTemplate.query(sql, new Object[]{age},
new BeanPropertyRowMapper<>(Customer.class));
update(INSERT/UPDATE/DELETE, 영향 행 수), queryForObject(단일 행), query(여러 행), execute(DDL).queryForObject 0건 → EmptyResultDataAccessException, 2건+ → IncorrectResultSizeDataAccessException.BeanPropertyRowMapper는 컬럼명↔필드명(snake↔camel) 자동 매핑.목표: JdbcTemplate(SQL Mapper)보다 한 단계 높은 추상화(ORM). 그리고 트랜잭션을 어노테이션 한 줄로 줄이는 마지막 진화(@Transactional, AOP의 첫 만남).
| JOIN | 의미 |
|---|---|
| INNER | 양쪽 공통 행만(교집합) |
| LEFT | 왼쪽 전부 + 매칭(없으면 NULL) |
| RIGHT | 오른쪽 전부 + 매칭 |
| FULL OUTER | 양쪽 전부(합집합), MySQL은 미지원 → LEFT UNION RIGHT |
| 측면 | 객체 | 관계형 DB |
|---|---|---|
| 모델링 | 상태+행동 | 행과 열 |
| 상속 | 있음 | 없음(SINGLE_TABLE/JOINED/TABLE_PER_CLASS로 표현) |
| 연관 | 참조(order.member) | 외래 키 |
| 식별 | == | PK |
| SQL Mapper | JPA | |
|---|---|---|
| SQL | 개발자 | 자동 생성 |
| 매핑 | RowMapper 수동 | 어노테이션 선언 |
| 객체 그래프 | 수동 | 자동(Lazy Loading) |
| 학습곡선 | 낮음 | 높음 |
Repository 인터페이스만 만들면 findByName 같은 메서드 이름으로 쿼리 자동 생성), Querydsl(타입 안전 동적 쿼리, 컴파일 시점 오류 검출). 실무 표준 조합: Spring Data JPA + Querydsl.| 전략 | 동작 | 적합 DB |
|---|---|---|
| IDENTITY | auto_increment | MySQL, PostgreSQL |
| SEQUENCE | 시퀀스 객체 | Oracle, PostgreSQL |
| TABLE | 키 생성 테이블 | 모든 DB(느림) |
| AUTO | 자동 선택 | 기본값 |
name/length/nullable/unique/columnDefinition. Spring Boot는 camelCase ↔ snake_case 자동 변환(SpringPhysicalNamingStrategy)이라 @Column(name="item_name") 생략 가능.① 수동 트랜잭션의 한계 (로우레벨)
conn.setAutoCommit(false);
try { ...; conn.commit(); }
catch (Exception e) { conn.rollback(); throw e; }
finally { conn.close(); }
② PlatformTransactionManager (인터페이스 추상화) — PART 10 DataSource와 같은 사상
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition def);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
③ @Transactional (선언적 트랜잭션, AOP)
@Transactional 빈은 실제로 프록시 객체가 등록됨.@Transactional
public void transfer(Long fromId, Long toId, int amount) {
accountDao.withdraw(fromId, amount);
accountDao.deposit(toId, amount);
} // 시작·커밋·롤백·Connection 관리 자동
this.inner()는 프록시를 거치지 않아 트랜잭션 안 걸림@Transactional(rollbackFor = Exception.class)REQUIRED(기본, 있으면 참여), REQUIRES_NEW(항상 새), NESTED(중첩/Savepoint)readOnly = true — 읽기 전용, 영속성 컨텍스트 최적화 → 성능 ↑목표: PART 11에서 "@Transactional은 프록시"라고만 했던 그 프록시가 어떻게 만들어지고 적용되는지의 모든 메커니즘. 자바·Spring 학습의 하이라이트. 김영한 스프링 핵심 원리 고급편의 압축.
set/get은 그 스레드만 본다. (PART 7 동시성 + PART 8 싱글톤과 직결)remove() 필수: 스레드 풀에서 스레드가 재사용되므로 값이 남아있으면 다음 요청에 이전 사용자 데이터 노출(보안 사고). 반드시 finally에서 remove().PART 8에서 본 패턴들의 재등장. 여기에 콜백이 신규로 더해진다.
Context→Template, Strategy→Callback. 콜백 = 인수로 넘겨 나중에 호출되는 실행 코드. → Spring의 JdbcTemplate·RestTemplate·TransactionTemplate 등 XxxTemplate 시리즈가 모두 이 패턴.BufferedReader/InputStreamReader(PART 6)가 데코레이터의 대표 사례.로우레벨의 고통: 수동 프록시는 적용 대상 100개면 거의 같은 프록시 클래스 100개. 유지보수 지옥.
method.invoke(target)로 어떤 메서드든 동적 호출. 유연하지만 컴파일 시점 오류 검출 불가 → 프레임워크 개발용.InvocationHandler 하나로 프록시 클래스를 런타임 자동 생성($Proxy1). 구현체가 100개여도 핸들러 1개.MethodInterceptor). 인터페이스 없어도 OK. final 클래스/메서드엔 불가(상속 불가). → PART 14 JPA 프록시, 엔티티 final 금지의 이유와 연결.| JDK 동적 프록시 | CGLIB | |
|---|---|---|
| 전제 | 인터페이스 필요 | 구체 클래스로 OK |
| 방식 | 인터페이스 구현 | 클래스 상속 |
| 핸들러 | InvocationHandler | MethodInterceptor |
proxyTargetClass=true면 강제 CGLIB). Spring Boot는 기본 CGLIB(일관성).InvocationHandler)와 CGLIB(MethodInterceptor)를 개념적으로 통합. 개발자는 org.aopalliance.intercept.MethodInterceptor의 invoke(MethodInvocation)에서 invocation.proceed()만 감싸면 됨.| 용어 | 의미 |
|---|---|
| Pointcut | "어디에" 적용할지 (필터링) |
| Advice | "어떤 로직"(부가 기능) |
| Advisor | Pointcut + Advice |
AspectJExpressionPointcut. 예: execution(* hello.proxy.app..*(..)).@Order.@Service·@Repository로 컴포넌트 스캔된 실제 객체는 프록시로 바꿀 방법이 없음.spring-boot-starter-aop가 자동 등록하는 빈 후처리기): 등록된 모든 Advisor를 찾아 각 빈의 Pointcut 매칭 시 프록시로 교체. 이름 = Annotation 인식 + AspectJ 표현식 + AutoProxyCreator.@Aspect 클래스 → Advisor 자동 변환. 단 자동 빈 등록은 아님(@Component/@Bean/@Import 필요).@Around(어드바이스+포인트컷), ProceedingJoinPoint.proceed()로 target 호출.| 용어 | 의미 |
|---|---|
| 조인 포인트(Join point) | 부가 기능을 적용할 수 있는 모든 지점 |
| 포인트컷(Pointcut) | 조인 포인트 중 실제 적용할 곳 선택 |
| 어드바이스(Advice) | 적용할 부가 기능 |
| 애스펙트(Aspect) | 포인트컷 + 어드바이스의 모듈 |
| 타겟(Target) | 부가 기능이 적용되는 실제 객체 |
| 위빙(Weaving) | 포인트컷으로 어드바이스를 결합(Spring AOP는 빈 후처리 시점) |
| AOP 프록시 | JDK 동적 프록시 또는 CGLIB |
@Before, @AfterReturning, @AfterThrowing, @After(finally).@Pointcut으로 시그니처 분리 → 여러 어드바이스가 재사용(DRY). 별도 Pointcuts 클래스로 모으고 &&/||/!로 조합.목표: PART 11에서 입문한 @Transactional을, PART 12의 AOP 메커니즘 위에서 끝까지 판다. 면접·실무에서 가장 자주 부딪히는 영역.
@Configuration을 한 곳에서 결합(@Import({A.class, B.class})). @ComponentScan(자동)과 달리 명시적. 컴포넌트 스캔 대상 밖(외부 라이브러리 @Configuration, 테스트 전용 빈) 등록에 사용. @EnableXxx도 내부적으로 @Import 활용.@Transactional): 어노테이션 한 줄, 간편하나 함정 많음. 프로그래밍(TransactionTemplate): 세밀 제어, 단 비즈니스 로직과 기술 코드 강결합. → 선언적이 거의 표준.getTransaction/commit/rollback try-catch가 비즈니스 로직과 뒤섞임(PART 11.5의 수동 코드). 프록시 도입 후: 트랜잭션 프록시가 그 책임을 모두 가져가고 서비스엔 순수 비즈니스 로직만 남음. 이게 가능한 건 PART 12의 빈 후처리기 + AnnotationAwareAspectJAutoProxyCreator 덕분.@Transactional 없는 external()이 같은 클래스의 @Transactional internal()을 this.internal()로 호출 → this는 프록시가 아닌 target → @Transactional 무시.@Transactional 메서드를 별도 빈으로 분리(다른 클래스 호출은 프록시를 거침). SRP 관점에서도 더 나음. (AspectJ 컴파일 방식이면 이 문제 없음.)@PostConstruct + @Transactional → 트랜잭션 적용 안 됨. @PostConstruct는 빈 후처리기(자동 프록시 생성)가 동작하기 이전에 실행 → 그 시점엔 프록시가 아직 없음.@EventListener(ApplicationReadyEvent.class) + @Transactional → 컨테이너 완성 후(모든 빈 프록시 교체 완료) 실행되어 정상 동작. (대안: ApplicationRunner, 별도 트랜잭션 빈 분리.)| 함정 | internal call (13.3) | @PostConstruct (13.4) |
|---|---|---|
| 본질 | 프록시를 거치지 않는 호출 | 프록시가 아직 안 만들어짐 |
| 시점 | 런타임 this 호출 | 빈 초기화 시점 |
| 해결 | 클래스 분리 | ApplicationReadyEvent |
→ 두 함정은 "프록시 없음"이라는 한 본질의 두 얼굴. 함께 외운다.
rollbackOnly=true 표시 → 외부가 commit 시도하면 rollbackOnly 발견 → 롤백 실행 + UnexpectedRollbackException. ("논리 하나라도 롤백되면 물리는 롤백.")트레이드오프: 격리 ↑ → 정합성 ↑·동시성 ↓. 적절한 수준 = 정합성과 성능의 균형점.
| 수준 | Dirty | Non-repeatable | Phantom | 기본 채택 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ | ❌ | ❌ | (표준 인정 X) |
| READ COMMITTED | ✅ | ❌ | ❌ | Oracle, PostgreSQL |
| REPEATABLE READ | ✅ | ✅ | ❌ | MySQL InnoDB (Gap Lock으로 일부 Phantom 방지) |
| SERIALIZABLE | ✅ | ✅ | ✅ | (동시성 최저) |
@Transactional(isolation = Isolation.REPEATABLE_READ).목표: PART 11에서 입문한 JPA의 모든 메커니즘. 영속성 컨텍스트가 왜 강력한지, 연관관계와 N+1을 어떻게 다루는지. SQL 로그를 켜고 학습해야 체화됨.
| SQL Mapper(MyBatis/JdbcTemplate) | ORM(JPA/Hibernate) | |
|---|---|---|
| 매핑 | SQL ↔ 객체 | 객체 ↔ 테이블 |
| SQL | 개발자 | JPA 자동 생성 |
| 복잡 통계 | 자유 | 어려움(JPQL/네이티브 필요) |
EntityManager 등)일 뿐, 직접 동작 안 함. 구현체는 Hibernate(~95%), EclipseLink 등. spring-boot-starter-data-jpa가 Hibernate 자동 설정.App → JPA(jakarta.persistence) → Hibernate → JDBC → DB. JPA도 내부적으로 JDBC·DataSource·HikariCP를 그대로 사용.@Id 식별자 ③ final 클래스/필드 금지(프록시=CGLIB 상속, PART 12.6) ④ enum/interface/inner 불가.@Embeddable/@Embedded): 여러 필드를 한 값 객체로 묶음(주소·금액·연락처). 불변 권장(가변이면 공유 시 부작용). 같은 타입 여러 번이면 @AttributeOverrides. (일종의 반정규화 — PART 15.2와 연결)ddl-auto: create/create-drop/update(데이터 손실 위험, 운영 금지), validate, none. 운영은 none + Flyway/Liquibase.@Transactional이 자동 생성·종료. 스레드 안전 X(트랜잭션마다 별도 인스턴스 → 동시성 회피). 핵심 메서드: persist/find/getReference/remove/merge/detach.persist/find → 영속(Managed) → detach/clear/close → 준영속(Detached); remove → 삭제(Removed). merge로 준영속 → 영속.detach(컨텍스트만 분리, DB 그대로) ≠ remove(커밋 시 DB 삭제).find 재조회 시 SQL 없이 캐시 반환. 트랜잭션 단위.== true(1차 캐시에서 같은 인스턴스). (다른 트랜잭션이면 false → equals/hashCode는 ID 기반으로)persist 시 즉시 INSERT 안 하고 SQL 저장소에 모음 → 커밋 시 한꺼번에 전송(배치 최적화, hibernate.jdbc.batch_size). 단 IDENTITY 전략은 쓰기 지연 불가(INSERT 후에야 ID).update() 호출 불필요. 준영속은 변경 감지 X.em.flush() ② 커밋 직전 ③ JPQL 실행 직전(메모리 변경을 먼저 반영해 일관된 결과). 플러시 ≠ 커밋(플러시 후에도 롤백 가능).@ManyToOne)이 가장 많이 사용. FK는 항상 N쪽. 1:1은 자주 조회되는 쪽에 FK.@ManyToMany)은 직접 사용 금지: 자동 조인 테이블에 부가 정보(수량·일시) 못 넣음 → 중간 엔티티(예: MemberProduct→Order)로 1:N + N:1로 풀고 도메인 의미를 부여.mappedBy(읽기 전용). 규칙: N쪽(@ManyToOne)이 주인, 1쪽이 mappedBy="주인_엔티티의_필드명".fetch=LAZY + 컬렉션 필드 초기화 + 편의 메서드 + @ToString에서 컬렉션 제외(무한 루프 방지).em.find()(실제 엔티티, 즉시 SELECT) vs em.getReference()(프록시, 필드 접근 시 SELECT). 프록시는 CGLIB로 원본 상속 → 비교는 instanceof(== 비교는 false). EM 닫힌 후 프록시 접근 시 LazyInitializationException.@ManyToOne·@OneToOne = EAGER(위험!), @OneToMany·@ManyToMany = LAZY.fetch=LAZY로 명시. EAGER 필요 시 fetch join(아래).JOIN FETCH는 연관까지 함께 SELECT → 한 방.LIMIT이 JOIN으로 뻥튀기된 행 수에 적용 → 의도한 부모 개수가 안 나오고, Hibernate가 전체를 메모리로 로드해 페이징(OOM 경고). → ManyToOne 페이징은 정상.IN 쿼리로 묶어 조회 → N+1 및 OneToMany 페이징 함정 동시 해결. 전역 default_batch_fetch_size: 100. (fetch join과 함께 쓰면 BatchSize 무시됨)| 도구 | 적합 |
|---|---|
| fetch join | ManyToOne 단건, 페이징 없는 컬렉션 |
| @BatchSize | OneToMany + 페이징, 일반 LAZY 최적화 |
| EntityGraph | 동적 fetch 전략 |
ALL/PERSIST/REMOVE/...). 단일 소유 부모-자식(주문-주문항목)에만. orphanRemoval: 컬렉션에서 빠진 자식을 DB 삭제. CASCADE.REMOVE(부모 자체 삭제 트리거) vs orphanRemoval(자식 컬렉션 제거 트리거). 둘 다면 완전한 부모-자식 관리.목표: 그동안 JPA가 추상화해 가려놓았던 DB 본연의 영역으로 내려간다. 모델링·정규화·인덱스·옵티마이저·분산이론. 모든 ORM의 기반.
@Entity가 이 둘을 잇는 다리(PART 14.3).' OR '1'='1 같은 SQL 주입 → 쿼리 조작(데이터 탈취·삭제·권한 우회).? 바인딩) ⭐: 쿼리 미리 컴파일 → 입력은 데이터로만 처리(SQL 키워드 X). JPA는 파라미터 바인딩으로 자동 적용. 다층 방어: Prepared Statement + 입력 검증 + 에러 숨김 + 최소 권한 + WAF. (단 동적 테이블/컬럼명은 여전히 위험)| 유형 | 대표 | 사례 |
|---|---|---|
| Key-Value | Redis, DynamoDB | 캐시·세션 |
| Document | MongoDB | 가변 구조 |
| Column-Family | Cassandra, HBase | 대량 시계열 |
| Graph | Neo4j | 관계 분석 |
@Lock(PESSIMISTIC_READ/WRITE). 격리 수준과 직결. 비관적 락 vs 낙관적 락.ANALYZE TABLE./*+ INDEX(t idx) */ 등)·모드(FIRST_ROWS/ALL_ROWS). 남용 주의.WHERE a=7 AND b=95✅, WHERE a>5✅, WHERE b=95❌(a로 분산 → Full Scan과 다름없음). 카디널리티 낮은 컬럼(상태값)은 인덱스 효과 적음.type(접근 방식, 좋은 순 const > eq_ref > ref > range > index > ALL, ALL=Full Scan 위험), key(실제 사용 인덱스), Extra(Using index=Covering, Using filesort/Using temporary=성능 ↓).USE INDEX(유도) / FORCE INDEX(강제) / IGNORE INDEX(무시). 우선순위: ANALYZE → EXPLAIN → 인덱스 추가 → 힌트(마지막).Extra: Using index). SELECT *는 이를 깸.ALGORITHM=INPLACE).목표: PART 15(이론)에 이어 운영 측면 — 데이터 타입, 고가용성, 확장, SQL 고급 도구. DB 영역의 이론+운영을 완성한다.
utf8_general_ci(대소문자 무시, 기본) vs utf8_bin(구분). 대소문자 구분은 COLLATE utf8_bin 또는 BINARY. 시스템 코드·ID는 구분, 일반 검색은 무시.| Replication | Clustering | |
|---|---|---|
| Write | Master만 | 모든 노드 |
| 일관성 | 비동기(지연) | 동기/즉시 |
| Failover | 수동/반자동 | 자동 |
| 목적 | 읽기 성능·백업 | 고가용성·즉시 Failover |
DROP PARTITION 즉시 삭제.| Partitioning | Sharding | |
|---|---|---|
| 단위 | 단일 DB 내 테이블 | 여러 DB 서버 |
| 트랜잭션 | 정상 | 분산 어려움 |
| 복잡도 | 낮음 | 높음 |
| DELETE | TRUNCATE | DROP | |
|---|---|---|---|
| 분류 | DML | DDL | DDL |
| 대상 | 특정 행(WHERE) | 모든 행 | 테이블 자체 |
| 속도 | 느림 | 빠름 | 즉시 |
| ROLLBACK | 가능 ✅ | 불가 ❌ | 불가 ❌ |
| AUTO_INCREMENT | 유지 | 초기화 | (사라짐) |
| 트리거 | ✅ | ❌ | ❌ |
LEFT JOIN ... WHERE B IS NULL. (PART 11.1, JPQL JOIN과 연결)CALL). 현대는 사용 감소 — 테스트·버전 관리·포팅·CI/CD 어려움. 비즈니스 로직은 애플리케이션(Spring 서비스)에, DB는 데이터 저장에 집중. 단 대량 ETL·관리 작업엔 적합.| Trigger | VIEW | PROCEDURE | |
|---|---|---|---|
| 호출 | 자동(이벤트) | SELECT | CALL(수동) |
| 매개변수 | OLD/NEW | X | ✅ |
| 용도 | 자동 부수 작업 | 가상 테이블 | 명시적 작업 단위 |
목표: PART 9에서 본 Servlet/웹 인프라를 토대로, 매일 쓰지만 내부는 모르는 DispatcherServlet의 요청 처리 흐름과 REST API 설계를 정복한다. PART 12의 AOP가 Filter·Interceptor와 어떻게 다른지도 여기서 비로소 제자리를 찾는다.
로우레벨의 불편함: 순수 Servlet은 URL마다 클래스를 만든다. URL이 수백 개면 Servlet이 폭증하고, 인증·로깅 같은 공통 처리가 모든 Servlet에 중복되며, URL 라우팅과 JSON 변환을 전부 수동 작성해야 한다.
해결 — Front Controller 패턴: 모든 요청을 한 곳에서 받아 적절한 처리기로 위임한다(전화 교환원 비유). 공통 처리를 한곳에 모으고 라우팅을 중앙화한다. Spring MVC의 DispatcherServlet이 정확히 이 역할이며, 그 정체는 HttpServlet을 상속한 평범한 Servlet 하나다. Spring Boot가 / 경로에 자동 등록한다.
위치 그림(요청이 흐르는 순서):
[Client] → [Servlet Container(Tomcat)] → [Filter Chain] → [DispatcherServlet]
→ [Interceptor Chain] → [Controller] → [Service/Repository(AOP)] → [DB]
1. DispatcherServlet HTTP 요청 수신
2. HandlerMapping 어떤 컨트롤러 메서드인지 탐색
3. HandlerAdapter 그 메서드를 통일된 방식으로 호출
4. Interceptor.preHandle 컨트롤러 호출 전
5. Controller 로직 실행 → (ModelAndView 또는 객체) 반환
6. Interceptor.postHandle 컨트롤러 호출 후, View 렌더 전
7. ViewResolver View 이름 → View 객체 (REST면 HttpMessageConverter가 객체→JSON)
8. View.render() HTML/JSON 응답 생성
9. Interceptor.afterCompletion 응답 완료 후(예외 포함)
모든 단계가 인터페이스로 추상화돼 빈 등록으로 확장 가능하다는 점이 Spring MVC의 핵심 강점이다.
RequestMappingHandlerMapping(@GetMapping 등 스캔). 같은 URL 중복 매핑 시 AmbiguousMappingException. 매핑 확인은 Actuator /actuator/mappings.RequestMappingHandlerAdapter가 표준. 내부에서 ArgumentResolver가 @PathVariable/@RequestParam/HttpServletRequest/@AuthenticationPrincipal 등 파라미터를 타입별로 추출 — 커스텀 ArgumentResolver로 확장 가능(예: 인증 사용자 자동 주입).preHandle(false 반환 시 컨트롤러·이후 인터셉터 모두 중단), postHandle, afterCompletion. 등록은 WebMvcConfigurer.addInterceptors로 addPathPatterns/excludePathPatterns.| 어노테이션 | 추출 위치 | 용도 |
|---|---|---|
@PathVariable | URL 경로 /users/{id} | 리소스 식별자 |
@RequestParam | 쿼리/폼 ?name= | 검색 조건·필터 |
@RequestBody | HTTP body(JSON) | 복잡한 객체(POST/PUT) |
@ModelAttribute | 쿼리+폼 → 객체 바인딩 | GET 다중 검색 조건 |
@RequestBody/@ResponseBody의 실체. MappingJackson2HttpMessageConverter가 Jackson ObjectMapper로 JSON↔객체 변환. JavaTimeModule(LocalDateTime), Include.NON_NULL 등 커스터마이즈. JPA Lazy 프록시를 그대로 직렬화하면 LazyInitializationException·무한 루프 → DTO 변환 / @JsonIgnore / fetch join으로 해결(PART 14 연결).Accept 헤더로 응답 형식 결정. produces/consumes로 명시 가능.@RestController가 ViewResolver를 건너뛰고 HttpMessageConverter로 직접 JSON 변환.@RestController = @Controller + @ResponseBody.return ResponseEntity.status(HttpStatus.CREATED)
.header("Location", "/users/" + saved.getId()).body(saved); // 201
표준 코드: 200/201/204, 400/401/403/404/409, 500. (401=인증 실패, 403=권한 없음 — PART 19와 직결)
세 도구는 "공통 처리를 어디서 거느냐"의 위치가 다르다.
| Filter | Interceptor | AOP | |
|---|---|---|---|
| 위치 | DispatcherServlet 외부 | DispatcherServlet 내부 | 메서드 단위 |
| 표준 | Servlet 표준 | Spring | Spring |
| Spring 빈 접근 | 제한적 | 가능 | 가능 |
| Handler 정보 | X | ✅(HandlerMethod) | ✅ |
| req/res 자체 변경 | ✅(Wrapper) | 어려움 | 인자/반환 |
| 적용 단위 | 전역 | URL 패턴 | 메서드 |
| 대표 활용 | Security·CORS·인코딩 | 인증·로깅·Rate Limit | 트랜잭션·비즈 로깅 |
호출 순서: Filter → Interceptor.preHandle → AOP before → Controller → AOP after → Interceptor.postHandle → afterCompletion → Filter.
선택 기준: 모든 요청 인코딩/CORS·Security → Filter(가장 외곽 차단), URL 패턴별 인증/로깅 → Interceptor, 메서드 단위 트랜잭션/감사 로그 → AOP(PART 12). 자기 호출 함정도 AOP에 그대로 적용(PART 13.3).
@ExceptionHandler(ExceptionHandlerExceptionResolver).@RestControllerAdvice 로 전역 예외 처리 → 모든 컨트롤러 예외를 한곳에서 표준 응답으로.@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex){ ... } // 400
@ExceptionHandler(EntityNotFoundException.class) ... // 404
@ExceptionHandler(Exception.class) ... // 500 (운영에선 스택트레이스 숨김)
}
@NotBlank/@Size/@Email/@Min 등. 컨트롤러 파라미터에 @Valid 없으면 검증 안 됨. 실패 시 MethodArgumentNotValidException. 중첩 객체·리스트는 @Valid 재귀.code(프로그래밍 식별), message(사람용), timestamp, path, errors(필드별). 운영에서 스택트레이스·DB 메시지 노출 금지./users/{id}/orders), 동사는 HTTP 메서드로.Pageable(page/size/sort 자동 바인딩). Page(전체 count 쿼리 추가 — 페이지 번호 UI) vs Slice(size+1 조회, count 없음 — 무한 스크롤). 대용량 OFFSET 비용은 Cursor 기반(afterId)으로. 페이징+OneToMany fetch join은 메모리 페이징 위험 → @BatchSize(PART 14)./swagger-ui.html). 버저닝은 URL Path(/api/v1) 방식이 가장 명확.목표: PART 1~17이 "하나의 앱 + 하나의 DB"였다면, 여기서부턴 여러 시스템이 협력한다. PART 15~16의 CAP·Replication·Quorum 토대 위에 캐싱(Redis)·메시징(Kafka)·MSA를 올린다.
| 자료구조 | 용도 | 대표 명령 |
|---|---|---|
| String | 캐시·카운터·Rate Limit | SET/GET/INCR |
| List | 큐·최근 활동 | LPUSH/RPOP/LRANGE |
| Set | 중복 제거·교집합 | SADD/SINTER |
| Hash | 객체(부분 업데이트) | HSET/HGET/HINCRBY |
| Sorted Set | 랭킹·시계열(score=ts)·우선순위 | ZADD/ZREVRANGE/ZRANK |
everysec 권장). 일반 운영은 RDB+AOF 병행.SET key val NX EX 30(없을 때만, 자동 만료). 해제는 "내 락이면 DEL"을 Lua로 원자 처리. 단일 Redis 장애 대비 Redlock(여러 인스턴스 과반수 SET). Java는 Redisson RLock. GC pause로 락 만료 중 타 노드 획득 위험이 가장 큼 — 단일 DB면 @Transactional+행 락으로 충분한 경우 많음.@EnableCaching + spring.cache.type=redis. @Cacheable/@CacheEvict/@CachePut은 AOP로 동작 → 자기 호출 시 안 됨(PART 13.3 함정 동일), @CacheEvict의 트랜잭션 커밋 전 실행(beforeInvocation) 일관성 주의.StringRedisSerializer(key) + GenericJackson2JsonRedisSerializer(value). 클라이언트는 비동기·스레드 안전한 Lettuce가 기본.| RabbitMQ | Redis | Kafka | |
|---|---|---|---|
| 용도 | 일반 메시징 | 간단·캐시 | 대규모 스트리밍 |
| 영속성 | 옵션 | 옵션(Stream) | 기본 |
| Replay | X | Stream만 | ✅ |
hash(key)%n)로 같은 키는 같은 파티션 → 순서 보장. 글로벌 순서가 필요하면 파티션 1개(처리량 제한).KafkaTemplate.send(topic, key, value)(비동기, CompletableFuture). Consumer: @KafkaListener(topics, groupId). Manual commit은 Acknowledgment.acknowledge(). 직렬화는 JSON(간단) 또는 Avro/Protobuf+Schema Registry(대규모). 에러는 재시도(DefaultErrorHandler+FixedBackOff)→DLT(Dead Letter Topic), 그리고 멱등 처리.목표: PART 17의 Filter가 본격적으로 활약하는 무대. 인증(누구인가) vs 인가(무엇을 할 수 있나)를 분리하고, Session vs Token, JWT, OAuth2, 웹 보안까지 정복한다.
Spring Security는 Servlet Filter로 동작 → DispatcherServlet 도달 전 차단/통과 결정(가장 외곽).
Filter Chain
└ DelegatingFilterProxy → FilterChainProxy
├ SecurityContextPersistenceFilter (Session↔SecurityContext 복원/저장)
├ UsernamePasswordAuthenticationFilter (/login POST 처리)
├ BasicAuthenticationFilter
├ ExceptionTranslationFilter (인증→401/로그인, 인가→403)
└ FilterSecurityInterceptor (최종 인가 결정)
@Async)에서 컨텍스트 손실 주의(전파 필요). 현재 사용자는 @AuthenticationPrincipal 또는 Authentication 주입으로.JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 추가하고 SessionCreationPolicy.STATELESS.AuthenticationManager(ProviderManager) → DaoAuthenticationProvider가 loadUserByUsername + PasswordEncoder.matches → 성공 시 인증 토큰 → SecurityContext 저장.HttpSecurity.authorizeHttpRequests): permitAll/authenticated/hasRole/hasAuthority/hasAnyRole. 순서 중요(구체→일반). Role은 ROLE_ 접두사 자동, Authority는 그대로.@EnableMethodSecurity): @PreAuthorize("hasRole('ADMIN')"), SpEL로 인자 참조(#id == authentication.principal.id), @PostAuthorize(반환 객체 검사), @PreFilter/@PostFilter(컬렉션). AOP 기반 → 자기 호출 함정 동일. @PostFilter는 DB에서 전부 가져와 필터링하므로 성능 주의.| 측면 | Session | JWT |
|---|---|---|
| 상태 | Stateful(서버 보관) | Stateless |
| 저장 | 서버 메모리/Redis | 클라이언트 |
| 확장성 | Sticky/공유 저장소 | 자유 |
| 즉시 폐기 | ✅ | ❌(어려움) |
| 공격 | CSRF 위험 | XSS 위험(저장 위치) |
| 모바일/MSA | 부적합 | 적합 |
Header.Payload.Signature(Base64URL . 구분). Header alg: HS256(대칭, 단일 서버) vs RS256(비대칭, MSA 권장 — 비밀키 발급·공개키 검증). Payload 표준 Claim: iss/sub/aud/exp/iat/nbf/jti + 커스텀. Payload는 암호화가 아니라 인코딩 → 민감정보 금지. Signature는 위변조 방지(JWT는 암호화가 아니라 서명: 내용은 보이되 변조하면 들킴).JwtTokenProvider(생성/검증) + JwtAuthenticationFilter(OncePerRequestFilter 상속 — 요청당 1회). 검증은 보통 DB 조회 없이 서명만.alg:none 공격, 알고리즘 혼동(RS256↔HS256), 약한 키(256비트+), 탈취(HttpOnly Cookie+HTTPS+짧은 만료), Clock Skew(clockSkewSeconds), Payload 민감정보 노출. 검증 시 알고리즘 명시./userinfo. ID Token=사용자 정보, Access Token=API 권한. Spring은 spring-boot-starter-oauth2-client + oauth2Login. 자체 OAuth2 Server는 보통 Keycloak/Auth0 같은 솔루션 사용.Access-Control-Allow-Origin/Methods/Headers). allowCredentials=true면 * 와일드카드 불가 → 명시적 origin. SPA(localhost:3000)↔API(8080) 분리 시 필수, 같은 도메인+리버스 프록시면 불필요.목표: PART 9의 테스트 입문을 넘어 "테스트로 설계한다"의 단계로. Mockito·슬라이스·MockMvc·Testcontainers·TDD·품질 도구까지.
Clock 주입) / Self-validating(assertion) / Timely. + Given-When-Then 가독성, 한글 메서드명("조건상황결과").@Test/@BeforeEach/@AfterEach/@BeforeAll(static)/@DisplayName/@Nested/@ParameterizedTest. @Nested로 컨텍스트 그룹화.assertThat...): 객체·컬렉션(extracting)·예외(assertThatThrownBy)·Optional·시간. 한 객체 여러 속성은 Soft Assertion(assertAll로 모든 실패 보고).@ParameterizedTest 소스 5종: @ValueSource/@CsvSource/@CsvFileSource/@MethodSource(Stream<Arguments>)/@EnumSource. 같은 로직·다른 입력이면 파라미터화.| Stub | Mock | Spy | Fake | |
|---|---|---|---|---|
| 검증 | 상태(반환값) | 행동(호출) | 둘 다 | 상태 |
| 진짜 동작 | X | X | 일부 | 단순화 |
when().thenReturn(), Mock=verify(). Spy=진짜 객체+일부 stub(남용 주의), Fake=메모리 구현(In-Memory Repo). Dummy=인자 채우기.@ExtendWith(MockitoExtension.class) + @Mock + @InjectMocks. verify(repo, times(2)/never()/atLeast()), Argument Matchers(any()/eq()/argThat() — 섞을 때 모두 매처), void는 doThrow().when(), 인자 상세 검증은 ArgumentCaptor.Clock 주입 권장)/private(→추출)/new(→Factory·빈 분리). Best Practice: 한 테스트=한 검증, 인터페이스 mock, Mock 5개+면 통합 테스트 신호.@SpringBootTest(전체, 느림 — 남발 금지) 대신 필요한 슬라이스만: @WebMvcTest(Controller/Filter/Interceptor) / @DataJpaTest(Repository+EntityManager, 자동 트랜잭션 롤백·H2 기본) / @JsonTest / @DataRedisTest.@DataJpaTest: TestEntityManager로 준비 후 em.clear()로 1차 캐시 비워 진짜 SELECT 검증(PART 14). 운영 DB 특화 기능은 @AutoConfigureTestDatabase(replace=NONE)+Testcontainers. 쿼리 카운터로 N+1도 테스트.@Mock(Mockito 단독) vs @MockBean(Spring 컨텍스트 주입 — 남발 시 컨텍스트 재생성으로 느림).perform(get/post...).andExpect(status()..., jsonPath("$.id").value(...)).andDo(print()). JsonPath로 배열·중첩·조건 검증.@RestControllerAdvice(404 등) 검증. Security는 @WithMockUser(roles=)(빠르고 충분) / @WithUserDetails / 실제 JWT 헤더. @Import(SecurityConfig.class)로 슬라이스에 포함.@Testcontainers + @Container + @DynamicPropertySource. Spring Boot 3.1+는 @ServiceConnection으로 보일러플레이트 제거(여러 컨테이너 조합 가능). 성능은 withReuse(true)+testcontainers.reuse.enable.@Transactional 롤백(빠르나 REQUIRES_NEW·비동기엔 함정) / @AfterEach 정리 / @Sql. 비동기 검증은 Awaitility await().@DisplayName으로 의도 명시(Cucumber는 학습비용 ↑).@DataJpaTest) → 진짜. Mock 남용은 통합 테스트 전환 신호.목표: 앞의 모든 코드가 사용자에게 전달되고 운영되는 영역. HTTP/네트워크 토대 → 컨테이너/K8s → CI/CD → Observability.
server.http2.enabled=true.SO_REUSEADDR·Connection Pool). 신뢰성: Sequence/ACK/Checksum/Flow Control/Congestion Control. TCP(신뢰·느림) vs UDP(비신뢰·빠름: DNS·게임·VoIP)..dockerignore, 명시적 버전(latest 금지).depends_on+healthcheck, volumes로 데이터 영속). 개발/테스트용(운영은 K8s)./actuator/health/{liveness,readiness}. HPA(CPU 등 메트릭 기반 Pod 개수 자동 조절, min~max) — Scale-Out(VPA는 Scale-Up).needs 의존)/Step/Action. Gradle·Docker 레이어 캐싱으로 50% 단축, Secret은 Repository Secrets. main 브랜치만 배포(안정성).12원칙 중 자주 위반: Config(환경변수/Secret 분리, 하드코딩 금지), Processes/Stateless(Pod 재시작 OK, Session은 외부 Redis — PART 19), Logs(파일 X, stdout → 플랫폼이 수집), Disposability(Graceful Shutdown: server.shutdown=graceful — SIGTERM 시 진행 요청 완료 후 종료), Dev/Prod Parity.
trace_id 전 로그 전파. ELK(Elasticsearch/Logstash/Kibana) 또는 Grafana Loki./actuator/prometheus 노출. 타입 4종: Counter(누적)·Gauge(현재값)·Histogram(분포)·Summary(Percentile). Grafana 시각화. SLI(지표)/SLO(목표)/SLA(계약).Part별 자기 점검은 본문 참조. 아래는 "이건 막힘없이 답해야 다음 단계로" 수준의 관문.
언어·JVM
1. 자바에 pass by reference가 없다는 말의 정확한 의미(스택 프레임으로)?
2. m과 new Member() 본체는 각각 어디에 저장되는가?
3. 바이트코드의 #7(심볼 참조)은 무엇이며 왜 필요한가?
GC·컬렉션
4. JVM이 참조 카운팅을 안 쓰는 이유는?
5. G1이 큰 힙에 적합한 이유(리전·정지시간 예측)?
6. HashMap LoadFactor 0.75와 Java 8 트리 변환의 의미는?
제네릭·함수형
7. PECS를 한 문장으로?
8. 스트림이 한 번만 사용 가능하고 지연 평가되는 이유는?
동시성
9. synchronized/volatile/Atomic의 해결 범위·성능 비교는?
10. CAS 4단계와 ABA 문제는?
11. Sync/Blocking을 가르는 두 개의 축은?
12. 직접 스레드 사용의 3가지 문제 → Executor가 어떻게 해결하나?
스프링
13. IoC에서 무엇이 무엇으로 역전되는가? DI와의 관계는?
14. 싱글톤 빈이 stateless여야 하는 이유(동시성과 연결)?
15. JdbcTemplate에 적용된 디자인 패턴 2가지는?
DB·JPA(입문)
16. Connection Pool이 필요한 이유를 TCP 관점에서?
17. ACID 4가지를 한 문장씩?
18. JPA가 JDBC를 대체하는가? (정확히)
19. @Transactional이 동작하기 위한 조건과 self-invocation 문제는?
AOP·프록시 (PART 12)
20. 수동 프록시 100개 문제 → JDK 동적 프록시 / CGLIB가 각각 어떻게 해결하나? (전제 조건 차이)
21. Pointcut·Advice·Advisor의 관계, target 1개에 AOP 여러 개면 프록시는 몇 개?
22. 빈 후처리기(AnnotationAwareAspectJAutoProxyCreator)가 하는 일은?
23. AOP 용어 7가지 중 위빙(Weaving)은 Spring AOP에서 언제 일어나나?
트랜잭션 심화 (PART 13)
24. internal call 함정과 @PostConstruct 함정 — 공통 본질과 각각의 해결은?
25. REQUIRED에서 내부 롤백 시 UnexpectedRollbackException이 던져지는 이유는?
26. Dirty / Non-repeatable / Phantom Read 차이와 4단계 격리 매트릭스, MySQL 기본값은?
JPA 심화 (PART 14)
27. 변경 감지(Dirty Checking)의 내부 동작(스냅샷)과 전제 조건은?
28. 4가지 연관관계의 기본 fetch 전략(외울 것)과 LAZY 명시 권장 이유는?
29. N+1이 EAGER·LAZY 모두에서 발생하는 이유, fetch join vs @BatchSize 선택은?
30. mappedBy의 값이 가리키는 것과 연관관계 주인이 필요한 이유는?
데이터베이스 (PART 15~16)
31. 1NF/2NF/3NF/BCNF를 한 줄씩, 3가지 이상 현상의 공통 원인은?
32. logN이 N보다 압도적인 이유(N=백만)와 멀티컬럼 인덱스 "왼쪽 우선" 원칙은?
33. EXPLAIN type의 좋은/나쁜 값, Covering Index란?
34. CAP에서 P가 필수인 이유, CP vs AP 대표 시스템은?
35. Replication vs Cluster의 결정적 차이, Quorum 최소 3노드 권장 이유는?
36. DELETE/TRUNCATE/DROP의 ROLLBACK 가능 여부와 그 이유(UNDO LOG)는?
Spring MVC (PART 17)
37. DispatcherServlet 9단계를 순서대로, HandlerMapping vs HandlerAdapter 역할 차이는?
38. Filter·Interceptor·AOP의 위치·표준·적용 단위 차이와 호출 순서는?
39. @RequestBody가 HttpMessageConverter로 동작하는 흐름, Lazy 프록시 직렬화 문제 해결은?
40. PUT/PATCH/POST 멱등성, Page vs Slice 선택은?
분산 시스템·MSA (PART 18)
41. 2PC의 한계와 Saga(Orchestration vs Choreography)가 대체하는 이유는?
42. 캐싱 4패턴과 3대 함정(Stampede/Penetration/Avalanche) 방어는?
43. Kafka Topic/Partition/Offset/Consumer Group 관계, acks 0/1/all 차이는?
44. Outbox가 해결하는 이중 쓰기 문제, At-least-once + 멱등성이 표준인 이유는?
Spring Security (PART 19)
45. 401 vs 403, SecurityFilterChain 핵심 필터와 SecurityContextHolder(ThreadLocal)는?
46. Session vs JWT 5가지 비교, Stateless의 장단점은?
47. JWT 3부분 역할, Payload 민감정보 금지 이유, HS256 vs RS256은?
48. CSRF·XSS·CORS의 원리와 방어, JWT는 어디에 저장하나?
테스트 (PART 20)
49. Mock/Stub/Spy/Fake 차이, @Mock vs @MockBean은?
50. @SpringBootTest 남발 문제와 슬라이스(@DataJpaTest의 em.clear 이유)는?
51. H2 vs Testcontainers 선택, @ServiceConnection 효과는?
52. 테스트 피라미드 비율과 Coverage 100%가 환상인 이유는?
HTTP·DevOps·Observability (PART 21)
53. TCP 3-way handshake와 TIME_WAIT, HTTP/1.1·2·3 차이는?
54. TLS 1.2 vs 1.3, 비대칭+대칭을 함께 쓰는 이유는?
55. Pod/Deployment/Service 역할, liveness/readiness/startup Probe 차이는?
56. Rolling/Blue-Green/Canary 선택, 502 vs 504, 3 Pillars와 SLI/SLO/SLA는?
| PART | 실무 적용 |
|---|---|
| 2~3 (JVM/GC) | OOM·heap dump 분석, GC 로그 튜닝 |
| 4 (컬렉션) | DTO/Entity 매핑 시 자료구조 선택 |
| 7 (동시성) | 스레드 덤프, @Async, 싱글톤 빈 동시성 |
| 8 (IoC/DI) | Service/Repository 분리, Strategy, Spring DI |
| 10 (DB) | HikariCP 튜닝, 트랜잭션 경계 설계 |
| 11 (JPA 입문) | Spring Data JPA + Querydsl, @Transactional 전파 설계 |
| 12 (AOP) | 변경 이력/감사 로그, 로그 추적기(ThreadLocal), 트랜잭션 프록시 |
| 13 (트랜잭션 심화) | 전파 설계, 격리 수준 선택(MySQL REPEATABLE READ), 초기 데이터 로딩 |
| 14 (JPA 심화) | N+1 튜닝(fetch join/@BatchSize), 영속성 컨텍스트·변경 감지, 연관관계 설계 |
| 15 (DB 이론) | 정규화 검토, 인덱스 설계·EXPLAIN, 마스터 데이터 Redis 캐싱 |
| 16 (DB 운영) | Read Replica 도입, 파티셔닝 후보, 데이터 타입 설계, 백업 전략 |
| 17 (Spring MVC) | 표준 에러 응답(@RestControllerAdvice), REST URL·상태코드 설계, 페이징·문서화(SpringDoc) |
| 18 (분산·MSA) | Redis 캐싱·분산 락, Kafka 비동기 처리·DLT, Modular Monolith·Saga·Outbox |
| 19 (Security) | JWT 인증 필터·Refresh Rotation, @PreAuthorize, CORS·CSRF·보안 체크리스트 |
| 20 (테스트) | 단위(Mockito)·@DataJpaTest·MockMvc·Testcontainers, ArchUnit 아키텍처 규칙 |
| 21 (운영) | Multi-stage·Layered JAR, K8s Probe·HPA, CI/CD 무중단 배포, 구조화 로그·메트릭·추적 |
[ ] PART 1 객체지향(OOP) 기초
[ ] PART 2 JVM 메모리 모델과 실행 원리
[ ] PART 3 GC
[ ] PART 4 문자열과 컬렉션
[ ] PART 5 제네릭·비교·함수형
[ ] PART 6 I/O와 직렬화
[ ] PART 7 멀티스레딩과 동시성
[ ] PART 8 객체 설계의 진화 → IoC/DI
[ ] PART 9 테스트와 웹 인프라
[ ] PART 10 DB 접근의 진화
[ ] PART 11 ORM/JPA와 트랜잭션 추상화
[ ] PART 12 프록시의 진화와 Spring AOP
[ ] PART 13 트랜잭션 심화 (전파·격리·라이프사이클 함정)
[ ] PART 14 JPA 심화 (영속성 컨텍스트·연관관계·N+1)
[ ] PART 15 데이터베이스 펀더멘털 (이론)
[ ] PART 16 데이터베이스 운영
[ ] PART 17 Spring MVC 내부와 REST API
[ ] PART 18 분산 시스템·캐싱·메시징·MSA
[ ] PART 19 Spring Security (인증·인가·JWT·OAuth2)
[ ] PART 20 테스트 심화
[ ] PART 21 HTTP·네트워크·DevOps·Observability
[ ] 통합 졸업 점검 관문 통과
PART 14(영속성 컨텍스트·N+1)와 PART 15~16(인덱스·실행 계획)은 이론만 읽으면 체화되지 않는다. SQL 로그를 켜고(show-sql, format_sql, org.hibernate.SQL: DEBUG) 직접 쿼리가 어떻게 나가는지, EXPLAIN으로 실행 계획이 어떻게 잡히는지 눈으로 확인할 것.
다음 주차 추가 시: PART 22 이후로 이어 붙인다. 새 주차가 기존 PART의 심화면 해당 PART에 "(심화)" 소단원으로 병합하고, 신규 주제면 새 PART로 만든 뒤 위 "기술 진화 척추" 표·졸업 관문·진도 체크리스트에 반영한다. (남은 후보 영역: 코딩 테스트 알고리즘, 시스템 디자인 면접, 프론트엔드 심화, 클라우드 네이티브 패턴)