Search This Blog

Saturday 29 September 2012

Managing Transactions the Spring way

In the previous post we saw how Spring managed caching via proxies. It follows a similar route to achieve transaction management in a very transparent way. Spring supports programmatic and declarative transactions. Declarative uses aspects and therefore proxies and is therefore achieved in a very transparent manner.
Transaction management whether via code or declarative advises involves the use of a transaction manager. Spring on its own hasn't created any transaction framework. It merely integrates with existing transaction support provided by a host of database integration technologies like JDBC, Hibernate, JDO, JPA and many more. For each of these varying technologies Spring has defined a Transaction Manager.
I decided to test transaction management for Hibernate. The first step is to create the Transaction Manager.
<bean id="txManager"
    class="org.springframework.orm.hibernate3.HibernateTransactionManager">        
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
The sessionFactory bean is a LocalSessionFactoryBean as defined in our hibernate post. As can be seen above the TransactionManager takes a reference to the sessionFactory. This allows the manager to get access to the current session and therefore the transaction.
I decided to add transactional capabilities to my previous test class.
public class HibProxyClient {
    @Autowired
    private IPersonDAO personDAO;
       
    public void testPersonLifecycle() {
        Person person = new Person();
        person.setName("Raj");
        person.setAge(26);
        personDAO.save(person);
        Long personId =  person.getId();
        System.out.println("Person was saved with id " + personId);
        person.setName("Raja");
        personDAO.updatePerson(person);
        System.out.println("Person with id " + personId + " was updated");        
        person = personDAO.getPersonById(personId);
        System.out.println("Person with id " + personId + " is " 
               + person.getName() + " and age is " + person.getAge());
        personDAO.delete(person);
        System.out.println("person with id " + personId 
               + " has been deleted successfully");
    }
    
    public void testPersonForRollback() {
        Person person = new Person();
        person.setName("Rim");
        person.setAge(21);
        personDAO.save(person);
        Long personId =  person.getId();
        System.out.println("Person was saved with id " + personId);
        System.out.println("***** NOW CAUSING ROLLBACK !! *****");
        throw new RuntimeException();
    }    
}
The configurations are made in the XML file:
<bean id="hibProxyClient"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="txManager" />
    <property name="target" ref="hibProxyClientTarget" />        
    <property name="transactionAttributes">
        <props>
            <prop key="test*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>
<!-- The actual bean -->
<bean id ="hibProxyClientTarget" class ="com.test.HibProxyClient">        
</bean>
As can be seen above we defined two beans - the hibProxyClient which is a proxy to manage transactional behavior and hibProxyClientTarget - the actual POJO, the one that needs transaction support.The proxy involves the following properties:
  • a reference to the Transaction Manager
  • a reference to the target bean
  • the details of the methods being proxied. In our case we need all methods which begin with "test" to be run within a transactional boundary.
public static void main(String[] args) {
        final ApplicationContext applicationContext =  
                   new ClassPathXmlApplicationContext("spring-transactional.xml");
        final HibProxyClient hibClient =  (HibProxyClient) 
                    applicationContext.getBean("hibProxyClient");
        hibClient.testPersonLifecycle();
        hibClient.testPersonForRollback();        
}
In the above test code, we loaded the proxy and not the actual class. All calls therefore are directed through the proxy.
The logs of the execution are as below:
5766 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  
- Finished creating instance of bean hibProxyClientTarget
5782 [main] DEBUG org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource  
- Adding transactional method [test*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
The Spring container created the proxy and also loads the details of the tranactional settings. For proxy creation, spring used CGLIB library:
5813 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Creating CGLIB2 proxy: target source is SingletonTargetSource 
for target object [com.test.HibProxyClient@155d3a3]
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Unable to apply any optimisations to advised method: 
public void com.test.HibProxyClient.testPersonLifecycle()
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Unable to apply any optimisations to advised method: 
public void com.test.HibProxyClient.testPersonForRollback()
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Found 'hashCode' method: public native int java.lang.Object.hashCode()
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Found finalize() method - using NO_OVERRIDE
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Unable to apply any optimisations to advised method: protected 
native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Found 'equals' method: public boolean java.lang.Object.equals(java.lang.Object)
6016 [main] DEBUG org.springframework.aop.framework.Cglib2AopProxy  
- Unable to apply any optimisations to advised method: 
public java.lang.String java.lang.Object.toString()
When we called the testPersonLifecycle() method:
6110 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Creating new transaction with name [com.test.HibProxyClient.testPersonLifecycle]
: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
6188 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Opened new Session [org.hibernate.impl.SessionImpl@cbd8dc] for Hibernate transaction
6188 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@cbd8dc]
6235 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Exposing Hibernate transaction as JDBC transaction 
[com.mchange.v2.c3p0.impl.NewProxyConnection@13a34af]
...
6250 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager  
- Initializing transaction synchronization
6250 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor  
- Getting transaction for [com.test.HibProxyClient.testPersonLifecycle]
On method completion:
6719 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor  
- Completing transaction for [com.test.HibProxyClient.testPersonLifecycle]
6719 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering beforeCommit synchronization
6735 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering beforeCompletion synchronization
6735 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Initiating transaction commit
6735 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Committing Hibernate transaction on Session [org.hibernate.impl.SessionImpl@cbd8dc]
6750 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering afterCommit synchronization
6750 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering afterCompletion synchronization
6750 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager  
- Clearing transaction synchronization
After this I called the testPersonForRollback() method:
5641 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Creating new transaction with name [com.test.HibProxyClient.testPersonForRollback]
: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
...
5875 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor  
- Getting transaction for [com.test.HibProxyClient.testPersonForRollback]
...
5938 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager  
- Retrieved value [org.springframework.orm.hibernate3.SessionHolder@13a34af] 
for key [org.hibernate.impl.SessionFactoryImpl@135f44e] bound to thread [main]
The code as can be seen throws an exception:
Exception in thread "main" java.lang.RuntimeException
    at com.test.HibProxyClient.testPersonForRollback(HibProxyClient.java:46)
    at com.test.HibProxyClient$$FastClassByCGLIB$$68c50ea7.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation
.invokeJoinpoint(Cglib2AopProxy.java:688)
Spring by default rollbacks on runtime exceptions. So the logs indicate:
Person was saved with id 12
***** NOW CAUSING ROLLBACK !! *****
6047 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor  
- Completing transaction for [com.test.HibProxyClient.testPersonForRollback] 
after exception: java.lang.RuntimeException
6047 [main] DEBUG org.springframework.transaction
.interceptor.RuleBasedTransactionAttribute  
- Applying rules to determine whether transaction 
should rollback on java.lang.RuntimeException
6047 [main] DEBUG org.springframework.transaction
.interceptor.RuleBasedTransactionAttribute  
- Winning rollback rule is: null
6250 [main] DEBUG org.springframework.transaction
.interceptor.RuleBasedTransactionAttribute  
- No relevant rollback rule found: applying default rules
6250 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering beforeCompletion synchronization
6250 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Initiating transaction rollback
6250 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Rolling back Hibernate transaction on Session  
[org.hibernate.impl.SessionImpl@20807c]
6266 [main] DEBUG org.springframework.orm.hibernate3.HibernateTransactionManager  
- Triggering afterCompletion synchronization
6266 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager  
- Clearing transaction synchronization

No comments:

Post a Comment