2026-05-27
2026-05-27 · 오늘 한 일
오늘의 메인은 JPA다. 부트캠프 "MyBatis to JPA" 강의 노트를 위키에 인제스트하고, 거기서 막힌 두 지점(영속성 컨텍스트, LAZY 프록시 / merge)을 질문으로 파고들었다. 결과: 위키 개념 8건 신설, 흐름 1건·소스 1건 신설, 기존 페이지 6곳 보강. 한 줄 요약 — "MyBatis에서 사라진
UPDATESQL이 어디로 갔나"를 끝까지 추적한 날.
1. JPA 강의 노트 인제스트 — "SQL 통제권을 한 칸 더 넘긴다"
왜 했나
MyBatis 실습을 끝내고 JPA로 넘어가는 단계다. 곧 Spring 프로젝트에 JPA를 실제로 적용할 거라, 강의 노트(raw/lectures/JPA.md)를 단순 요약이 아니라 MyBatis와의 비교 흐름 + 개념 트리로 정착시키고 싶었다.
핵심 깨달음
JPA = 명세, Hibernate = 구현체, ORM = 개념. 세 단어가 층위가 다른데 늘 뒤섞여 있었다. 내가 Spring Boot로 JPA를 쓰면 실제로 SQL을 만드는 손은 [[hibernate]]다.
JPA가 존재하는 진짜 이유 = 패러다임 불일치. 객체는 상속·참조·그래프 탐색으로 사고하는데 테이블은 FK·JOIN으로 사고한다. [[mybatis]]/[[jdbc]]는 이 간극을 내가 손으로 메웠고, JPA는 그걸 프레임워크가 맡는다.
추상화 사다리가 보였다: 손 JDBC → MyBatis → JPA → Spring Data JPA. 위로 갈수록 SQL 통제권을 넘기고 코드가 준다. [[mybatis]]를 배운 게 낭비가 아니다 — JPQL 한계를 메우는 복잡 쿼리 안전판으로 공존(CQRS)한다.
위키 변경
신설(개념 7): [[jpa]] · [[hibernate]] · [[spring-data-jpa]] · [[entity-manager]] · [[jpa-entity-mapping]] · [[jpa-association]] · [[jpql]]
신설(흐름 1): [[mybatis-to-jpa]] — 1:1 대응표(SQL작성·매핑·관계·작업단위·빈·트랜잭션·UPDATE방식) + 전환 6절차 + 공존 전략.
신설(소스 1): [[jpa-lecture]]
보강 6곳: [[orm]](자바 ORM=JPA/Hibernate), [[mybatis]](다음 단계·공존), [[n-plus-1-problem]], [transaction], [[dao-pattern]], [[spring-framework]](영속성 사다리).
2. 영속성 컨텍스트가 뭐야? — Query
왜 물었나
인제스트 때 [[entity-manager]] 페이지에 "강의가 이 용어를 안 다뤄서 표준 동작으로 보강함"이라고 ⚠️ 플래그를 남겨뒀다. JPA의 핵심이면서 MyBatis와 가장 다른 지점이라 그냥 넘길 수 없었다.
핵심 깨달음
영속성 컨텍스트 = 엔티티를 올려두는 "작업대"(1차 캐시). 트랜잭션 하나 = 작업대 하나. persist/find한 엔티티는 DB로 바로 안 가고 작업대에 올라갔다가 커밋 때 반영된다.
내가 [[mybatis]]에서 하던 userMapper.updateUser(user) — JPA에선 update 호출이 없다. 작업대가 엔티티 올릴 때 스냅샷을 찍어두고, 커밋 때 비교해서 바뀐 필드만 UPDATE한다(변경 감지). 이게 "merge 안 해도 바뀌는" 이유의 정체.
flush ≠ 커밋. 정확한 순서는 커밋 → flush(SQL 전송) → 변경 감지 → DB 반영. 작업대 수명이 트랜잭션과 묶여 있다.
작업대 기준 생명주기 4단계: 비영속 → 영속 → 준영속 → 삭제. 영속 상태에서만 변경 감지가 돈다.
위키 변경
신설(개념 1): [[persistence-context]] — 작업대 비유, MyBatis 대비, 5대 기능표, 생명주기, flush vs 커밋.
보강 3곳: [[entity-manager]](⚠️ 플래그 해소), [[jpa]], [[transaction]](커밋→flush 순서로 정밀화).
3. LAZY 연관 객체와 merge 로직 — Query
왜 물었나
[[jpa-association]]에서 본 FetchType.LAZY와, 영속성 컨텍스트에서 예고됐던 merge가 "실제로 어떤 객체이고 어떤 로직인지"가 흐릿했다. 프로젝트에서 가장 자주 부딪힐 지점이라고 들었다.
핵심 깨달음
LAZY 연관 객체 = 프록시(가짜 대역). getOrder()는 ID만 든 빈 껍데기를 주고, 진짜 필드를 건드리는 순간에야 SELECT를 날려 속을 채운다(프록시 초기화). "안 쓸 수도 있는 걸 미리 안 가져온다"가 목적.
LazyInitializationException = 작업대(트랜잭션)가 닫힌 뒤 프록시를 처음 건드릴 때 터지는 예외. → 트랜잭션 경계가 곧 프록시를 초기화할 수 있는 구간이다.
merge는 직관과 다르다. ①ID로 영속 엔티티를 찾아 ②필드를 통째 복사하고 ③그 영속 엔티티를 반환한다. 인자로 넣은 객체는 여전히 준영속 → 반환값을 안 쓰면 변경이 안 먹는 버그. 게다가 null 필드까지 덮어쓴다. 그래서 일상 수정은 find + 변경 감지가 안전하고, merge는 준영속 재등록 전용이다.
위키 변경
보강 2곳(새 페이지 대신 기존 가지에 추가):
[[jpa-association]] — "LAZY 연관 객체의 정체 = 프록시" + LazyInitializationException 섹션.
[[persistence-context]] — "merge의 로직" 섹션(4단계 + 함정 2개).
오늘의 위키 변동 요약 (JPA 줄기)
항목 | 변화 |
|---|---|
개념 페이지 | 84 → 92 (+8) |
흐름 페이지 | 10 → 11 (+1) |
소스 페이지 | 21 → 22 (+1) |
통합 원본 | 21 → 22 (+1) |
기존 페이지 보강 | 6곳 |
그 외에 이 날은 [[connection-pool]]·[[hikaricp]](커넥션 풀), [[pi]](에이전트 하네스) 인제스트, [[proxy]] vs AOP 프록시 개념 정리, 위키 lint도 했다. 학습의 메인 줄기는 JPA였다.
오늘을 관통하는 메타 인사이트
세 작업이 결국 하나의 질문으로 모인다 — "MyBatis에서 직접 쓰던 UPDATE SQL이 JPA에선 어디로 갔나?"
JPA 강의 — SQL 통제권을 프레임워크에 넘겼다(추상화 사다리 한 칸 위).
영속성 컨텍스트 — 그 SQL은 "작업대 + 변경 감지"로 사라졌다. 객체만 바꾸면 커밋 때 생성된다.
LAZY / merge — 사라진 대가로 새 함정이 생겼다(프록시 초기화 예외, merge 덮어쓰기).
→ 5/26 TIL에서 잡았던 "추상화는 무언가를 가린 만큼 다른 자리에 새 책임을 만든다"가 또 확인됐다. 원리를 이해한다는 건 사라진 게 어디로 갔는지 추적하는 일이다. JPA에서 그게 "작업대"였다.
다음 단계 후보
fetch join / @EntityGraph — LazyInitializationException과 [[n-plus-1-problem]]을 한 번에 푸는 실무 해법. 아직 위키에 독립 페이지 없음.
OSIV(Open Session In View) — 준영속을 안 만들려고 트랜잭션 밖까지 작업대를 여는 설정. 트레이드오프 정리 필요.
프로젝트에 JPA를 실제로 붙일 때 [[mybatis-to-jpa]]의 6절차를 체크리스트로 사용.
댓글
댓글이 없습니다.
