Manual transactional demarcation in spring and hibernate
Often you are forced to write code where the @Transactional in spring simply does not cut it. You want to execute certain pieces of code in different transactions all with a different propagation and isolation levels. The manual transactional demarcation in spring is stupidly simple after some upfront configuration and works uniformly by enrolling in what ever the enclosing transaction manager is. In case you are running in a JEE server it will tie in with the JTA transaction manager otherwise hibernate transaction manager.
In case you want to integrate with the JTA transaction manager and want the spring @Transactional to work simply put this int he application context:
<!-- configure JTA transaction manager -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="allowCustomIsolationLevels" value="true" />
</bean>
If you are running inside a plain servlet container like tomcat you can configure the hibernate transactions, vanilla JDBC transactions and etc. to use the hibernate transaction manager like this:
<!-- use the hibernate transaction manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
<property name="dataSource" ref="dataSource" />
</bean>
Notice that the session factory and the data source properties are both set. This then allows jdbcTemplate etc. to all participate in the same transactions as a hibernate call to save() and load().
Once configured its great any spring managed bean with the following annotations on methods will work flawlessly.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
This is where the problem starts. Even though you get very granular in the transactions here - calling methods within a service implementation will not be transactionally aware because the demarcation, flushing, commits etc. happen by generating a proxy around the class and internal calls are simply by passed.
In this case you can make use of the spring TransactionTemplate and the PlatformTransactionManager class.
Using it is quite simple - inject the platformTransactionManager into a bean:
<!-- use the hibernate transaction manager -->
<bean id="xyzService" class="XYZServiceImpl">
<property name="platformTransactionManager" ref="transactionManager" />
</bean>
and then directly use transaction templates by specifying custom isolation and propagation levels like this:
public class XYZServiceImpl implements XyzService {
PlatformTransactionManager platformTransactionManager;
public void doService() {
TransactionTemplate template = new TransactionTemplate(platformTransactionManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
//annon. inner class
template.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
//your business logic here
}
});
}
public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
this.platformTransactionManager = platformTransactionManager;
}
}
The cool thing is that you could have parts of the same method execute in a different transactions - looking at different isolation levels. No more complex XA/JTA code, hibernate sessions will flush, transactions will commit/rollback at the demarcations you expect - spring makes it too easy.
