Search This Blog

Monday, 25 June 2012

Stateless Session

In an earlier post I used the ScrollableResults object along with a session to batch multiple operations. In this case I had to manually flush and clear the session after every n SQL operations to prevent my persistence cache from having a memory overflow.
Hibernate also offers an alternative type of Session where it counts on the user to mange the SQL updates on his own.
In this type of hibernate session, there is no concept of the persistence context. So hibernate is not capable of detecting any automatic changes (dirty-checking) and automatically scheduling updates. As it does not have a cache of objects loaded from the database, the same SQL query fired in one session could result in two different objects in java memory.  
All the advantages of the Session Interface like delayed write, first level cache are lost. This interface is known as the StatelessSession
Consider the below example. It is very similar to the one involving Session (except that it doesn't use the Session anymore).
static void testEntityBatchUpdate() {
    final String query = "from Entity where id < :id";
    StatelessSession session = sessionFactory.openStatelessSession();
    System.out.println("new StatelessSession : " + session);
    Transaction transaction = null;
    ScrollableResults allEntities = null;
    transaction = session.beginTransaction();
        
    allEntities = session.createQuery(query).setLong("id", 7).scroll();
    while(allEntities.next()) {
        Entity entity = (Entity) allEntities.get(0);//This object is in detached state
        System.out.println("StatelessSession after an entity fetch : " + session);            
        System.out.println("Updating entity with id  - "
                + entity.getId() + " current name is " + entity.getName());
        //Detached object updated
        entity.setName("Updated new Name " + entity.getId());
        session.update(entity);//Will not make object persistent again 
            //- instead fires an SQL update immediately 
    }
    transaction.commit();
    session.close();
}
The Stateless session directly executes statements on the database based on the methods we call. It is very much like JDBC. As the objects are no longer updated based on dirty-checks, we need to explicitly call the update method. The object state that we learned earlier also does not apply to these objects. Whereas in case of a session our modified objects would have been in the persistent state, here the objects returned from the SQL select query are all in the detached state.(Hence no dirty checking and automatic save.) On calling the update method the object is explicitly updated to the database and then becomes detached again.
The logs below indicate the absence of any persistence cache in this session. Also the queries are fired immediately  when the update calls are fired.
3063 [main] DEBUG org.hibernate.SQL  - 
    select
        entity0_.ID as ID0_,
        entity0_.NAME as NAME0_ 
    from
        ENTITY entity0_ 
    where
        entity0_.ID<?
StatelessSession after an entity fetch : org.hibernate.impl.StatelessSessionImpl
@4c4975
3156 [main] DEBUG org.hibernate.engine.TwoPhaseLoad  - done materializing entity
 [com.batch.stateless.Entity#1]
StatelessSession after an entity fetch : org.hibernate.impl.StatelessSessionImpl
@4c4975
Updating entity with id  - 1 current name is wew
    update
        ENTITY 
    set
        NAME=? 
    where
        ID=?
...
StatelessSession after an entity fetch : org.hibernate.impl.StatelessSessionImpl
@4c4975
...
Updating entity with id  - 2 current name is wew
...
    update
        ENTITY 
    set
        NAME=? 
    where
        ID=?
As we are working with detached objects would I be able to navigate the object graph?  
How would this behaviour affect cascade relations ?
I extended my entity class and added a many-to-one-relation in the sub-class to another very simple entity.
public class ComplexEntity extends Entity {
    private Master master;
//setter-getters
}
public class Master {    
    private Long id;
    private String data;
//setter-getters
}
The hbm file for ComplexEntity includes the mapping:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.batch.stateless">
    <class name="ComplexEntity" table="COMPLEX_ENTITY">
        <id name="id" column="ID" type="long">
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>
        <many-to-one name="master" class="Master" foreign-key="COMPLEX_ENTITY_FK1">
            <column name="MASTER_ID"></column>
        </many-to-one>
    </class>
</hibernate-mapping>
I then tried to navigate the object graph to read the fields via the Master association.
static void testComplexEntityBatchUpdate() {
    final String query = "from ComplexEntity where id < :id";
    StatelessSession session = sessionFactory.openStatelessSession();
    System.out.println("new StatelessSession : " + session);
    Transaction transaction = null;
    ScrollableResults allEntities = null;
    transaction = session.beginTransaction();
        
    allEntities = session.createQuery(query).setLong("id", 3).scroll();
    while(allEntities.next()) {
        ComplexEntity cEntity = (ComplexEntity) allEntities.get(0);
        System.out.println("StatelessSession after an entity fetch : " + session);            
        System.out.println("Updating entity with id  - "
            + cEntity.getId() + " current name is " + cEntity.getName()
            + " and Master data is " + cEntity.getMaster().getData());

        cEntity.setName("Updated new Name " + cEntity.getId());
        session.update(cEntity);
    }
    transaction.commit();
    session.close();
}
On executing the code:
2766 [main] DEBUG org.hibernate.SQL  - 
    select
        complexent0_.ID as ID0_,
        complexent0_.NAME as NAME0_,
        complexent0_.MASTER_ID as MASTER3_0_ 
    from
        COMPLEX_ENTITY complexent0_ 
    where
        complexent0_.ID<?
...
2813 [main] DEBUG org.hibernate.engine.TwoPhaseLoad  - done materializing entity
 [com.batch.stateless.ComplexEntity#1]
2813 [main] DEBUG org.hibernate.engine.StatefulPersistenceContext  - initializin
g non-lazy collections
StatelessSession after an entity fetch : org.hibernate.impl.StatelessSessionImpl
@1315d34
...
Exception in thread "main" org.hibernate.SessionException: proxies cannot be fet
ched by a stateless session
    at org.hibernate.impl.StatelessSessionImpl.immediateLoad(StatelessSessionImpl.j
ava:220)
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializ
er.java:66)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyIn
itializer.java:111)
    at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitiali
zer.java:150)
    at com.batch.stateless.Master$$EnhancerByCGLIB$$2efcc8d1.getData(<generated>)
    at com.batch.stateless.TestUpdate.testComplexEntityBatchUpdate(TestUpdate.java:
58)
    at com.batch.stateless.TestUpdate.main(TestUpdate.java:18)
Being in the detached state Hibernate is unable to navigate the association, resulting in an exception. To make this work we would need to remove the lazy loading for this relation.
For cascade operations, modifications made to collections would be ignored (unless it was a collection of entities) Also any interceptors defined would be bypassed by the stateless-session.
Considering all of the above, I would prefer not to use this API, instead sticking to manually flushing and clearing the session for batch updates.

2 comments:

  1. When you are using hibernate stateless session, then hibernate does not use any cache( first level, or second level) even does not maintain transaction , no proxy object. Stateless session is useful when you have thousands of records to fetch and you have very less memory which causes out of memory error.

    ReplyDelete