🎯 F-lab Java 10주차 학습 커리큘럼

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~10주차 흐름 정리

주차주제핵심 변화
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주차 (지금)트랜잭션 정리 + 빈 라이프사이클 함정 + 격리 수준트랜잭션 학습의 마무리

🗓️ 권장 학습 일정 (압축 5일)

DayPhase학습 목표
1일차Phase 1 + 2@Import + 스프링 트랜잭션 추상화 정리
2일차Phase 3선언적 vs 프로그래밍 + 프록시 적용
3일차Phase 4@PostConstruct + @Transactional 함정 (★)
4일차Phase 5트랜잭션 격리 수준 (★)
5일차종합 자기 점검 + 실습전체 정리

여유 일정 (7일): Phase 4·5에 각 +1일. 격리 수준은 직접 SQL을 돌리며 시각적으로 체화하는 게 효과적.


📚 Phase 1 — @Import: 설정 클래스의 결합

목표: 여러 설정 파일로 분리된 빈을 하나로 묶는 작은 Spring 도구를 익힌다.

Unit 1.1 — @Import의 역할

선수 지식: 5주차 Phase 8 (ApplicationContext)

핵심 개념

문제:

  • 설정 클래스가 늘어나면 관리 부담
  • 도메인별/환경별로 분리하고 싶음

@Import 의 해결:

  • 여러 @Configuration 클래스를 한 곳에서 결합
  • 결합한 설정 클래스를 ApplicationContext에 등록하면 모든 빈이 자동 등록
@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 {}

자기 점검

  • 8-9주차에서 @Import(AspectV1.class) 를 본 맥락은?
  • @Import@ComponentScan 의 차이는? (힌트: 명시적 vs 자동)

Unit 1.2 — 실무에서의 @Import 활용 패턴

선수 지식: Unit 1.1

핵심 패턴

환경별 설정 분리:

@Configuration
@Import({DatabaseConfig.class, SecurityConfig.class, CacheConfig.class})
public class ApplicationConfig {}

테스트용 설정 추가:

@SpringBootTest
@Import(TestConfig.class)  // 테스트 전용 빈 추가
class IntegrationTest {}

언제 @Component 대신 @Import?:

  • 컴포넌트 스캔 대상 외부의 클래스
  • 외부 라이브러리의 @Configuration 클래스
  • 테스트에서만 활성화할 빈

자기 점검

  • 외부 라이브러리의 빈을 등록할 때 @ComponentScan으로는 왜 한계가 있는가?
  • @Import vs @EnableXxx 어노테이션의 관계는? (힌트: 후자도 @Import 활용)

📚 Phase 2 — 스프링 트랜잭션 추상화 정리 (7주차 복습+심화)

목표: 7주차에서 다룬 PlatformTransactionManager의 본질을 다시 짚고 Spring Boot의 자동 구성까지 정리한다.

Unit 2.1 — 트랜잭션 기술마다 다른 코드 (문제 정의)

선수 지식: 7주차 Phase 5

핵심 문제

기술마다 트랜잭션 코드가 다름:

  • JDBC: connection.setAutoCommit(false) + commit() / rollback()
  • JPA: EntityTransaction.begin() + commit() / rollback()
  • MyBatis: SqlSession 단위
  • Hibernate: Session 단위

결과:

  • JDBC → JPA로 전환 시 모든 트랜잭션 코드 수정
  • 기술 종속성이 비즈니스 로직까지 침투

추상화 필요 = PlatformTransactionManager

자기 점검

  • 6주차 DataSource 추상화의 사상과 같은가?
  • 7주차에서 본 동일한 토픽을 한 줄로 요약하면?

Unit 2.2 — 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, MyBatisDataSourceTransactionManager (= JdbcTransactionManager)
Hibernate (직접 사용)HibernateTransactionManager
JPAJpaTransactionManager

자기 점검

  • 인터페이스의 메서드가 단 3개인 이유는? (힌트: 트랜잭션의 본질)
  • 7주차의 DataSource 인터페이스 메서드 수와 비교하면?

Unit 2.3 — Spring Boot의 자동 트랜잭션 매니저 등록

선수 지식: Unit 2.2

핵심 개념

Spring Boot의 자동 구성:

  • 클래스패스의 라이브러리를 인식
  • 적절한 TransactionManager를 자동 빈 등록
의존성자동 등록
spring-boot-starter-jdbcDataSourceTransactionManager
spring-boot-starter-data-jpaJpaTransactionManager

개발자가 할 일:

  • 의존성만 추가
  • @Transactional 사용
  • 트랜잭션 매니저 선택·등록 모두 생략 가능

ILIC 사례:

  • spring-boot-starter-data-jpa 사용 → JpaTransactionManager 자동 등록
  • 별도 설정 불필요

자기 점검

  • 두 가지 데이터 접근 기술을 함께 쓰면? (힌트: ChainedTransactionManager 등)
  • 자동 구성이 잘못 동작하면 어떻게 진단하는가? (힌트: --debug 출력)

📚 Phase 3 — 선언적 vs 프로그래밍 방식 + AOP 적용 (9주차 복습+심화)

목표: 9주차에서 본 @Transactional의 본질을 "프록시 도입 전후 코드 비교" 로 다시 한번 못 박는다.

Unit 3.1 — 두 가지 트랜잭션 관리 방식

선수 지식: 7주차 Phase 7, 9주차 Phase 10

핵심 개념

방식정의장점단점
선언적 (@Transactional)어노테이션 한 줄 선언매우 간편함정 多 (internal call 등)
프로그래밍 (TransactionTemplate)트랜잭션 코드 직접 작성세밀한 제어비즈니스 로직과 결합

프로그래밍 방식의 한계:

"애플리케이션 코드가 트랜잭션이라는 기술 코드와 강하게 결합"

실무 결론:

"선언적 트랜잭션이 거의 표준 — 프로그래밍 방식은 특수 케이스에만"

자기 점검

  • "기술 코드와의 결합"이 왜 나쁜가? (힌트: 5주차 관심사 분리)
  • 프로그래밍 방식이 필요한 케이스는? (힌트: 동적 트랜잭션 범위 결정)

Unit 3.2 — 프록시 도입 전: 비즈니스 로직 안의 트랜잭션 코드

선수 지식: 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);
        }
    }
}

문제점:

  • 비즈니스 로직(②)과 트랜잭션 코드(①③④)가 한 곳에
  • 시간 측정·로깅·예외 변환까지 추가하면 배보다 배꼽
  • 매 메서드마다 반복

자기 점검

  • 8주차 Phase 1의 로그 추적기 문제와 같은가?
  • 5주차의 어떤 디자인 패턴이 이 문제를 해결했는가? (힌트: 템플릿 메소드, 전략)

Unit 3.3 — 프록시 도입 후: 순수 비즈니스 로직만

선수 지식: 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);
        }
    }
}

핵심 통찰:

"트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다.
서비스 계층에는 순수한 비즈니스 로직만 남는다."

자기 점검

  • 이 구조가 가능한 이유는 8-9주차 어떤 메커니즘 덕인가? (힌트: 빈 후처리기, AnnotationAwareAspectJAutoProxyCreator)
  • 프록시가 가져간 책임 4가지를 다시 나열하면?

📚 Phase 4 — 빈 라이프사이클의 함정 (★ @PostConstruct + @Transactional)

목표: 9주차의 internal call 함정과 짝을 이루는 또 하나의 면접 단골 함정을 마스터한다.

Unit 4.1 — @PostConstruct의 역할과 적합 사용처

선수 지식: 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 인터페이스
  • 생성자 직접 작업: DI 완료 전이라 의존성 사용 불가

자기 점검

  • 생성자에서 초기화 작업을 하면 어떤 문제가? (힌트: 의존성 미완)
  • @PostConstruct가 5주차의 어떤 라이프사이클 단계에서 호출되는가?

Unit 4.2 — @PostConstruct + @Transactional의 함정 (★★★ 면접 단골)

선수 지식: 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)가 빈 후처리기로 동작
  • @PostConstruct 호출은 빈 후처리기 적용 이전

internal call 함정과의 비교:

함정9주차 Internal Call10주차 @PostConstruct
본질프록시를 거치지 않는 호출프록시가 아직 만들어지지 않음
시점런타임 (객체 자체 호출)초기화 시점
해결클래스 분리ApplicationReadyEvent

자기 점검

  • 이 함정을 모르고 캐시 초기화에 트랜잭션을 걸면 어떤 사고가?
  • "프록시가 없다"는 말의 정확한 의미는?

Unit 4.3 — 해결책: ApplicationReadyEvent

선수 지식: Unit 4.2

핵심 해결

@Component
public class DataInitializer {

    @EventListener(value = ApplicationReadyEvent.class)
    @Transactional         // ✅ 정상 동작
    public void init2() {
        log.info("Hello init ApplicationReadyEvent");
        // DB 작업이 트랜잭션 안에서 실행됨
    }
}

왜 되는가:

  • ApplicationReadyEvent = 컨테이너가 완전히 생성된 후 발행되는 이벤트
  • 이 시점에는 모든 빈이 프록시로 교체 완료
  • → @Transactional 정상 적용

스프링 부트의 라이프사이클 이벤트 4종:

  • ApplicationStartingEvent — 애플리케이션 시작
  • ApplicationEnvironmentPreparedEvent — 환경 정보 준비
  • ApplicationContextInitializedEvent — 컨텍스트 초기화
  • ApplicationReadyEvent — 모든 준비 완료 (사용 권장)

다른 대안:

  • CommandLineRunner / ApplicationRunner 인터페이스
  • 별도의 트랜잭션 빈으로 분리 + 명시적 호출
  • TransactionTemplate (프로그래밍 방식)

자기 점검

  • ApplicationReadyEvent와 @PostConstruct가 호출되는 순서는?
  • ILIC 시작 시 마스터 데이터를 DB에서 메모리로 로드하는 작업에는 어느 것이 적합한가?

📚 Phase 5 — 트랜잭션 격리 수준 (★ DB와 Spring의 경계)

목표: 6주차 ACID의 'I(Isolation)'를 깊이 파헤친다. 3가지 충돌 → 4가지 격리 수준 매트릭스를 완성한다.

Unit 5.1 — 격리 수준이란

선수 지식: 6주차 Phase 6 (ACID)

핵심 개념

격리 수준 (Isolation Level):

"트랜잭션끼리 얼마나 서로 고립 되어 있는지의 수준"

왜 필요한가:

  • 트랜잭션의 I (Isolation) 보장 수단
  • Locking으로 해결 가능하지만 무조건 강한 락 = 성능 저하
  • 적절한 수준 = 정합성과 성능의 균형점

트레이드오프:

  • 격리 수준 ↑ → 정합성 ↑, 동시성 ↓
  • 격리 수준 ↓ → 정합성 ↓, 동시성 ↑

자기 점검

  • 6주차 ACID의 I와 격리 수준의 관계는?
  • 격리 수준이 동시성과 트레이드오프 관계인 이유는?

Unit 5.2 — Dirty Read (커밋 안 된 데이터 읽기)

선수 지식: 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는 환상의 값을 본 것)

자기 점검

  • B가 본 값으로 또 다른 작업을 했다면 어떤 사고가?
  • 가장 위험한 read 문제 3가지 중 어떤 것?

Unit 5.3 — Non-repeatable Read (반복 읽기 불가)

선수 지식: Unit 5.2

핵심 시나리오

"같은 트랜잭션 안에서 같은 데이터를 두 번 읽었는데 값이 다름"

원인: 다른 트랜잭션이 수정/삭제

예시:
1. 트랜잭션 A: x 조회 → 10
2. 트랜잭션 B: x = 10 → 50 변경 후 커밋
3. 트랜잭션 A: x 다시 조회 → 50 ← 같은 트랜잭션인데 다름!

언제 문제인가:

  • 같은 트랜잭션 안에서 일관된 스냅샷이 필요한 경우
  • 예: 보고서 생성 중 데이터가 바뀌면 합계가 틀어짐

자기 점검

  • Dirty Read와 Non-repeatable Read의 결정적 차이는? (힌트: 커밋 여부)
  • 6주차 격리성의 정의를 다시 본다면 이 문제와의 관계는?

Unit 5.4 — Phantom Read (유령 행)

선수 지식: Unit 5.3

핵심 시나리오

"같은 조건으로 두 번 조회했는데 결과 행 수가 다름"

원인: 다른 트랜잭션이 삽입(또는 삭제)

예시:
1. 트랜잭션 A: 잔액 ≥ 100만 조건으로 조회 → 5건
2. 트랜잭션 B: 잔액 200만 신규 계좌 INSERT 후 커밋
3. 트랜잭션 A: 같은 조건 다시 조회 → 6건 ← "유령" 행 등장

Non-repeatable Read와의 차이:

  • Non-repeatable Read: 기존 행의 값 변경
  • Phantom Read: 새로운 행 등장 (또는 사라짐)

자기 점검

  • 행 수가 같을 수 있어도 phantom read인 경우는? (힌트: 삭제 + 삽입)
  • ILIC의 "운임 견적 목록" 조회 중 phantom read가 발생하면?

Unit 5.5 — 4가지 격리 수준과 매트릭스 (★ 면접 단골)

선수 지식: Unit 5.2~5.4

핵심 매트릭스:

격리 수준Dirty ReadNon-repeatable ReadPhantom 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:

  • 커밋된 데이터만 조회
  • Oracle, PostgreSQL 기본
  • Dirty Read만 방지

Level 2 — REPEATABLE READ ⭐:

  • 트랜잭션 시작 시점의 스냅샷 유지
  • MySQL InnoDB 기본
  • Phantom Read만 가능 (MySQL은 Gap Lock으로 일부 방지)

Level 3 — SERIALIZABLE:

  • 모든 충돌 방지
  • 동시성 최저 → 거의 사용 안 함
  • 매우 엄격한 정합성이 필요한 영역에서만

Spring에서 격리 수준 지정:

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void method() { ... }

자기 점검

  • ILIC가 사용하는 MySQL의 기본 격리 수준은?
  • "성능을 위해 격리 수준을 낮추겠다"는 결정 시 검토할 4가지는?
  • SERIALIZABLE이 거의 안 쓰이는 이유는?

🎓 종합 자기 점검 (10주차 졸업 시험)

@Import

  1. @Import의 정의와 사용 시점은?
  2. @Import와 @ComponentScan의 차이는?
  3. 외부 라이브러리의 @Configuration을 등록할 때 어느 것을 쓰는가?

트랜잭션 추상화

  1. PlatformTransactionManager 인터페이스의 3가지 핵심 메서드는?
  2. Spring Boot가 자동으로 어떤 TransactionManager를 등록하는지 설명하라
  3. 7주차 DataSource 추상화와 같은 사상인 이유는?

선언적 vs 프로그래밍 방식

  1. 두 방식의 차이를 한 문장씩으로?
  2. "프로그래밍 방식이 비즈니스 로직과 강하게 결합"의 의미는?
  3. 프록시 도입 전후 서비스 코드의 차이를 설명하라

@PostConstruct 함정 (★)

  1. @PostConstruct + @Transactional이 동작하지 않는 이유는?
  2. 9주차 internal call 함정과의 본질적 차이는?
  3. ApplicationReadyEvent가 해결책인 이유는?
  4. 캐시 초기화 + 트랜잭션이 필요한 시나리오에 어떤 도구를 쓸 것인가?

트랜잭션 격리 수준 (★)

  1. 격리 수준이 동시성과 트레이드오프 관계인 이유는?
  2. Dirty Read, Non-repeatable Read, Phantom Read의 차이를 시나리오로 설명하라
  3. Non-repeatable Read와 Phantom Read의 결정적 차이는?
  4. 4가지 격리 수준 × 3가지 충돌 매트릭스를 작성하라
  5. MySQL의 기본 격리 수준과 그 이유는?
  6. Oracle/PostgreSQL의 기본 격리 수준과 그 이유는?
  7. ILIC에서 격리 수준을 명시적으로 변경하고 싶은 시나리오는?

📌 학습 운영 팁

9-섹션 마스터 프롬프트로 깊이 파야 할 Unit

★★★ 면접 단골 (반드시):

  • Unit 4.2 — @PostConstruct + @Transactional 함정 (9주차 internal call과 짝)
  • Unit 5.5 — 4가지 격리 수준 × 3가지 충돌 매트릭스

★★ 매우 권장:

  • Unit 4.3 — ApplicationReadyEvent 패턴
  • Unit 5.2~5.4 — 3가지 read 문제의 시나리오

Phase별 진도 체크리스트

[ ] 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문항 통과

두 함정의 짝 — 9주차와 10주차

internal call 함정과 @PostConstruct 함정은 같은 본질의 두 얼굴:

9주차 internal call10주차 @PostConstruct
본질프록시를 거치지 않는 호출프록시가 아직 만들어지지 않음
시점런타임, this 호출 시빈 초기화 시점
증상@Transactional 무시@Transactional 무시
해결클래스 분리ApplicationReadyEvent

두 함정은 함께 묶어 외워야 한다 (면접 빈출).

실습 권장

Phase 4 실습:

  • @PostConstruct에 @Transactional 추가 → 트랜잭션이 적용 안 되는 것 확인
  • TransactionSynchronizationManager.isActualTransactionActive() 로 검증
  • ApplicationReadyEvent로 변경 → 정상 동작 확인

Phase 5 실습:

  • MySQL에 두 개 세션 열기
  • 각 격리 수준에서 Dirty Read / Non-repeatable / Phantom 재현
  • SET TRANSACTION ISOLATION LEVEL 명령으로 수준 변경

1~10주차 통합 흐름의 마무리

트랜잭션 학습의 5단계 완결:

  • 6주차: 트랜잭션 ACID 개념 + JdbcTemplate
  • 7주차: PlatformTransactionManager + @Transactional 입문
  • 8-9주차: AOP 메커니즘 + @Transactional 동작 원리 + 트랜잭션 전파
  • 10주차: 트랜잭션 정리 + 빈 라이프사이클 함정 + 격리 수준 (지금)

이제 트랜잭션의 모든 것을 안다. 11주차부터는 새 영역으로 진입할 시점.

profile
Software Developer

0개의 댓글