6.6 KiB
01 — get() vs load() / getReference()
← Previous: 00 — Versions | Next: 02 — merge() vs refresh() →
Backs ankurm.com: Hibernate 7 — get() vs load().
Source: GetVsLoadRunner.
Run it yourself with ./scripts/run.sh getvsload; the full transcript below is committed at
docs/output/get-vs-load.txt.
What the six steps show
session.get()on an existing id — oneSELECTimmediately, returns the real entity.session.get()on a missing id — oneSELECT, returnsnull. No exception, ever.session.getReference()on an existing id — noSELECTat the call site. TheSELECTonly happens the moment a non-identifier accessor (getTitle()) is called on the returned proxy. The log ordering in the transcript is the actual evidence for this, not a claim about it — theabout to call/now callingmarkers in the code bracket exactly where eachSELECTdoes or doesn't appear.session.getReference()on a missing id — same deferral: the proxy is handed back with no exception, and only accessing it throwsjakarta.persistence.EntityNotFoundException: No row with the given identifier exists.- A proxy outlives its session —
getReference(), then commit and close, then access:org.hibernate.LazyInitializationException: Could not initialize proxy [...] - no session. - Proxy identity —
Book.class.isInstance(proxy)istrue, butreal.getClass() == proxy.getClass()isfalse(the proxy's runtime class isBook$HibernateProxy), andreal.equals(proxy)isfalseeven though both represent the same row.
What surprised me building this
Step 6 is the one worth sitting with. Book here does not override equals(), so
real.equals(proxy) falling through to reference equality is not a Hibernate quirk — it's plain
Java doing exactly what an un-overridden equals() always does. But it means the moment get()
and getReference() are mixed for the same row in code that ever compares entities by
equals() (a Set, a List.contains(), a manual reconciliation), the proxy boundary silently
breaks that comparison. instanceof survives it. equals(), hashCode()-based collections, and
naive == do not. The fix is the one every Effective-Java-style guide already recommends —
implement equals()/hashCode() off the identifier, never off the object's identity — but this
is what makes skipping that advice actually bite: not a compile error, a false you don't
notice until two supposedly-identical entities land in the same HashSet and both show up.
Also unexpected going in: getReference() on a missing row throws EntityNotFoundException
on first non-id access, not ObjectNotFoundException. The two names get used interchangeably in
older Hibernate discussion; running it pins down which one this exact version actually throws
(see the transcript for the full class name and message).
Full captured transcript
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
Hibernate: select next value for book_seq
Hibernate: /* insert for com.ankurm.hibernatedemo.model.Book */insert into book (author,title,version,id) values (?,?,?,?)
binding parameter (1:VARCHAR) <- [Joshua Bloch]
binding parameter (2:VARCHAR) <- [Effective Java]
binding parameter (3:BIGINT) <- [0]
binding parameter (4:BIGINT) <- [1]
SEED: inserted Book id=1
--- Step 1: session.get() on an existing id ---
about to call session.get(Book.class, 1)
Hibernate: select b1_0.id,b1_0.author,b1_0.title,b1_0.version from book b1_0 where b1_0.id=?
binding parameter (1:BIGINT) <- [1]
get() returned: Book{id=1, title=Effective Java, author=Joshua Bloch, version=0}
--- Step 2: session.get() on a missing id ---
about to call session.get(Book.class, 999001)
Hibernate: select b1_0.id,b1_0.author,b1_0.title,b1_0.version from book b1_0 where b1_0.id=?
binding parameter (1:BIGINT) <- [999001]
get() returned: null (no exception thrown)
--- Step 3: session.getReference() on an existing id ---
getReference() returned proxy of class com.ankurm.hibernatedemo.model.Book$HibernateProxy -- no SELECT above this line
now calling proxy.getTitle() ...
Hibernate: select b1_0.id,b1_0.author,b1_0.title,b1_0.version from book b1_0 where b1_0.id=?
binding parameter (1:BIGINT) <- [1]
getTitle() returned 'Effective Java' -- the SELECT for this ran just above this line
--- Step 4: session.getReference() on a missing id ---
getReference() returned a proxy for a row that does not exist -- no exception yet: com.ankurm.hibernatedemo.model.Book$HibernateProxy
Hibernate: select b1_0.id,b1_0.author,b1_0.title,b1_0.version from book b1_0 where b1_0.id=?
binding parameter (1:BIGINT) <- [999001]
accessing the proxy threw jakarta.persistence.EntityNotFoundException: No row with the given identifier exists for entity [com.ankurm.hibernatedemo.model.Book with id '999001']
--- Step 5: proxy accessed after its session is closed ---
session closed. proxy in hand: com.ankurm.hibernatedemo.model.Book$HibernateProxy
accessing the proxy after close threw org.hibernate.LazyInitializationException: Could not initialize proxy [com.ankurm.hibernatedemo.model.Book#1] - no session
--- Step 6: proxy identity vs a real loaded instance ---
Hibernate: select b1_0.id,b1_0.author,b1_0.title,b1_0.version from book b1_0 where b1_0.id=?
binding parameter (1:BIGINT) <- [1]
real.getClass() = com.ankurm.hibernatedemo.model.Book
proxy.getClass() = com.ankurm.hibernatedemo.model.Book$HibernateProxy
proxy instanceof Book.class: true
real.getClass() == proxy.getClass(): false
real.equals(proxy) before proxy access: false
← Previous: 00 — Versions | Next: 02 — merge() vs refresh() →