Add Hibernate 7 batches 2-5: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping and named queries (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
# hibernate-demo
|
||||
|
||||
Companion repository for a batch of fifteen ankurm.com chapters on Hibernate 7: the
|
||||
persistence-context APIs (`get()` vs `getReference()`, `merge()` vs `refresh()`, batch inserts),
|
||||
mapping styles (annotations vs. XML, JPA persistence annotations, natural ids, immutable
|
||||
entities), and a set of deeper operational topics (stored procedures, in-memory test databases,
|
||||
mocking JNDI datasources, proxies and lazy initialization, association mappings, date/time
|
||||
mapping, and named queries). Every claim in those chapters 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.
|
||||
|
||||
## Versions
|
||||
|
||||
| Component | Version | Notes |
|
||||
|---|---|---|
|
||||
| Spring Boot | `4.1.1` | parent POM |
|
||||
| Hibernate ORM | `7.4.5.Final` | what Boot 4.1.1 manages — see [`docs/00-versions.md`](docs/00-versions.md) for why this is *not* the same patch Boot 4.1.0 resolves |
|
||||
| Spring Framework | `7.0.9` | pulled in transitively by Boot 4.1.1 |
|
||||
| Jakarta Persistence | `3.2.0` | `jakarta.persistence-api` |
|
||||
| Java | `25` (Temurin 25.0.4.1 LTS) | |
|
||||
| H2 | `2.4.240` | in-memory, `DB_CLOSE_DELAY=-1` |
|
||||
| HSQLDB | `2.7.3` | in-memory |
|
||||
| Apache Derby | `10.16.1.1` | in-memory; needs `hibernate-community-dialects` (see below) |
|
||||
| simple-jndi | `0.25.0` | `com.github.h-thurow:simple-jndi`, test scope — **not** the abandoned `simple-jndi:simple-jndi` groupId |
|
||||
| JUnit Jupiter | `6.0.3` | via `spring-boot-starter-test` |
|
||||
|
||||
**This repo was bumped this session from Boot `4.1.0` / Hibernate `7.4.1.Final` to Boot `4.1.1` /
|
||||
Hibernate `7.4.5.Final`.** If you see either of those older numbers anywhere outside
|
||||
`docs/00-versions.md`'s own explanation of the bump, that's stale and should be corrected — the
|
||||
live `pom.xml` and every chapter added or touched this session already reflect `4.1.1` /
|
||||
`7.4.5.Final`.
|
||||
|
||||
See [`docs/00-versions.md`](docs/00-versions.md) for how these were verified (against
|
||||
`maven-metadata.xml`, not Maven Central's search index) and the trap worth knowing about if you
|
||||
bump the Spring Boot version again: two different Boot 4.1.x patch releases can resolve two
|
||||
different Hibernate patch releases from the same "4.1" line.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
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). `mvn test` runs the full suite — **141
|
||||
tests, 0 failures** as of the last commit — and takes a couple of minutes, since it boots
|
||||
Hibernate against H2, HSQLDB and Derby repeatedly, spins up a real embedded Tomcat for the OSIV
|
||||
chapter, and runs a 5,500-iteration timing comparison for the named-query chapter.
|
||||
|
||||
Two configuration decisions in this repo are easy to trip over if you're extending it rather than
|
||||
just reading it:
|
||||
|
||||
- **`hibernate.cache.use_second_level_cache: false`** is pinned explicitly in
|
||||
[`application.yml`](src/main/resources/application.yml), on purpose. `hibernate-jcache` is on
|
||||
this project's classpath (chapter 06's natural-id L2 measurements need it), and Hibernate
|
||||
7.4.5 turns the second-level cache **on by itself**, for every context in the repo, the moment
|
||||
it finds a `RegionFactory` via the service loader — with nothing configured anywhere. Before
|
||||
this was pinned, that silently broke an unrelated test (see
|
||||
[chapter 09](docs/09-testing-in-memory-databases.md#hibernate-jcache-on-the-classpath-turns-on-l2-for-everyone-whether-you-asked-or-not)
|
||||
for the verbatim failure and the `JCacheOnClasspathAutoEnablesL2Test` that pins it down). If
|
||||
you add a JCache/Ehcache/Infinispan dependency to a Hibernate project for one narrow purpose,
|
||||
assume it just turned L2 caching on globally unless you pin this back down yourself.
|
||||
- **`hibernate-community-dialects` is a required runtime dependency**, not an optional add-on,
|
||||
because `org.hibernate.dialect.DerbyDialect` no longer exists in `hibernate-core` as of
|
||||
Hibernate 6.2+. Derby's dialect moved to a different package,
|
||||
`org.hibernate.community.dialect.DerbyDialect`, in this separate artifact — see
|
||||
[chapter 09](docs/09-testing-in-memory-databases.md#headline-correction-derby-has-no-dialect-in-hibernate-7-without-an-extra-dependency)
|
||||
for both failure messages this produces if the dependency or the dialect setting is missing.
|
||||
|
||||
## Three databases, and why
|
||||
|
||||
H2, HSQLDB and Derby are all exercised side by side (chapter 09 in particular boots all three
|
||||
against the identical entity mapping), specifically to find where their dialect emulation
|
||||
actually diverges rather than repeating folklore about it — the reserved-word survey, the
|
||||
CHAR-padding behavior, and the Derby dialect relocation above are all things that only showed up
|
||||
by running all three, not by reading about one of them. **Testcontainers is discussed but not
|
||||
runnable in the sandbox this repo was built in** — there is no Docker binary and no
|
||||
`/var/run/docker.sock` available, so nothing Testcontainers-based was attempted or claimed to
|
||||
have been run. The in-memory engines cover fast, unit-level ORM testing; a real production
|
||||
database via Testcontainers is a separate, slower integration stage this repo doesn't attempt to
|
||||
substitute for.
|
||||
|
||||
## Chapter index
|
||||
|
||||
| Chapter | Covers |
|
||||
|---|---|
|
||||
| [00 — Versions](docs/00-versions.md) | Verified version pins, and the Spring Boot patch that silently changes which Hibernate patch you get |
|
||||
| [01 — get() vs getReference()](docs/01-get-vs-load.md) | The 4-calls-4-outcomes table, the same-session matrix, proxy identity vs `equals()` |
|
||||
| [02 — merge() vs refresh()](docs/02-merge-vs-refresh.md) | 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](docs/03-inserting-objects.md) | `IDENTITY` vs `SEQUENCE`, the allocationSize and batch_size sweeps, `Session` vs `StatelessSession` |
|
||||
| [04 — Annotations vs. XML mappings](docs/04-annotations-vs-xml.md) | `hbm.xml` still works (WARN, not broken), `orm.xml` can define a whole entity, XML always wins on conflict, and the third XML dialect (`mapping.xml`) neither older article mentions |
|
||||
| [05 — JPA persistence annotations](docs/05-jpa-persistence-annotations.md) | `@Temporal`'s formal deprecation, the ORDINAL enum reorder failure reproduced, `@JdbcTypeCode(JSON)`'s hidden Jackson dependency, the surrogate-id `HashSet` trap, mixed access-type gotchas, and what's actually new in Jakarta Persistence 3.2 |
|
||||
| [06 — Natural IDs](docs/06-natural-ids.md) | `bySimpleNaturalId`, L1 vs L2 cache behavior measured with real query counts, the "L2 populates on INSERT, not first lookup" surprise, enforced natural-id immutability, and composite natural ids |
|
||||
| [07 — Immutable entities](docs/07-immutable-entities.md) | `@Immutable`'s actual mechanism (dirty-check exclusion, not a write guard), what it does and doesn't block, and a measured flush-cost comparison against `setReadOnly()` |
|
||||
| [08 — Stored procedures](docs/08-stored-procedures.md) | IN/OUT/INOUT against a real HSQLDB procedure, a genuine HSQLDB-driver result-set incompatibility, the parameter name-vs-position trap, and why procedure calls don't auto-flush |
|
||||
| [09 — Testing with in-memory databases](docs/09-testing-in-memory-databases.md) | Derby's relocated dialect, the `hibernate-jcache` classpath-pollution trap, the real (and not-Derby-exclusive) cross-database divergences, and schema/test isolation strategies |
|
||||
| [10 — Mocking JNDI datasources](docs/10-mocking-jndi-datasources.md) | Why `SimpleNamingContextBuilder` is gone, getting `simple-jndi` 0.25.0 actually working, the failure modes verbatim, and whether mock JNDI is still the right answer in 2026 |
|
||||
| [11 — Proxies and lazy initialization](docs/11-proxies-and-lazy-initialization.md) | What a `HibernateProxy` actually is, both `LazyInitializationException` message templates, `fetchgraph` vs `loadgraph` demonstrated, and what OSIV actually masks |
|
||||
| [12 — Association mappings](docs/12-association-mappings.md) | N+1 counted exactly, `MultipleBagFetchException` and its fixes, the cartesian-product trap, the `@OneToOne` lazy trap, and cascade/orphanRemoval's real (and not-so-real) failure modes |
|
||||
| [13 — Date and time mapping](docs/13-date-and-time-mapping.md) | Every basic temporal type round-tripped, the six `TimeZoneStorageType` constants measured under a JVM zone change, nanosecond rounding vs. truncation across databases, and `hibernate.jdbc.time_zone`'s raw-storage effect |
|
||||
| [14 — Named queries](docs/14-named-queries.md) | Startup validation's two different failure modes, Hibernate's extra `@NamedQuery` attributes, `orm.xml` named queries, and a measured (negative) answer to "are named queries faster?" |
|
||||
|
||||
## Every surprising claim from chapters 01–03, mapped to a test
|
||||
|
||||
Each row is one `./mvnw -Dtest=ClassName test` away from reproducing itself.
|
||||
|
||||
| Test class | Backs post | Proves |
|
||||
|---|---|---|
|
||||
| [`GetVsGetReferenceTest`](src/test/java/com/ankurm/hibernatedemo/GetVsGetReferenceTest.java) | 4859 (get vs getReference) | The 4-calls-4-outcomes table, the same-session matrix, proxy identity vs `equals()` |
|
||||
| [`MergeRefreshTest`](src/test/java/com/ankurm/hibernatedemo/MergeRefreshTest.java) | 4860 (merge vs refresh) | `merge()` returns the pre-existing managed instance; `merge()` initializes a cascaded LAZY collection; `refresh()` silently discards an unflushed edit |
|
||||
| [`OptimisticLockTest`](src/test/java/com/ankurm/hibernatedemo/OptimisticLockTest.java) | 4860 (merge vs refresh) | Exactly when `OptimisticLockException` surfaces relative to `merge()`/commit |
|
||||
| [`IdentityBatchTest`](src/test/java/com/ankurm/hibernatedemo/IdentityBatchTest.java) | 4861 (batch inserts) | `GenerationType.IDENTITY` disables batching entirely |
|
||||
| [`SequenceBatchTest`](src/test/java/com/ankurm/hibernatedemo/SequenceBatchTest.java) | 4861 (batch inserts) | `GenerationType.SEQUENCE` allows real batching |
|
||||
| [`AllocationSizeSweepTest`](src/test/java/com/ankurm/hibernatedemo/AllocationSizeSweepTest.java) | 4861 (batch inserts) | `allocationSize` sweep (1, 10, 25, 50) at fixed `batch_size=25` |
|
||||
| [`BatchSizeSweepTest`](src/test/java/com/ankurm/hibernatedemo/BatchSizeSweepTest.java) | 4861 (batch inserts) | `batch_size` sweep (1, 10, 25, 50) at fixed `allocationSize=50` |
|
||||
|
||||
For chapters 04–14, the test classes are linked directly from each chapter rather than
|
||||
duplicated here — every source file, test class, and captured output file a chapter discusses is
|
||||
a relative link in that chapter.
|
||||
|
||||
```bash
|
||||
./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, 141 tests, 0 failures as of the last commit
|
||||
```
|
||||
|
||||
## CommandLineRunner scenarios
|
||||
|
||||
The original narrative scenarios (chapters 01–03 only) 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](docs/01-get-vs-load.md) |
|
||||
| `mergerefresh` | `merge()` vs `refresh()` against a `@Version`-ed entity | [docs/02-merge-vs-refresh.md](docs/02-merge-vs-refresh.md) |
|
||||
| `insert-identity` | Batch insert attempt with `GenerationType.IDENTITY` | [docs/03-inserting-objects.md](docs/03-inserting-objects.md) |
|
||||
| `insert-sequence` | The same insert, with `GenerationType.SEQUENCE` | [docs/03-inserting-objects.md](docs/03-inserting-objects.md) |
|
||||
|
||||
```bash
|
||||
./scripts/run.sh getvsload
|
||||
./scripts/run.sh mergerefresh
|
||||
./scripts/run.sh insert-identity
|
||||
./scripts/run.sh insert-sequence
|
||||
```
|
||||
|
||||
Chapters 04–14 don't have `CommandLineRunner` profiles — they're covered entirely by JUnit tests
|
||||
and `javap`/`unzip` probes, captured the same way (see below).
|
||||
|
||||
## Regenerating captured output
|
||||
|
||||
```bash
|
||||
./scripts/run-all.sh
|
||||
```
|
||||
|
||||
Regenerates the `CommandLineRunner`-scenario files in `docs/output/` (chapters 01–03 only).
|
||||
`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. Every other transcript in
|
||||
`docs/output/` (chapters 04–14, and the test-suite transcripts for 01–03) was captured directly
|
||||
from `./mvnw -Dtest=... test`, `javap`, or `unzip -l` piped through the same cleanup approach, not
|
||||
regenerated by a single script — see each chapter for the exact command that produced its output.
|
||||
|
||||
## Output index
|
||||
|
||||
`docs/output/` holds 56 raw, unedited transcripts, grouped here by topic prefix.
|
||||
|
||||
| Prefix | Files | What it captures |
|
||||
|---|---|---|
|
||||
| `get-vs-*` | 2 | Chapter 01: the `CommandLineRunner` scenario and the `GetVsGetReferenceTest` run |
|
||||
| `merge-vs-refresh*` | 2 | Chapter 02: the `CommandLineRunner` scenario and the `MergeRefreshTest`/`OptimisticLockTest` run |
|
||||
| `insert-*` | 2 | Chapter 03: the identity vs. sequence `CommandLineRunner` scenarios |
|
||||
| `allocation-and-batch-size-sweeps.txt` | 1 | Chapter 03: the allocationSize and batch_size sweep test run |
|
||||
| `mappingstyle-*` | 1 | Chapter 04: the full `hbm.xml`/`orm.xml`/`mapping.xml` test run |
|
||||
| `persistenceannotations-*` | 1 | Chapter 05: the full persistence-annotations test run |
|
||||
| `naturalid-*` | 1 | Chapter 06: the full natural-id L1/L2/mutability/composite test run |
|
||||
| `immutable-*` | 2 | Chapter 07: the `@Immutable` behavior run, and the `javap` capture of the annotation itself |
|
||||
| `procedure-*` | 4 | Chapter 08: happy-path and failure-mode runs, the HSQLDB driver quirk reproduction, and the `javap` API-surface capture |
|
||||
| `testdb-*` | 9 | Chapter 09: dialect resolution, DDL generation, the reserved-word survey, cross-database behavior, schema isolation, startup timing, the Derby dialect failure, and the jcache classpath-pollution failure |
|
||||
| `jndi-*` | 6 | Chapter 10: the `SimpleNamingContextBuilder` removal proof, the simple-jndi jar listing, Boot's JNDI autoconfigure `javap` capture, Hibernate's own JNDI datasource `javap` capture, and both the filtered and unfiltered full test runs |
|
||||
| `proxy-*` | 7 | Chapter 11: proxy identity and `LazyInitializationException` (filtered and raw), entity-graph fetch behavior (filtered and raw), OSIV masking (filtered and raw), and the `javap` capture of proxy-related settings |
|
||||
| `association-*` | 4 | Chapter 12: N+1 counting, the multiple-bag-fetch/cartesian-product runs, the `@OneToOne` lazy trap, and cascade/orphanRemoval |
|
||||
| `datetime-*` | 7 | Chapter 13: basic temporal round trips, the `@Temporal` deprecation warning, both `@TimeZoneStorage` JVM-zone runs, nanosecond rounding on both H2 and HSQLDB, and `hibernate.jdbc.time_zone` |
|
||||
| `namedquery-*` | 4 | Chapter 14: startup validation, execution/projections, `orm.xml` named queries, and the pre-parsing performance comparison |
|
||||
|
||||
## 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
|
||||
│ └── check_links.py walk docs/*.md and verify every relative link resolves on disk
|
||||
├── src/main/java/com/ankurm/hibernatedemo/
|
||||
│ ├── HibernateDemoApplication.java
|
||||
│ ├── model/ Book, Note, Widget* entities (chapters 01–03)
|
||||
│ ├── scenario/ one CommandLineRunner per profile (chapters 01–03)
|
||||
│ ├── mappingstyle/ chapter 04 entities
|
||||
│ ├── persistenceannotations/ chapter 05 entities
|
||||
│ ├── naturalid/ chapter 06 entities
|
||||
│ ├── immutable/ chapter 07 entities
|
||||
│ ├── procedure/ chapter 08 entities
|
||||
│ ├── testdb/ chapter 09 entity
|
||||
│ ├── proxy/ chapter 11 entities
|
||||
│ ├── association/ chapter 12 entities
|
||||
│ ├── datetime/ chapter 13 entities
|
||||
│ └── namedquery/ chapter 14 entities
|
||||
├── src/test/java/com/ankurm/
|
||||
│ ├── brokenprobe/ a deliberately broken entity kept outside the scanned package (chapter 14)
|
||||
│ └── hibernatedemo/
|
||||
│ ├── GetVsGetReferenceTest.java, MergeRefreshTest.java, OptimisticLockTest.java,
|
||||
│ │ IdentityBatchTest.java, SequenceBatchTest.java, AllocationSizeSweepTest.java,
|
||||
│ │ BatchSizeSweepTest.java, ImmutableEntityTest.java, ImmutableBulkUpdateAllowedTest.java,
|
||||
│ │ ImmutableFlushCostTest.java (chapters 01–03, 07)
|
||||
│ ├── mappingstyle/, persistenceannotations/, naturalid/, procedure/, testdb/,
|
||||
│ │ proxy/, association/, datetime/, namedquery/, jndi/ (chapters 04–06, 08–14)
|
||||
└── docs/
|
||||
├── 00-versions.md .. 14-named-queries.md
|
||||
└── output/*.txt captured, unedited console transcripts (56 files)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user