글
지금까지 작성한 글들을 모아봤습니다.
-
[JPA + Spring Mastery 09] 어디서 무엇을 쓸까 — JPA / JPQL / Native SQL 결정 트리, 측정값 4종으로 갈리는 컷
8 편을 거치며 PersistenceContext / Dirty Checking / Optimistic Lock / N+1 / IDENTITY / self-invocation / Saga·Outbox 를 측정값으로 풀었습니다. 이 마무리편은 그 모든 evidence 를 한 결정 트리 위에 올려놓습니다 — 도메인 단위인지 데이터 단위인지의 첫 갈림길로 시작해, JPA Dirty Checking 이 빛나는 곳 5 가지와 적자인 곳 5 가지, JPQL 의 중간 자리, Native / JdbcTemplate / MyBatis 의 자리를 4 측정값 (Dirty Checking 132× / saveAll IDENTITY ~1000× / OFFSET 1M 570× / bulk JPQL vs Dirty Checking 84×) 으로 컷하고, Eric Evans Aggregate (DDD 2003), Fowler PoEAA Identity Map / Unit of Work (2002), Greg Young CQRS Documents (2010), Vlad Mihalcea High-Performance Java Persistence (2016) 의 학술 anchor 와 카카오페이 readOnly QPS 58% 회고로 묶었습니다. 면접에서 'JPA 어떻게 다뤄봤어요?' 에 URL 한 줄로 답하기 위한 entry point 입니다.
-
@Transactional(readOnly = true) 한 줄이 응답시간을 132배 줄인 이야기 — JPA Dirty Checking 의 정체 해부
Updated:JPA dirty checking 비용을 1만 row UPDATE 6 시나리오로 분해. 비교의 *기준* 을 분리해서 읽어야 한다 — S1(일반) vs S2(readOnly SELECT) ≈ 132× 는 *readOnly 가 빠진 메서드가 부담하는 비용*, S4(@DynamicUpdate) vs S6(raw JDBC) ≈ 68× 는 *@DynamicUpdate 만으로는 dirty checking dominant 비용을 못 줄인다* 는 의미, S4 vs S5(bulk JPQL) ≈ 50× 는 *모델 자체를 dirty checking 에서 bulk 로 바꿨을 때* 효과, S5 vs S6 ≈ 1.32× 는 JPA 추상화 오버헤드 30% 수준. @DynamicUpdate 는 변경 컬럼 조합마다 SQL string 이 달라져 batch_update 효과를 약화시킬 수 있고 Query Plan Cache 압박을 일으킬 수 있어 *국지적 도구*. loadedState / FlushMode MANUAL / SPR-16956 / bytecode enhancement 까지 Hibernate 6 내부 구조와 함께, 단정 표현은 톤다운한 결론으로 정리한 v2 [실측 — Java/Spring Stage 2, EXP-13].
-
Aggregate boundary 를 *권한* 으로 강제 — 한 schema 에 5 user 분리 + cross-domain write 차단 7/7 실측
Distributed Monolith 의 가장 흔한 함정 — *모든 도메인이 같은 user 로 같은 schema 에 접근* 하니 cross-domain write 가 코드 리뷰에서만 잡힘. W4 P5 에서 같은 schema 에 5 user (be_owner / be_billing / be_persona / be_rule / be_workflow) 분리 + GRANT 로 cross-domain WRITE 를 *런타임* 에 ERROR 1142 로 차단. 7/7 검증 시나리오 모두 의도대로 동작 — 잘못된 코드가 *DB 가 직접* 잡아주는 첫 단계. 이 글은 "왜 같은 schema 안에서 user 분리가 의미 있는가" 의 *진화 전략* (W3 논리적 namespace → W4 user 분리 → W6 별도 DB 인스턴스) 의 두 번째 단계 측정 기록입니다.
-
saveAll() 이 1만 INSERT 가 되는 이유 — IDENTITY + Hibernate batch 비활성화의 구조적 함정
hibernate.jdbc.batch_size=50 으로 설정해도 1만 row 의 saveAll() 이 1만 INSERT 로 발사되는 이유. GenerationType.IDENTITY 는 매 INSERT 마다 LAST_INSERT_ID() 가 즉시 필요해서 Hibernate 가 batch 를 *구조적으로 비활성화* — Statement.RETURN_GENERATED_KEYS 가 batch 와 호환 안 됨. TABLE 전략 시뮬레이션 (애플리케이션이 ID 미리 발급) 은 batch 묶임 → 200 SQL. raw JDBC batchUpdate + rewriteBatchedStatements=true 는 multi-value INSERT 로 약 10 SQL — 가장 빠름. DZone 의 IDENTITY → SEQUENCE 100배 글은 PostgreSQL 기준 — MySQL 은 SEQUENCE 미지원 (TABLE 로 에뮬레이션). MySQL 환경의 진짜 답은 UUID / TableGenerator pooled-lo / Snowflake / raw JDBC batch 중 선택입니다.