Search This Blog

Thursday, 23 February 2012

Controlling the size of the Persistence cache

As we saw earlier the Persistence Cache holds a collection of loaded persistent objects. For every object that we load in Hibernate a separate copy (called a snapshot) is maintained within the persistence cache. This copy is used by Hibernate to mange dirty checks.(It is also used as a first level cache.) A downside to this phenomenon is that if we load a very huge object graph into the session there is a possibility that we might run into an OutOfMemoryException.
One of the times this could be avoided is when we load objects from queries. If the reason for the fetch is to simply read them, then the snapshot created is simply a waste of space. A prime example is data loaded from Master tables. This data is never modified, and hence the snapshot in this case is completely irrelevant from the dirty check perspective. So although we need these objects for the repeatable reads, we do not need them to be dirty checked.
Also once we are sure that we wont be needing a particular entity, it would be useful if we could get rid of it from  the persistence context.
Hibernate provides us with methods that helps manage the memory consumption for these objects within the persistence cache and also gain some performance improvements.
One option is use the session.readOnly(entity,true) method. This tells Hibernate that the loaded object is a read-only object and hence the snapshot is avoided. We can re-enable dirty checking for this object by calling the same method for the same object with value false.
If a particular set of objects have been operated on successfully and are not necessary needed within the session(i.e. all operations like update/insert are done and flushed) then the objects can be removed from the session using a session.evict(object) call. If all objects need to be cleared from the persistence cache then a session.clear() call can be used.
Consider the below sample code:
public static void testPCSize() {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    Entity entity1 = (Entity) session.load(Entity.class, 1);
    System.out.println("Session after loading 1 entity " + session);
    Entity entity2 = (Entity) session.load(Entity.class, 2);
    System.out.println("Session after loading 2 entities " + session);
    System.out.println("Data is " + entity1.getData() + " " + entity2.getData());
    System.out.println("Session after complete loading of 2 entities " + session);
    session.evict(entity1);
    System.out.println("Session after evicting 1 entity " + session);
    Entity entity3 = (Entity) session.get(Entity.class, 3);
    session.setReadOnly(entity3, true);
    System.out.println("Session after loading a read-only entity " + session);
    session.clear();
    System.out.println("Session after clear " + session);
    transaction.commit();
}
The logs indicate the following:
2765 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - loading en
tity: [com.model.Entity#1]
2765 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - creating n
ew proxy for entity
Session after loading 1 entity SessionImpl(PersistenceContext[entityKeys=[],coll
ectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreat
ions=[] collectionRemovals=[] collectionUpdates=[]])
2781 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - loading en
tity: [com.model.Entity#2]
2781 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - creating n
ew proxy for entity
Session after loading 2 entities SessionImpl(PersistenceContext[entityKeys=[],co
llectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCre
ations=[] collectionRemovals=[] collectionUpdates=[]])
The two load calls did not actually load any data from the database. It only created proxies. Hence no snapshot found in the persistence cache
2781 [main] DEBUG org.hibernate.impl.SessionImpl  - initializing proxy: [com.mod
el.Entity#1]
2781 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - attempting
 to resolve: [com.model.Entity#1]
2781 [main] DEBUG org.hibernate.event.def.DefaultLoadEventListener  - object not
 resolved in any cache: [com.model.Entity#1]
2781 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Fetc
hing entity: [com.model.Entity#1]
2781 [main] DEBUG org.hibernate.loader.Loader  - loading entity: [com.model.Enti
ty#1]
2781 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2796 [main] DEBUG org.hibernate.SQL  - 
    select
        entity0_.id as id0_0_,
        entity0_.DATA as DATA0_0_ 
    from
        Entity entity0_ 
    where
        entity0_.id=?
2937 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open ResultSet 
(open ResultSets: 0, globally: 0)
2937 [main] DEBUG org.hibernate.loader.Loader  - processing result set
2937 [main] DEBUG org.hibernate.loader.Loader  - result set row: 0
2937 [main] DEBUG org.hibernate.loader.Loader  - result row: EntityKey[com.model
.Entity#1]
2968 [main] DEBUG org.hibernate.loader.Loader  - done processing result set (1 r
ows)
2968 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to close ResultSet
 (open ResultSets: 1, globally: 1)
...
mergeCase2sdsad
When the code tried to work with the actual fields in the two objects, Hibernate searched for their presence in the persistence cache. On not finding the first object (Entity with id 1), it executed a query to fetch the first object and then added it to the cache. The same steps were repeated for the second object.The persistence cache has now loaded two objects in it.
Session after complete loading of 2 entities SessionImpl(PersistenceContext[enti
tyKeys=[EntityKey[com.model.Entity#1], EntityKey[com.model.Entity#2]],collection
Keys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[
] collectionRemovals=[] collectionUpdates=[]])
Next the code decides to evict an object.
2984 [main] DEBUG org.hibernate.event.def.DefaultEvictEventListener  - evicting 
[com.model.Entity]
Session after evicting 1 entity SessionImpl(PersistenceContext[entityKeys=[Entit
yKey[com.model.Entity#2]],collectionKeys=[]];ActionQueue[insertions=[] updates=[
] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[]
])
The persistence cache now does not hold Entity with id 1. An important thing to keep in mind here is that Entity 1 is not in the first level cache anymore. So if we need Entity with id 1 again, the session will have to issue a select call to get that particular object.
 On loading the entity with id 3(read-only):
3000 [main] DEBUG org.hibernate.loader.Loader  - done entity load
Session after loading a read-only entity SessionImpl(PersistenceContext[entityKe
ys=[EntityKey[com.model.Entity#2], EntityKey[com.model.Entity#3]],collectionKeys
=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] co
llectionRemovals=[] collectionUpdates=[]])
The call to clear is executed after this.
Session after clear SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=
[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] col
lectionRemovals=[] collectionUpdates=[]])

No comments:

Post a Comment