Skip to main content

Hibernate 7

Hibernate 7 get() vs getReference(): Four Calls, Four Outcomes

session.get() and session.getReference() look interchangeable at a glance, but the difference isn't "eager vs lazy" -- it's what you're telling Hibernate you need. This piece runs four calls against Hibernate 7.4.1.Final, builds the full same-session matrix, and pins down exactly where the proxy identity contract breaks -- including one same-session result that the usual L1-cache explanation gets wrong.

Hibernate 7 merge() vs refresh(): Which One Fails Loudly?

merge() and refresh() get described as opposite directions of the same tool -- one pushes, one pulls. Run them against a real @Version-ed entity and that framing misses the actual question: which one fails loudly when the state it's holding is stale? Three named experiments against Hibernate 7.4.1.Final, including a result that overturned the article's own original assumption.

Hibernate 7 Batch Inserts: How to Prove Batching Is Working

Setting hibernate.jdbc.batch_size is not the same as verifying batching happens. Two nearly identical entities, one GenerationType difference, and Hibernate's own statistics show a 30-vs-4 prepared-statement gap -- then two sweeps (allocationSize, batch_size) run the numbers instead of predicting them, including one off-by-one that reproduced across two separate full-suite runs.

Hibernate Validator + CDI: When @Inject Actually Works Inside a Validator

The same ConstraintValidator, built two ways: Validation.buildDefaultValidatorFactory() throws a NullPointerException on an @Inject field it never processes, while a real Weld SE CDI container resolves the same field correctly. This post isolates exactly which ValidatorFactory makes @Inject real inside Hibernate Validator.

Hibernate 7 Aggregate Functions: COUNT, SUM, AVG, GROUP BY/HAVING, and the Null-vs-Zero Trap

Pulling thousands of rows into Java just to sum or average them is the memory-first trap. This guide covers Hibernate 7's COUNT, SUM, AVG, MIN, MAX, GROUP BY/HAVING with typed record projections, the Criteria API equivalent, and HQL window functions, including the null-vs-zero trap on empty result sets and the correction that window functions have existed since Hibernate 6.2, not 7.

persist, save, merge, saveOrUpdate in Hibernate 7: The Five Decisions That Cause Duplicate Inserts

Here is the table that every Hibernate tutorial promises and none of them finish: MethodEntity state: TransientEntity state: ManagedEntity state: Detachedpersist()Schedules INSERT; ID assigned at flush (SEQUENCE) or immediately (IDENTITY)No-op — entity already trackedThrows PersistentObjectExceptionsave()Schedules INSERT; returns ID immediately (may fire INSERT for IDENTITY)Returns existing ID; entity already trackedTreats as new: ignores existing ID, generates new one — duplicate insertmerge()Copies state to new managed entity; returns the managed copyReturns same instance (identity)Issues SELECT to load current DB state; merges over it; returns managed copyupdate()Throws TransientPropertyValueExceptionNo-op if already in session; throws NonUniqueObjectException if different instance same IDRe-attaches; schedules UPDATE; throws NonUniqueObjectException if conflicting instance existssaveOrUpdate()Calls save() pathNo-op if already in sessionCalls update() path; same NonUniqueObjectException risk Most of the bugs that come to production code review — duplicate inserts, silent data loss, unexpected exceptions — trace to a mismatch between what the developer expected from the column above and what the table actually says. The rest of this post is a catalog of the five most common mismatches, with code showing exactly what goes wrong and why.

Sorting in Hibernate 7: @OrderBy, Criteria Order, Null Precedence, and the Dynamic-Sort Injection Trap

Sorting a List in Java after fetching it works until the table gets large, and letting a caller pick the sort column by string concatenation is an HQL injection risk. This guide covers Hibernate 7's @OrderBy, @SortNatural/@SortComparator, a whitelist fix for dynamic sorting, Criteria API Order across joins, JPA 3.2's Nulls precedence, and case-insensitive sorting, verified against a real project.

Pagination in Hibernate 7: LIMIT/OFFSET, the JOIN FETCH Trap, and Keyset Pagination

Fetching every row just to show ten is a linear-cost mistake. This guide covers Hibernate 7 pagination with setFirstResult/setMaxResults, ScrollableResults for batch jobs, keyset pagination, and the total-count pattern, plus the corrected JOIN FETCH in-memory-fallback trap: it only triggers when ordering by the fetched collection's own column, and the real warning code is HHH90003004, not HHH000104.

Hibernate 7 Interceptors: The State-Array Contract, Global Registration, and What Bulk Updates Skip

Scattering audit and normalization logic across every service method that saves an entity is a bug waiting for a forgotten call site. This guide covers Hibernate 7 interceptors: implementing Interceptor directly (EmptyInterceptor moved to an internal, non-extendable class, not removed), the state-array mutation contract, session-scoped vs. globally-registered interceptors, and why bulk HQL updates never call them at all.

Hibernate 7 Batch Insert: 1M Rows in 12s — Settings, Benchmarks, and the JDBC Trap

One million rows, PostgreSQL, HikariCP pool of 10, SEQUENCE generator: approximately 12 seconds with proper Hibernate batch configuration. Without it, the same job runs for over two minutes — and that's before accounting for the OutOfMemoryError you get around row 80,000 if you skip the flush/clear cycle. The configuration is four properties. The JDBC trap is one silent gotcha that disables batching without any error. The flush/clear cadence is one loop pattern. This post covers all three and the approximate numbers so you can reason about what your specific job should take. The Problem: The "Chatty" Database Trap When you persist objects in a standard loop, Hibernate sends one INSERT or UPDATE statement per object. This creates massive network latency. Imagine you have 10,000 records and a 5ms network round-trip delay. You have already lost 50 seconds just to "talk" to the database, even before the engine starts processing the data. This is often called the "N+1 Problem of Writing." Each individual insert requires the database to parse the SQL, execute it, update indexes, and send an acknowledgment. Multiplying this overhead by thousands of records is the fastest way to kill application performance.