1
0

Add hibernate-demo: get() vs load(), merge() vs refresh(), inserting objects (Hibernate 7.4.1.Final + Spring Boot 4.1.0)

This commit is contained in:
2026-08-26 17:26:04 +00:00
commit 5533c6c8fe
24 changed files with 1653 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
# 03 — Inserting objects efficiently
[← Previous: 02 — merge() vs refresh()](02-merge-vs-refresh.md) | [Back to README →](../README.md)
Backs [ankurm.com: Inserting objects efficiently](https://ankurm.com/mastering-hibernate-7-the-ultimate-guide-to-inserting-objects-efficiently/).
Sources: [`InsertIdentityRunner`](../src/main/java/com/ankurm/hibernatedemo/scenario/InsertIdentityRunner.java) /
[`InsertSequenceRunner`](../src/main/java/com/ankurm/hibernatedemo/scenario/InsertSequenceRunner.java),
entities [`WidgetIdentity`](../src/main/java/com/ankurm/hibernatedemo/model/WidgetIdentity.java) /
[`WidgetSequence`](../src/main/java/com/ankurm/hibernatedemo/model/WidgetSequence.java).
Run both with `./scripts/run.sh insert-identity` and `./scripts/run.sh insert-sequence`.
Transcripts: [`docs/output/insert-identity.txt`](output/insert-identity.txt),
[`docs/output/insert-sequence.txt`](output/insert-sequence.txt).
## The one-line difference that matters
Both entities persist 30 rows in a single transaction with **identical** settings:
```yaml
hibernate.jdbc.batch_size: 25
hibernate.order_inserts: true
```
`WidgetIdentity` uses `@GeneratedValue(strategy = GenerationType.IDENTITY)`.
`WidgetSequence` uses `@GeneratedValue(strategy = GenerationType.SEQUENCE)` with a matching
`allocationSize = 25`. That's the entire diff between the two entity classes.
## What Hibernate's own statistics say happened
| | `entityInsertCount` | `prepareStatementCount` |
|---|---|---|
| `WidgetIdentity` (IDENTITY) | 30 | **30** |
| `WidgetSequence` (SEQUENCE) | 30 | **4** |
`hibernate.jdbc.batch_size=25` did nothing at all for the `IDENTITY` run — every one of the 30
inserts is its own round trip to the database (`prepareStatementCount` equals
`entityInsertCount`). With `SEQUENCE`, Hibernate knows the id before the row is written, so it
can queue inserts and batch them: 30 rows at a batch size of 25 means two insert batches
(25 + 5), plus two calls to pull the next block of ids from `widget_seq` (the sequence's
`allocationSize` is also 25, so the first 25 ids come from one call and the remaining 5 force a
second) — four prepared statements total, for the same 30 rows.
## What surprised me building this
The number that surprised me was not "IDENTITY doesn't batch" — that's documented, if you know
to look for it. It was seeing `prepareStatementCount` for `SEQUENCE` land at exactly **4**, not
2. It's obvious in hindsight — `allocationSize` governs how often the sequence itself gets hit,
independently of `batch_size` governing how the inserts get grouped — but "obvious in hindsight"
and "what I would have guessed beforehand" are different things, and the gap between them is
exactly what running this instead of describing it catches. If `allocationSize` had been left at
JPA's default of `50` instead of matching `batch_size` at `25`, the sequence would only need one
call for all 30 ids, and `prepareStatementCount` would drop to 3 — a change to a number that has
nothing to do with batching, moving a number that looks like it's entirely about batching.
The practical version of this: if a switch to `SEQUENCE` doesn't produce the batching win the
Hibernate docs promise, checking `hibernate.generate_statistics=true` and reading
`prepareStatementCount` directly answers "is it actually batching" in a way that reading the
`hibernate.jdbc.batch_size` value in a config file cannot — the config says what was requested,
not what happened.
## Long-tail edge cases not covered above
- **`save()` vs `persist()`** — `save()` is Hibernate's own pre-JPA API and still works, but
returns the generated id immediately rather than `void`, and can be called outside a
transaction (where it will fail later, confusingly, at flush time). `persist()` is the
JPA-portable choice; there's no scenario for this in this repo because the observable
difference is in the method signature and portability, not in captured runtime behaviour.
- **Bulk inserts via `StatelessSession`** — bypasses the persistence context and lifecycle
callbacks entirely; worth a dedicated repository of its own rather than a profile bolted onto
this one, since the interesting failure modes (cascades silently not firing, no dirty
checking) need a scenario built around triggering them specifically.
- **Native SQL batch inserts via `createNativeQuery` + `addBatch`** — sidesteps Hibernate's own
batching machinery altogether; the batching behaviour at that point is entirely the JDBC
driver's, not Hibernate's, which is a different post.
## Full captured transcripts
### `insert-identity.txt`
```console
Hibernate: create global temporary table HTE_book(rn_ integer not null, id bigint, version bigint, author varchar(255), title varchar(255), primary key (rn_)) transactional
Hibernate: create global temporary table HTE_widget_sequence(rn_ integer not null, id bigint, name varchar(255), primary key (rn_)) transactional
Hibernate: create table book (id bigint not null, author varchar(255), title varchar(255), version bigint not null, primary key (id))
Hibernate: create table widget_identity (id bigint generated by default as identity, name varchar(255), primary key (id))
Hibernate: create table widget_sequence (id bigint not null, name varchar(255), primary key (id))
Hibernate: create sequence book_seq start with 1 increment by 50
Hibernate: create sequence widget_seq start with 1 increment by 25
--- inserting 30 WidgetIdentity rows (GenerationType.IDENTITY) ---
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-1]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-2]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-3]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-4]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-5]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-6]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-7]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-8]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-9]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-10]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-11]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-12]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-13]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-14]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-15]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-16]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-17]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-18]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-19]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-20]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-21]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-22]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-23]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-24]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-25]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-26]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-27]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-28]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-29]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetIdentity */insert into widget_identity (name,id) values (?,default)
binding parameter (1:VARCHAR) <- [identity-30]
entityInsertCount = 30
prepareStatementCount = 30
(with IDENTITY, expect prepareStatementCount to land close to entityInsertCount -- each insert has to go to the database immediately to hand back the generated key, so there is nothing left for hibernate.jdbc.batch_size to batch)
```
### `insert-sequence.txt`
```console
Hibernate: create global temporary table HTE_book(rn_ integer not null, id bigint, version bigint, author varchar(255), title varchar(255), primary key (rn_)) transactional
Hibernate: create global temporary table HTE_widget_sequence(rn_ integer not null, id bigint, name varchar(255), primary key (rn_)) transactional
Hibernate: create table book (id bigint not null, author varchar(255), title varchar(255), version bigint not null, primary key (id))
Hibernate: create table widget_identity (id bigint generated by default as identity, name varchar(255), primary key (id))
Hibernate: create table widget_sequence (id bigint not null, name varchar(255), primary key (id))
Hibernate: create sequence book_seq start with 1 increment by 50
Hibernate: create sequence widget_seq start with 1 increment by 25
--- inserting 30 WidgetSequence rows (GenerationType.SEQUENCE, allocationSize=25) ---
Hibernate: select next value for widget_seq
Hibernate: select next value for widget_seq
Hibernate: select next value for widget_seq
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-1]
binding parameter (2:BIGINT) <- [1]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-2]
binding parameter (2:BIGINT) <- [2]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-3]
binding parameter (2:BIGINT) <- [3]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-4]
binding parameter (2:BIGINT) <- [4]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-5]
binding parameter (2:BIGINT) <- [5]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-6]
binding parameter (2:BIGINT) <- [6]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-7]
binding parameter (2:BIGINT) <- [7]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-8]
binding parameter (2:BIGINT) <- [8]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-9]
binding parameter (2:BIGINT) <- [9]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-10]
binding parameter (2:BIGINT) <- [10]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-11]
binding parameter (2:BIGINT) <- [11]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-12]
binding parameter (2:BIGINT) <- [12]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-13]
binding parameter (2:BIGINT) <- [13]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-14]
binding parameter (2:BIGINT) <- [14]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-15]
binding parameter (2:BIGINT) <- [15]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-16]
binding parameter (2:BIGINT) <- [16]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-17]
binding parameter (2:BIGINT) <- [17]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-18]
binding parameter (2:BIGINT) <- [18]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-19]
binding parameter (2:BIGINT) <- [19]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-20]
binding parameter (2:BIGINT) <- [20]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-21]
binding parameter (2:BIGINT) <- [21]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-22]
binding parameter (2:BIGINT) <- [22]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-23]
binding parameter (2:BIGINT) <- [23]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-24]
binding parameter (2:BIGINT) <- [24]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-25]
binding parameter (2:BIGINT) <- [25]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-26]
binding parameter (2:BIGINT) <- [26]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-27]
binding parameter (2:BIGINT) <- [27]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-28]
binding parameter (2:BIGINT) <- [28]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-29]
binding parameter (2:BIGINT) <- [29]
Hibernate: /* insert for com.ankurm.hibernatedemo.model.WidgetSequence */insert into widget_sequence (name,id) values (?,?)
binding parameter (1:VARCHAR) <- [sequence-30]
binding parameter (2:BIGINT) <- [30]
entityInsertCount = 30
prepareStatementCount = 4
(with SEQUENCE, the id is known before the row is written, so Hibernate can defer and batch the inserts -- expect prepareStatementCount well below entityInsertCount)
```
[← Previous: 02 — merge() vs refresh()](02-merge-vs-refresh.md) | [Back to README →](../README.md)