"Repository 인터페이스만으로 어떻게 구현체가 생기는가"
"Repository를 만드는 것과, Repository가 어떻게 작동하는지 아는 것은 다르다"
동적 프록시 기반 Repository 생성부터 Propagation 7가지, N+1 해결, HikariCP 튜닝까지
왜 이렇게 설계됐는가 라는 질문으로 Spring Data / JPA / Hibernate 내부를 끝까지 파헤칩니다
Spring Data JPA에 관한 자료는 넘쳐납니다. 하지만 대부분은 "어떻게 쓰나" 에서 멈춥니다.
| 일반 자료 | 이 레포 |
|---|---|
"JpaRepository를 상속하면 CRUD가 자동으로 됩니다" |
RepositoryFactorySupport가 동적 프록시를 생성하고 SimpleJpaRepository로 위임하는 전체 과정 |
"@Transactional을 붙이면 트랜잭션이 적용됩니다" |
Propagation 7가지가 각각 AbstractPlatformTransactionManager에서 어떻게 분기되는가 |
"N+1 문제는 fetch join으로 해결합니다" |
Hibernate의 Lazy Loading 프록시 생성 원리, 5가지 해결 전략별 실제 SQL 쿼리 수 비교 |
"readOnly=true를 붙이면 성능이 좋아집니다" |
Hibernate FlushMode 변경, JDBC Connection readOnly 힌트, 스냅샷 생략으로 절약되는 실측 비용 |
"private 메서드에는 @Transactional이 안 됩니다" |
CGLIB 오버라이딩 불가 원리 + Self-Invocation 함정의 바이트코드 수준 설명 |
| 이론 나열 | 실행 가능한 코드 + Hibernate show_sql 출력 + 쿼리 수 실측 비교 |
선행 학습 권장: Spring Core Deep Dive — IoC, DI, AOP 프록시 원리를 이해한 후 이 레포를 학습하면 효과가 배가됩니다.
각 챕터의 첫 문서부터 바로 학습을 시작하세요!
💡 각 섹션을 클릭하면 상세 문서 목록이 펼쳐집니다
핵심 질문:
JpaRepository인터페이스만 선언했는데, 런타임에 어떻게 구현체가 생기는가?
동적 프록시 Repository 생성부터 Query Method 파싱, Auditing까지 (8개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Repository 프록시 생성 과정 | RepositoryFactorySupport.getRepository()가 JDK 동적 프록시를 생성하는 과정, SimpleJpaRepository로 위임되는 구조, @EnableJpaRepositories 트리거 |
| 02. Query Method 파싱 메커니즘 | findByNameAndAge()가 JPQL로 변환되는 PartTree 파싱 과정, Subject / Predicate 분리, 지원 키워드 목록과 한계 |
| 03. @Query JPQL vs Native Query 처리 | @Query 어노테이션이 NamedQuery와 다르게 처리되는 이유, Native Query 실행 경로, nativeQuery=true일 때 결과 매핑 방식 |
| 04. Projection — Interface vs DTO | Closed / Open Interface Projection이 Hibernate 프록시로 구현되는 방식, DTO Projection이 인터페이스 방식보다 성능상 유리한 조건 |
| 05. QueryDSL 통합 원리 | QuerydslPredicateExecutor가 Spring Data에 통합되는 방식, JPAQueryFactory 설정과 Q타입 생성 메커니즘, 타입 안전 동적 쿼리 |
| 06. Specifications를 통한 동적 쿼리 | JpaSpecificationExecutor와 Specification<T> 인터페이스, Criteria API를 래핑하는 구조, Specification 조합(and, or) 원리 |
| 07. Auditing 동작 원리 | @CreatedDate / @LastModifiedDate가 AuditingEntityListener와 AuditorAware를 통해 주입되는 과정, @EnableJpaAuditing 설정 체인 |
| 08. Custom Repository 구현 패턴 | Impl 네이밍 컨벤션의 동작 원리, JpaRepositoryImplementation 위임 구조, EntityManager 직접 사용이 필요한 경우와 설계 트레이드오프 |
핵심 질문:
@Transactional(propagation=REQUIRES_NEW)는 내부적으로 무슨 일을 하는가?
PlatformTransactionManager부터 Propagation 7가지, Rollback 규칙까지 (8개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. PlatformTransactionManager 구조와 구현체 | PlatformTransactionManager 인터페이스 설계, JpaTransactionManager vs DataSourceTransactionManager 구현 차이, TransactionStatus 객체 역할 |
| 02. @Transactional 프록시 생성 메커니즘 | TransactionInterceptor가 AOP Advice로 등록되는 과정, TransactionAttributeSource의 어노테이션 파싱, 트랜잭션 시작/커밋/롤백 흐름 |
| 03. Propagation 7가지 완전 분석 | REQUIRED / REQUIRES_NEW / NESTED / SUPPORTS / NOT_SUPPORTED / MANDATORY / NEVER 각각의 분기 조건과 내부 동작, 물리 트랜잭션 vs 논리 트랜잭션 구분 |
| 04. Isolation Level과 Database Lock | READ_UNCOMMITTED ~ SERIALIZABLE 4단계의 JDBC 설정 경로, Phantom Read / Non-Repeatable Read 재현 코드, DB별 기본 Isolation Level과 Lock 전략 |
| 05. private 메서드에 @Transactional이 안 되는 이유 | CGLIB 오버라이딩 불가 원리, Self-Invocation 함정 (this.method() 호출 시 프록시 우회), AopContext.currentProxy() 우회 방법과 트레이드오프 |
| 06. readOnly=true의 실제 효과 | Hibernate FlushMode.MANUAL 설정, 스냅샷 비교 생략 비용 절감, JDBC Connection.setReadOnly() 힌트, MySQL/PostgreSQL 리플리카 라우팅 연계 |
| 07. Rollback 규칙 — checked vs unchecked | Spring 기본 롤백 정책 (RuntimeException 기준), rollbackFor / noRollbackFor 설정 방법, checked exception으로 롤백하지 않아 데이터가 오염되는 실제 사례 |
| 08. TransactionSynchronization 활용 | TransactionSynchronizationManager의 ThreadLocal 기반 자원 관리, afterCommit() / afterCompletion() 훅 활용 패턴, @TransactionalEventListener와의 연결 |
핵심 질문: Hibernate는 어떻게 변경을 감지하고, 언제 쿼리를 날리는가?
Persistence Context부터 N+1 완전 해결, Batch Insert 최적화까지 (7개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. EntityManager vs Hibernate Session | JPA 표준 EntityManager와 Hibernate Session의 관계, EntityManager.unwrap(Session.class) 필요한 상황, Spring의 SharedEntityManagerCreator 프록시 구조 |
| 02. Persistence Context — 1차 캐시 동작 원리 | PersistenceContext 내부 EntityKey → EntityEntry 맵 구조, 동일 트랜잭션 내 동일 ID 조회 시 쿼리가 발생하지 않는 이유, Detached / Managed / Removed 상태 전환 |
| 03. Dirty Checking 메커니즘 | ActionQueue와 EntityEntry.loadedState 스냅샷 비교 방식, flush() 시점에 변경 감지가 일어나는 내부 코드, FlushMode 별 동작 차이 (AUTO vs COMMIT) |
| 04. N+1 문제 완전 해결 | N+1 발생 원리 (Lazy Loading 프록시 초기화 시점), @EntityGraph / Fetch Join / @BatchSize / FetchMode.SUBSELECT / DTO Projection 5가지 해결 전략별 실제 SQL 쿼리 수 비교 |
| 05. Lazy Loading 프록시 생성 과정 | Hibernate ByteBuddyProxyFactory가 엔티티 서브클래스를 생성하는 방식, LazyInitializationException 발생 조건, OSIV(Open Session In View) 패턴의 트레이드오프 |
| 06. Cascade 타입과 Orphan Removal | CascadeType 6가지의 내부 동작 원리, orphanRemoval=true와 CascadeType.REMOVE의 차이, 무분별한 ALL 사용의 위험성과 연관관계 설계 기준 |
| 07. Batch Insert/Update 최적화 | hibernate.jdbc.batch_size 설정만으로 Batch가 안 되는 이유 (IDENTITY 전략 한계), SEQUENCE 전략과 allocationSize 조합, saveAll() 호출 시 실제 발생하는 SQL 수 실측 |
핵심 질문: 같은 결과를 내는 쿼리인데, 왜 어떤 방법은 SQL이 1개고 어떤 방법은 N+1개인가?
JPQL/QueryDSL 비교부터 Pagination, Second-Level Cache까지 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. JPQL vs Criteria API vs QueryDSL 비교 | 세 방식의 컴파일 타임 안전성 / 동적 쿼리 지원 / 성능 비교, 각각이 내부에서 CriteriaQuery와 NativeQuery로 변환되는 경로 |
| 02. Fetch Join vs @EntityGraph 선택 기준 | Fetch Join과 @EntityGraph가 생성하는 SQL 비교, MultipleBagFetchException 발생 조건과 회피 전략, 컬렉션 2개 이상 조인 시 선택 기준 |
| 03. Pagination 최적화 전략 | Pageable을 사용한 LIMIT/OFFSET 방식의 성능 함정 (대용량 오프셋), Cursor 기반 Pagination, CountQuery 분리 최적화, @QueryHints로 페이지 카운트 캐싱 |
| 04. @BatchSize vs default_batch_fetch_size | @BatchSize(엔티티 레벨)와 hibernate.default_batch_fetch_size(전역 설정)의 차이, IN 절 파라미터 수가 성능에 미치는 영향, 적정 값 설정 기준 |
| 05. Query Plan Cache 동작 | Hibernate가 JPQL을 파싱해 QueryPlan으로 캐싱하는 구조, hibernate.query.plan_cache_max_size 튜닝, 파라미터 바인딩 방식(? vs :name)이 캐시 히트율에 미치는 영향 |
| 06. Second-Level Cache — Ehcache / Redis | @Cache 어노테이션이 Hibernate Region Factory를 통해 Ehcache/Redis에 저장되는 구조, READ_ONLY / READ_WRITE / NONSTRICT_READ_WRITE 전략 차이, 캐시 무효화 시점 |
핵심 질문:
JdbcTemplate은 어떻게 반복되는 JDBC 보일러플레이트를 제거하는가?
JdbcTemplate 내부 구조부터 Batch 처리까지 (5개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. JdbcTemplate 내부 구조 | Template Method 패턴으로 Connection 획득 / PreparedStatement 생성 / 예외 변환 / Connection 반환이 추상화되는 방식, DataSourceUtils를 통한 트랜잭션 컨텍스트 연계 |
| 02. RowMapper vs ResultSetExtractor | RowMapper(행 단위 변환)와 ResultSetExtractor(전체 ResultSet 제어)의 사용 시점, BeanPropertyRowMapper의 리플렉션 비용과 커스텀 RowMapper와의 성능 비교 |
| 03. NamedParameterJdbcTemplate 활용 | :name 바인딩이 내부적으로 ? 치환되는 파싱 과정, MapSqlParameterSource vs BeanPropertySqlParameterSource 차이, SQL Injection 방지 원리 |
| 04. SimpleJdbcInsert로 단순 삽입 | SimpleJdbcInsert가 DB 메타데이터를 조회해 컬럼 목록을 자동 감지하는 방식, usingGeneratedKeyColumns()로 자동 생성 키를 받는 과정 |
| 05. Batch 처리 최적화 | batchUpdate()가 PreparedStatement.addBatch() / executeBatch()를 감싸는 구조, Chunk 단위 처리 패턴, JPA saveAll()과의 실측 처리 속도 비교 |
핵심 질문: 적정 Pool Size는 어떻게 결정하며, Connection Leak은 어디서 발생하는가?
HikariCP 내부 구조부터 Leak 탐지, Statement 캐싱까지 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. HikariCP 설정과 최적화 | HikariPool 내부 ConcurrentBag 자료구조, 주요 설정값(maximumPoolSize, minimumIdle, connectionTimeout) 의미와 상호작용, Spring Boot 자동 설정 경로 |
| 02. Connection Leak 탐지와 디버깅 | leakDetectionThreshold 설정 시 ProxyLeakTask가 스택 트레이스를 캡처하는 방식, 트랜잭션 밖에서 EntityManager를 직접 사용할 때 발생하는 Leak 패턴 |
| 03. Pool Size 튜닝 공식 | Connections = cores × 2 + effective_spindle_count 공식의 이론적 배경, I/O 바운드 vs CPU 바운드 서비스에서의 적정값 차이, 과도한 Pool Size가 오히려 성능을 낮추는 이유 |
| 04. Connection Health Check 전략 | connectionTestQuery vs connectionInitSql vs JDBC4 isValid() 방식 비교, keepaliveTime으로 유휴 Connection 유효성 유지, DB 재시작 후 Pool 복구 동작 |
| 05. Statement Caching 효과 | HikariCP cachePrepStmts 설정이 DB 드라이버 레벨 캐싱과 연동되는 방식, MySQL prepStmtCacheSize와 prepStmtCacheSqlLimit 튜닝 포인트, 캐싱 전후 파싱 비용 실측 |
| 06. Connection Timeout vs Idle Timeout | connectionTimeout(연결 대기) / idleTimeout(유휴 제거) / maxLifetime(최대 수명) 세 타임아웃의 역할 차이, DB 방화벽 강제 종료 시나리오에서 maxLifetime 설정의 중요성 |
핵심 질문:
@DataJpaTest는 무엇을 로딩하고, 테스트의@Transactional은 왜 함정인가?
슬라이스 테스트부터 Testcontainers, @Sql 관리까지 (5개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. @DataJpaTest 범위와 제약 | @DataJpaTest가 로딩하는 슬라이스 컨텍스트 범위, 기본 H2 인메모리 DB 설정 경로, @Service 빈이 로딩되지 않는 이유와 @Import로 추가하는 방법 |
| 02. @Transactional in Test의 함정 | 테스트 메서드의 @Transactional 자동 롤백이 실제 트랜잭션 동작을 은폐하는 방식, REQUIRES_NEW 동작이 테스트에서 다르게 보이는 이유, 통합 테스트에서 @Rollback(false) 사용 기준 |
| 03. Testcontainers vs H2 선택 기준 | H2 호환 모드의 한계(JSON 타입, DB 고유 함수), Testcontainers @DynamicPropertySource로 실제 DB를 띄우는 방식, CI 환경에서의 컨테이너 재사용 전략 |
| 04. Repository 테스트 전략 | Query Method 단위 테스트 / 복잡한 JPQL 통합 테스트 / Custom Repository 격리 테스트 전략 구분, TestEntityManager를 활용한 fixture 데이터 준비 패턴 |
| 05. @Sql 스크립트 관리 | @Sql의 executionPhase(BEFORE_TEST_METHOD / AFTER_TEST_METHOD) 동작 순서, SQL 스크립트 격리 전략, @SqlConfig로 트랜잭션 처리 방식 제어 |
🟢 "@Transactional이 뭔지 정확히 알고 싶다" — 면접 준비 / 실무 의문 해소 (2주)
Week 1 — Repository와 트랜잭션의 작동 방식
Ch1-01 Repository 프록시 생성 과정
Ch2-01 PlatformTransactionManager 구조
Ch2-02 @Transactional 프록시 생성 메커니즘
Ch2-05 private 메서드에 @Transactional이 안 되는 이유
Week 2 — 실무 혼동 포인트와 성능 이슈
Ch2-03 Propagation 7가지 완전 분석 (면접 단골)
Ch2-06 readOnly=true의 실제 효과
Ch2-07 Rollback 규칙 — checked vs unchecked
Ch3-04 N+1 문제 완전 해결
🔵 JPA/Hibernate 내부를 원리로 이해하고 싶은 개발자 (6주)
Week 1 Chapter 1 전체 — Spring Data JPA 동적 프록시 Repository 생성
Week 2 Chapter 2 전체 — 트랜잭션 관리 내부 구조
Week 3 Chapter 3 전체 — Hibernate Persistence Context / Dirty Checking / N+1
Week 4 Chapter 4 전체 — 쿼리 성능 튜닝 (QueryDSL, Pagination, 2차 캐시)
Week 5 Chapter 5 전체 + Chapter 6 전체 — JDBC Template + Connection Pool
Week 6 Chapter 7 전체 — 데이터 레이어 테스트 전략
🔴 성능 최적화가 목표인 개발자 (집중 코스)
핵심 경로 (쿼리 수와 응답 시간 직접 개선)
Step 1 Ch3-04 N+1 문제 완전 해결 → 즉각적인 쿼리 수 감소
Step 2 Ch4-02 Fetch Join vs @EntityGraph 선택 기준
Step 3 Ch4-03 Pagination 최적화 (OFFSET 함정 → Cursor 전환)
Step 4 Ch3-07 Batch Insert/Update → 대량 저장 속도 개선
Step 5 Ch6-03 Pool Size 튜닝 공식 → 커넥션 대기 시간 제거
Step 6 Ch4-06 Second-Level Cache → 반복 조회 비용 제거
Step 7 Ch2-06 readOnly=true → 스냅샷 비용 절감
각 문서의 "⚡ 성능 임팩트" 섹션에서 Before/After 쿼리 수와 실행 시간을 확인하세요.
모든 문서는 동일한 구조로 작성됩니다.
| 섹션 | 설명 |
|---|---|
| 🎯 핵심 질문 | 이 문서를 읽고 나면 답할 수 있는 질문 |
| 🔍 왜 이게 존재하는가 | 문제 상황과 설계 배경 |
| 😱 흔한 오해 또는 잘못된 사용 | Before — 많은 개발자가 틀리는 방식 |
| ✨ 올바른 이해와 사용 | After — 원리를 알고 난 후의 올바른 접근 |
| 🔬 내부 동작 원리 | Spring Data / Hibernate 소스코드 직접 추적 + 다이어그램 |
| 💻 실험으로 확인하기 | 직접 실행 가능한 코드 + hibernate.show_sql 출력 예시 |
| ⚡ 성능 임팩트 | Before/After 쿼리 수, 실행 시간 비교 |
| 🤔 트레이드오프 | 이 설계의 장단점, 언제 다른 방법을 택할 것인가 |
| 📌 핵심 정리 | 한 화면 요약 |
| 🤔 생각해볼 문제 | 개념을 더 깊이 이해하기 위한 질문 + 해설 |
- Spring Data JPA Reference Documentation
- Hibernate ORM User Guide
- Spring Framework Reference — Data Access
- High-Performance Java Persistence — Vlad Mihalcea
- HikariCP GitHub
- Baeldung Spring Data JPA Guides
⭐️ 도움이 되셨다면 Star를 눌러주세요!
Made with ❤️ by Dev Book Lab
"Repository를 만드는 것과, Repository가 어떻게 작동하는지 아는 것은 다르다"