85 lines
8.5 KiB
Markdown
85 lines
8.5 KiB
Markdown
---
|
|
title: company-tech-blog / Spring read-only transaction Hibernate optimization — Vlad Mihalcea
|
|
source_type: company-tech-blog
|
|
url: https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/
|
|
archive_url:
|
|
related_branches: [feature-application-query-bypass-contract]
|
|
related_projects: [ca-skeleton]
|
|
tags: [spring, hibernate, read-only, transaction, dirty-check, flush-mode, performance, memory, ca-skeleton]
|
|
created: 2026-06-04
|
|
last_reviewed: 2026-06-04
|
|
---
|
|
|
|
# Spring read-only transaction Hibernate optimization — Vlad Mihalcea
|
|
|
|
> Layer: `raw/company-tech-blogs/` — Vlad Mihalcea 의 기술 블로그 (vladmihalcea.com) 포스트 "Spring read-only transaction Hibernate optimization" (2018-09-25) 발췌. Hibernate 작동 전문가인 저자가 Spring 5.1 에서 개선된 `@Transactional(readOnly=true)` 의 Hibernate 세션 최적화를 설명.
|
|
>
|
|
> **출처 신뢰도**: `engineering-blog` 등급 — Vlad Mihalcea 는 Hibernate core committer 이자 "High-Performance Java Persistence" 저자. 개인 블로그이나 Hibernate 공식 contributor 의 기술 분석. Spring 공식 문서가 아님. `company-case-study` 승급 불가 (사례 아닌 기술 분석).
|
|
|
|
## Parent / 활용 branch (필수)
|
|
|
|
| Branch | 이 자료가 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/branch-notes/feature-application-query-bypass-contract]] | D2 (transaction bypass — read-only transaction 을 쓰지 않을 때의 실제 cost) 의 기술적 근거 — `readOnly=true` 가 Hibernate session setDefaultReadOnly(true) 로 propagate 되어 loaded state (hydrated state) 를 discarded 하는 최적화의 실제 의미. 단순 single-entity SELECT 에는 dirty-check overhead 가 미미함을 시사. |
|
|
|
|
## 출처 / Source
|
|
|
|
- 원본 URL: https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/
|
|
- 아카이브 URL:
|
|
- 저자 / 조직: Vlad Mihalcea (Hibernate core committer, "High-Performance Java Persistence" 저자)
|
|
- 발행일: 2018-09-25
|
|
- 마지막 확인일: 2026-06-04
|
|
|
|
## 왜 저장했는지 / Why archived
|
|
|
|
ca-tmpl 의 transaction bypass 결정(D2)에서 "no-tx read 가 얼마나 위험한가" 의 반대 근거로 보관. `readOnly=true` 의 실제 최적화 내용이 **메모리 절약과 dirty-check skip** 이며, **단순 단일 SELECT** 에는 이 최적화의 이득이 미미함을 시사. 결과적으로 skeleton 의 no-tx read 허용 범위를 정당화하는 보조 근거.
|
|
|
|
## 핵심 인용 / Key quotes (verbatim)
|
|
|
|
> "Prior to Spring 5.1, when using Hibernate, the readOnly attribute of the @Transactional annotation was only setting the current Session flush mode to FlushType.MANUAL, therefore disabling the automatic dirty checking mechanism."
|
|
|
|
> "the readOnly attribute did not propagate to the underlying Hibernate Session, I decided to create the SPR-16956 issue and provided a Pull Request...which after being Jürgenized, it got integrated"
|
|
|
|
> "upon loading an entity, the loaded state is stored by the Hibernate Session unless the entity is loaded in read-only mode."
|
|
|
|
> "the main advantage of the Spring 5.1 read-only optimization for Hibernate is that we can save a lot of memory when loading read-only entities since the loaded state is discarded right away"
|
|
|
|
> "if the user tries to do a manual flush, entities that are virtually read-only won't be propagated"
|
|
|
|
## Claims Extracted / 추출된 주장
|
|
|
|
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
|
|---|---|---|---|---|---|
|
|
| VM-READTX-C1 | Spring 5.1 이전에는 `@Transactional(readOnly=true)` 가 Hibernate Session flush mode 를 `FlushType.MANUAL` 로만 설정하고, underlying Hibernate Session 에 propagate 되지 않았다 | "Prior to Spring 5.1...the readOnly attribute...was only setting the current Session flush mode to FlushType.MANUAL, therefore disabling the automatic dirty checking mechanism." | `engineering-blog` | Spring 5.1 미만 + Hibernate 사용 환경 | Spring 5.1+ 이후에도 flush mode 설정이 일어나지 않는다는 뜻은 아님 — 5.1+ 에서는 추가로 `setDefaultReadOnly(true)` 도 호출됨 |
|
|
| VM-READTX-C2 | Spring 5.1+ 에서는 `@Transactional(readOnly=true)` 가 underlying Hibernate Session 에 `setDefaultReadOnly(true)` 로 propagate 되어, 로드된 entity 의 **hydrated state (loaded state snapshot) 이 즉시 discard** 됨 | "the readOnly attribute did not propagate to the underlying Hibernate Session" (결함 진술) + "upon loading an entity, the loaded state is stored by the Hibernate Session unless the entity is loaded in read-only mode." | `engineering-blog` | Spring 5.1+ + Hibernate JPA provider 사용 환경 (HibernateJpaDialect 경유) | EclipseLink 등 다른 JPA provider 에도 동일 최적화가 적용된다는 보장 없음. Spring 5.1+ 에서도 HibernateJpaDialect 를 사용해야 적용됨 |
|
|
| VM-READTX-C3 | `@Transactional(readOnly=true)` 의 **주요 이득은 메모리 절약** — read-only entity 로드 시 loaded state 가 즉시 discarded 되어 persistence context 존속 기간 동안 보관되지 않음 | "the main advantage of the Spring 5.1 read-only optimization for Hibernate is that we can save a lot of memory when loading read-only entities since the loaded state is discarded right away" | `engineering-blog` | 많은 entity 를 로드하는 read-heavy operation | 단순 단일 entity 또는 단일 DTO projection SELECT 에 동일한 이득이 있다는 뜻은 아님 — 로드되는 entity 수가 많을수록 이득이 커짐 |
|
|
| VM-READTX-C4 | read-only entity 로 로드된 경우 manual flush 를 호출해도 해당 entity 는 **propagate 되지 않음** — dirty check 자체가 skip 됨 | "if the user tries to do a manual flush, entities that are virtually read-only won't be propagated" | `engineering-blog` | Hibernate Session 의 flush 가 read-only entity 에 미치는 영향 | read-only entity 를 변경하면 예외가 발생한다는 강제 보증은 본 인용에 없음 — 변경 자체는 가능하나 flush 시 반영 안 됨 |
|
|
|
|
## Usage Boundaries / 적용 경계
|
|
|
|
- 이 자료가 직접 증명하는 것:
|
|
- `VM-READTX-C1`: Spring 5.1 이전 `readOnly=true` 의 제한된 동작 (flush mode MANUAL 만)
|
|
- `VM-READTX-C2`: Spring 5.1+ 에서 HibernateJpaDialect 경유 시 `setDefaultReadOnly(true)` 전파
|
|
- `VM-READTX-C3`: 주요 이득 = **메모리 절약** (많은 entity 로드 시). query 속도 개선이 아님
|
|
- `VM-READTX-C4`: dirty check skip 으로 flush 시 read-only entity 는 DB 반영 안 됨
|
|
- 이 자료가 증명하지 않는 것:
|
|
- 단순 단일 entity SELECT 에서 `readOnly=true` 유무의 실제 성능 차이 — 본 포스트의 예시는 여러 entity 를 findAllByTitle 로 bulk 로드하는 시나리오
|
|
- `readOnly=true` 없이 실행(no-tx or REQUIRED write tx)하는 simple SELECT 가 응용 결과에 영향을 주는 케이스 (dirty entity 가 없으면 flush 로 인한 추가 DML 없음)
|
|
- OSIV(open-in-view) enabled 환경에서의 동작 차이
|
|
- DB connection 유지 시간의 차이 (transaction 경계 = connection 점유 기간 이지만 본 포스트는 이 cost 를 다루지 않음)
|
|
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
|
- ca-tmpl 에서 `TransactionPort.inRead` 없이 실행되는 repository read method 가 HikariCP autocommit=true 환경에서 개별 connection 을 점유하는지 측정
|
|
- Hibernate 6 (Spring Boot 3.x) 에서 `setDefaultReadOnly(true)` 가 실제로 loaded state 를 즉시 discard 하는지 통합 테스트 검증
|
|
|
|
## 메모 / Notes
|
|
|
|
- **중요 해석**: VM-READTX-C3 가 명시하듯 `readOnly=true` 의 주요 이득은 메모리 절약이지 query latency 개선이 아님. 단순 ID-by-PK lookup 같은 single-entity read 에서는 hydrated state 가 1개이므로 메모리 이득이 미미. 따라서 ca-tmpl 의 no-tx bypass 가 허용되는 "단순 읽기" 정의에는 VM-READTX-C3 의 scope — 많은 entity 를 bulk 로드하는 연산은 readOnly=true 가 의미 있음.
|
|
- 본 포스트는 Hibernate core committer 의 기술 분석이므로 engineering-blog 등급이나 Hibernate 내부 동작 설명의 신뢰도는 높음. 단 production case study 가 아니므로 `company-case-study` 로 승급 불가.
|
|
|
|
## Related / 관련
|
|
|
|
- [[raw/official-docs/spring-tx-management-reference]] — readOnly 속성의 공식 정의 (SPRING-TX-MGR-C6)
|
|
- [[raw/official-docs/spring-data-jpa-transactionality-spring-official]] — Spring Data CrudRepository 의 readOnly 기본 동작
|
|
- [[raw/official-docs/at-transactional-spring-official]] — `@Transactional` 전체 동작
|
|
- [[raw/branch-notes/feature-application-port-usecase-contract]] — TransactionPort.inRead 선행 계약
|