Adds a JUnit test suite (GetVsGetReferenceTest, MergeRefreshTest, OptimisticLockTest, IdentityBatchTest, SequenceBatchTest, AllocationSizeSweepTest, BatchSizeSweepTest) so every surprising behavior described in the three companion posts has a reproducible test, alongside the original CommandLineRunner scenarios. Rewrites all three doc chapters and the README around the new experiments: the get()/getReference() same-session matrix, the merge()/refresh() experiments (including exactly when OptimisticLockException surfaces and a corrected LAZY-plus- cascade merge() result), and two new sweeps (allocationSize, batch_size) for batch inserts.
hibernate-demo
Companion repository for three ankurm.com posts on Hibernate 7's persistence-context APIs:
get() vs getReference(), merge() vs refresh(), and batch inserts. Every claim in those
posts that comes from this repo traces to a named JUnit test here and a captured transcript in
docs/output/ — nothing is asserted that wasn't actually run. Every surprising behavior
described in the three posts has a reproducible test backing it; the table below maps each one.
Versions
| Component | Version |
|---|---|
| Hibernate ORM | 7.4.1.Final (GA 2026-06-09) |
| Spring Boot | 4.1.0 (GA 2026-06-10) |
| Java | 25 (LTS) |
| H2 | in-memory, managed by Spring Boot |
See docs/00-versions.md for how these were verified and a trap worth
knowing about if you bump the Spring Boot version.
Quickstart
git clone https://ankurm.com/git.app/asmhatre/hibernate-demo.git
cd hibernate-demo
./mvnw test
Requires JDK 25 and a network connection the first time (Maven needs to fetch plugins online
before -o offline mode works for subsequent runs).
Every surprising claim, mapped to a test
Each row is one ./mvnw -Dtest=ClassName test away from reproducing itself. This is the
reproduction path referenced throughout all three posts and all three doc chapters below.
| Test class | Backs post | Proves |
|---|---|---|
GetVsGetReferenceTest |
4859 (get vs getReference) | The 4-calls-4-outcomes table, the same-session matrix, proxy identity vs equals() |
MergeRefreshTest |
4860 (merge vs refresh) | merge() returns the pre-existing managed instance; merge() initializes a cascaded LAZY collection; refresh() silently discards an unflushed edit |
OptimisticLockTest |
4860 (merge vs refresh) | Exactly when OptimisticLockException surfaces relative to merge()/commit |
IdentityBatchTest |
4861 (batch inserts) | GenerationType.IDENTITY disables batching entirely |
SequenceBatchTest |
4861 (batch inserts) | GenerationType.SEQUENCE allows real batching |
AllocationSizeSweepTest |
4861 (batch inserts) | allocationSize sweep (1, 10, 25, 50) at fixed batch_size=25 |
BatchSizeSweepTest |
4861 (batch inserts) | batch_size sweep (1, 10, 25, 50) at fixed allocationSize=50 |
./mvnw -Dtest=GetVsGetReferenceTest test
./mvnw -Dtest=MergeRefreshTest,OptimisticLockTest test
./mvnw -Dtest=IdentityBatchTest,SequenceBatchTest test
./mvnw -Dtest=AllocationSizeSweepTest test
./mvnw -Dtest=BatchSizeSweepTest test
./mvnw test # the whole suite, 24 tests, 0 failures as of the last commit
CommandLineRunner scenarios
The original narrative scenarios are still here, unchanged, for anyone who wants to read a straight-line script instead of a test class:
| Profile | Runs | Chapter |
|---|---|---|
getvsload |
session.get() vs session.getReference(), proxies, LazyInitializationException |
docs/01-get-vs-load.md |
mergerefresh |
merge() vs refresh() against a @Version-ed entity |
docs/02-merge-vs-refresh.md |
insert-identity |
Batch insert attempt with GenerationType.IDENTITY |
docs/03-inserting-objects.md |
insert-sequence |
The same insert, with GenerationType.SEQUENCE |
docs/03-inserting-objects.md |
./scripts/run.sh getvsload
./scripts/run.sh mergerefresh
./scripts/run.sh insert-identity
./scripts/run.sh insert-sequence
Regenerating captured output
./scripts/run-all.sh
Regenerates the CommandLineRunner-scenario files in docs/output/. scripts/clean_output.py
strips JVM noise and a harmless duplicate SQL echo line so the committed transcripts stay
readable — nothing else is edited by hand. The test-suite transcripts in docs/output/ (the
session matrix, the sweeps) were captured the same way, from ./mvnw -Dtest=... test piped
through the same script.
Documentation index
| Chapter | Covers |
|---|---|
| 00 — Versions | Verified version pins, and the Spring Boot patch that silently changes which Hibernate patch you get |
| 01 — get() vs getReference() | The 4-calls-4-outcomes table, the same-session matrix, proxy identity vs equals() |
| 02 — merge() vs refresh() | Three named experiments: which method fails loudly vs silently, exactly when the optimistic-lock check fires, and what a cascaded LAZY collection does under merge() |
| 03 — Hibernate 7 batch inserts | IDENTITY vs SEQUENCE, the allocationSize and batch_size sweeps, Session vs StatelessSession, with hibernate.generate_statistics as evidence throughout |
Captured output index
| File | Source |
|---|---|
| docs/output/get-vs-load.txt | ./scripts/run.sh getvsload (CommandLineRunner) |
| docs/output/get-vs-getreference-tests.txt | ./mvnw -Dtest=GetVsGetReferenceTest test |
| docs/output/merge-vs-refresh.txt | ./scripts/run.sh mergerefresh (CommandLineRunner) |
| docs/output/merge-vs-refresh-tests.txt | ./mvnw -Dtest=MergeRefreshTest,OptimisticLockTest test |
| docs/output/insert-identity.txt | ./scripts/run.sh insert-identity (also matches IdentityBatchTest) |
| docs/output/insert-sequence.txt | ./scripts/run.sh insert-sequence (also matches SequenceBatchTest) |
| docs/output/allocation-and-batch-size-sweeps.txt | ./mvnw -Dtest=AllocationSizeSweepTest,BatchSizeSweepTest test |
Layout
hibernate-demo/
├── pom.xml
├── LICENSE
├── scripts/
│ ├── run.sh start one CommandLineRunner profile, run it, exit
│ ├── run-all.sh regenerate the CommandLineRunner docs/output/ files
│ └── clean_output.py strip JVM noise + a duplicate SQL echo line from a raw capture
├── src/main/java/com/ankurm/hibernatedemo/
│ ├── HibernateDemoApplication.java
│ ├── model/ Book, Note, WidgetIdentity, WidgetSequence,
│ │ WidgetAlloc1/10/25/50, WidgetBatchSweep1/10/25/50
│ └── scenario/ one CommandLineRunner per profile
├── src/test/java/com/ankurm/hibernatedemo/
│ ├── GetVsGetReferenceTest.java
│ ├── MergeRefreshTest.java, OptimisticLockTest.java
│ ├── IdentityBatchTest.java, SequenceBatchTest.java
│ └── AllocationSizeSweepTest.java, BatchSizeSweepTest.java
└── docs/
├── 00-versions.md .. 03-inserting-objects.md
└── output/*.txt captured, unedited console transcripts (both runner and test output)
License
MIT — see LICENSE.