Working through a Hibernate StatelessSession, you load an entity, change one of its fields, and also add an element to its @OneToMany collection that is mapped with CascadeType.ALL. What is written to the database, and why?
answer
- Detached from birth — no snapshot
- update() writes ALL columns
- Collections ignored, cascades not applied
- You order parent-then-child inserts
- Callbacks and L2 invalidation vanish
basics
~20 sNothing at all. There is no dirty checking, so the field change is invisible until you call update(entity) — which then writes every mapped column. Collections are ignored and cascades do not run, so the new child is never inserted; you must insert it and set its foreign key yourself.
solid answer
~50 sNothing is written, and that surprises people twice over. **The scalar change**: a stateless session keeps no loaded-state snapshot and no persistence context, so there is nothing to dirty-check against and no flush that would look. The mutation lives only in your Java object. You have to call `update(entity)` explicitly — and because Hibernate has no snapshot to diff, the resulting `UPDATE` sets **all** mapped columns from the current object state, not just the one you touched. **The collection change**: a stateless session ignores collections entirely and does not apply cascades. `CascadeType.ALL` is simply not consulted. The child row is never inserted, and no join-table row is written either. You must `insert(child)` yourself with the foreign key populated. Two further consequences worth stating: no lifecycle callback or interceptor fires for any of this, and because `update()` writes every column, a stale in-memory object can silently overwrite columns another writer changed.
code
java · 10 linesOrder o = ss.get(Order.class, 1L);
o.setTotal(new BigDecimal("99.00")); // no snapshot, no flush: nothing happens
o.getLines().add(new OrderLine()); // collections ignored, cascade not applied
// correct form:
ss.update(o); // UPDATE sets EVERY mapped column
OrderLine line = new OrderLine();
line.setOrder(o); // set the FK yourself
ss.insert(line); // parent first, then childgo deeper
Get the outcome right — nothing is written — and know that stateless work requires explicit insert/update/delete calls.
Explain the mechanism: no persistence context and no loaded-state snapshot, so no dirty checking; collections ignored and cascades not applied.
Add the second-order effects — the all-columns UPDATE and its blind-overwrite risk, manual insert ordering, callbacks and second-level cache invalidation that no longer happen — and show the corrected code.
Treat it as a boundary decision: stateless access removes cross-cutting behaviour the rest of the codebase relies on, so it belongs in isolated bulk jobs with their own review standards, not scattered through domain code.
## Why nothing happens Both surprises come from the same root cause: a `StatelessSession` has **no persistence context**, and every convenience of a regular `Session` is built on top of one. ### The field change In a stateful `Session`, loading an entity stores two things — the instance, and a *loaded-state snapshot* copying every mapped scalar as it came from the database. At flush time Hibernate diffs the instance against the snapshot and generates an `UPDATE` for the differences. That is automatic dirty checking. A stateless session stores neither. The object it hands you is **detached from birth**. There is no registry that knows the object exists, no snapshot to compare against, and no flush event that would look. Mutating a field is therefore a purely in-memory operation with no persistence consequence whatsoever. Silence, not an exception. The explicit form is `statelessSession.update(entity)`. Note the important secondary effect: with no snapshot, Hibernate cannot know which columns changed, so the `UPDATE` includes **every mapped column** with the object's current values. Two consequences follow: - **Lost-update risk by overwrite.** If you loaded the row a while ago and another writer changed a column you never touched, your update writes your stale value back over theirs. In a stateful session, dirty checking would have limited the statement to the columns you actually changed and left theirs alone. - **You must load the whole row.** Updating from a partially populated object writes nulls or defaults into the columns you did not populate. ### The collection change Hibernate's documentation is blunt: collections are ignored by a stateless session, and operations do not cascade to associated instances. `CascadeType.ALL`, `CascadeType.PERSIST`, orphan removal — none of it is consulted, because the machinery that applies cascades lives in the stateful event pipeline. So adding a child to `parent.getLines()` mutates a Java list and nothing else. There is no INSERT for the child, no INSERT into a join table, no UPDATE of the child's foreign key. The correct code sets the relationship yourself and writes each row explicitly: set the child's parent reference (or its foreign-key field), then call `insert(child)`. And you own the **ordering**: parents before children, because there is no action queue reordering statements for you. A stateful session sorts insertions so foreign keys resolve; a stateless one issues statements as you call them. ## The other silent losses in this scenario - **No lifecycle callbacks or interceptors.** `@PrePersist`, `@PreUpdate`, entity listeners, and `Interceptor` hooks are not invoked, because stateless operations bypass Hibernate's event system. If your entity relies on a callback to stamp `updatedAt` or derive a field, that field is now whatever the object happens to hold. - **No second-level cache interaction.** The row you updated may still be cached from earlier stateful reads; the stateless write does not invalidate it. Long-lived caches can serve the pre-update value indefinitely. - **No identity guarantees.** If the same logical row is loaded twice in the job, you hold two independent objects, and updating one does not reflect in the other. Any `equals`/`hashCode` based on object identity misbehaves; implement them on the business or primary key. - **Lazy associations are not navigable.** You cannot count on walking `parent.getLines()` to *read* the children either — with no session-attached proxies, the supported route is an explicit `join fetch` in the query or a separate query. ## The mental model to carry away A stateless session is not "a session with the cache turned off". It is a thin, explicit command API over SQL that happens to reuse your ORM mappings for column mapping and type conversion. Everything the ORM normally *infers* — what changed, what to cascade, what order to write, what side effects to fire — you now state explicitly. That is precisely why it is fast and predictable for bulk work, and precisely why it is dangerous for domain logic where those inferences carry business meaning. A strong answer states the outcome (nothing is written), gives the mechanism (no snapshot, no persistence context, no event pipeline), shows the corrected code, and adds at least one of the second-order consequences — the all-columns update, the manual ordering, or the vanished callbacks.
- Why does StatelessSession.update() write every mapped column instead of only the changed ones?Because it has no loaded-state snapshot to diff against. Column-narrowed updates in a stateful session are a product of dirty checking: Hibernate compares current values to the snapshot and builds the SET clause from the differences. With no snapshot there is no way to know what changed, so the statement writes the whole row from the object's current state.
- What is the practical risk of that all-columns update under concurrency?Blind overwrite. If you loaded the row earlier and another writer has since changed a column you never touched, your update writes your stale copy of that column back over theirs, losing their change without any error. Mitigations are to keep the load-to-update window short, to re-read immediately before updating, or to use a targeted bulk `update ... set` HQL statement that touches only the columns you mean to change.
- How do you correctly write a parent and its children through a StatelessSession?Insert the parent first so its identifier exists, then set each child's foreign-key reference explicitly and insert the children one by one. There is no action queue reordering statements and no cascade, so ordering and relationship wiring are entirely your responsibility. If the graph is deep or the ordering rules are non-trivial, that is a signal to use a regular Session with batched flush/clear instead.
A stateful session is an editor who watches your manuscript and files every change you make. A stateless one is a printing press: it prints exactly the page you hand it, whole, in the order you hand them over — and never notices you scribbled on your own copy.
saying these in an interview costs you the question
- Expecting the field change to be flushed at commit.
- Believing CascadeType.ALL still inserts the child from a stateless session.
- Assuming update() produces a narrowed UPDATE of just the changed column.
- Forgetting that parents must be inserted before children, since nothing reorders statements.
- Not realising @PreUpdate and interceptors are skipped, so audit fields go unstamped.