The labs on this site need JavaScript to run. The same content is written out below.
Learn › JPA › Proxies and fetching
Proxies and lazy fetching
Is an object received through lazy fetching a real entity?
A member object you get from em.getReference or from lazy fetching may not be a real Member. It may be a proxy (a stand-in object) that knows only the identifier. A proxy queries the DB only at the moment the real data is needed.
Checked against real execution · 20 combinations were run on Hibernate 7.4.5 and H2, and whether m is a proxy, when the query SQL was sent, and the result values and exceptions were checked
Concepts in this lab
- Entity proxy
- A stand-in object that is put in the place of a real entity. Hibernate creates a subclass of the entity class at run time and returns an object of it, and at first it knows only the identifier and the rest of its contents is empty. The moment you first read a value other than the identifier, it queries the DB and reads the real entity; this is called proxy initialization. The result of em.getReference and an association configured for lazy fetching come in as proxies. The class of a proxy is different from the entity class, so use instanceof instead of getClass() when you compare types.
- LazyInitializationException
- The exception that Hibernate throws when you try to use an uninitialized proxy or a lazily fetched collection after the persistence context has been closed. It occurs because initializing a proxy requires querying the DB through the persistence context, and that is already closed, so there is no way to query. You often meet it when you take an associated object in the controller or the view after the transaction has ended. You avoid it by reading the values you need in advance inside the transaction, by fetching them together from the start with a fetch join, or by converting the entity to an object that holds only the values you need before you return it.
- Lazy fetching and eager fetching
- Settings for when to load an associated entity. Eager fetching (EAGER) loads the associated entities together when the entity is loaded. Lazy fetching (LAZY) puts a proxy object in the place of the associated entity and loads it when its values are actually used. Eager fetching always loads data even when it is not needed and produces queries that are hard to predict, so using lazy fetching as the default is recommended.
- Persistence context
- The logical space in JPA that holds and manages entities. Entities that you load or save are kept here, and JPA tracks the changes made to them and applies those changes to the database when the transaction ends. It usually shares its lifecycle with one transaction (in a web request with Open Session in View (OSIV) turned on, it lasts until the request ends). Features such as the first-level cache, dirty checking, and transactional write-behind work on top of it.
- First-level cache
- The store inside the persistence context that keeps entities by identifier. If you load the same identifier again within the same transaction, it returns the stored entity without sending a query to the database. As a result, entities with the same identifier are always the same object within the same transaction. By default it disappears when the transaction ends (in a web request with Open Session in View (OSIV) turned on, it lasts until the request ends), so it is different from an application-wide cache.
Try these
- Default settings — No SQL is sent when you get it with getReference, and a SELECT is sent when you read the name.
- Reading only the identifier — The proxy already knows that value, so no SQL is sent.
- A member received through lazy fetching — What order.getMember() returns is a proxy, too.
- After the persistence context is closed — There is no way to fill the empty proxy, so an exception is thrown.
- Comparing with getClass() — The class of the proxy is not Member, so the result is false.
- When a member was already read — getReference returns the real entity, not a proxy.
- When you got the proxy first — The em.find called later returns that proxy, too.
Running with the default settings
- em.getReference(Member.class, 1L): Does not query the DB and returns a proxy object that holds only the identifier 1. A proxy is an object of a class that Hibernate creates at run time as a subclass of Member, and the rest of its contents, such as the name, is still empty.
- m.getName(): The proxy holds only the identifier, so it cannot know the name. At this moment, it sends a SELECT to read the real Member (initializing the proxy), and the proxy is connected to that object.
- The proxy calls getName() of the real Member on its behalf and returns "Alice". A proxy that has been initialized once does not query again.
Result of this run
- The query SQL is sent not at the moment you receive the object but at the moment you first use a value other than the identifier. This is lazy fetching.
Code
@Entity
class Member {
@Id Long id;
String name;
Long getId() { return id; }
String getName() { return name; }
}
// The DB has member 1 "Alice"
Member m = em.getReference(Member.class, 1L);
String name = m.getName();
Assumptions of this model
All 20 combinations (5 ways to get the object and 4 things to do with it) were run on real Hibernate 7.4.5 and an H2 database, and whether m is a proxy, when and how many times the member table was queried, and the value or exception on the last line were confirmed to match the screen. This is based on an ordinary mapping where @Id is placed on the entity's field and getters are provided, and on a single persistence context. The name of the proxy class was Member$HibernateProxy in this environment, and depending on the version, a suffix may be added after it. The following were also confirmed in the same environment. If you read a field of the proxy directly without going through a getter (m.name), the proxy is not initialized and you get null. If you call getReference with an identifier that is not in the DB, there is no exception at that point, and an exception (ObjectNotFoundException) is thrown after the query is sent when you first read a value. Even after the persistence context is closed, getId() of a proxy returns its value. In a Spring Boot web request, the default OSIV setting keeps the persistence context open until the response ends, so this exception may show up late, but that part was not verified by execution. The documentation says that in an environment that uses bytecode enhancement, lazy fetching may work in another way instead of through proxies, but this was not run either.
Previous lab: Cascade and orphan removal · Next lab: N+1