This post is part 5 in a 9-part series on Envers, Spring, and JTA. The next post is beforeCompletion, I need to figure this out, and the previous post is The versioning isn’t happening, part 4: Registering with the wrong transaction?
The time had come to ask my questions at the Spring Data Access forum. Following is a copy of that forum post (Whew, does it take work to try to ask a question well!) :
Sychronization hook not called when using JTA txmgr
We’re using Spring 2.5.5 on JBoss 4.2.2.GA, along with JBoss envers version 1.1.0.GA. (Envers uses Hibernate’s onPostInsert() hook to register a javax.transaction.Synchronization callback that’s called during transaction commit, to write audit records about records that were created during that transaction.)When we use Spring’s HibernateTransactionManager and write a record, the registered envers javax.transaction.Synchronization.beforeCompletion () event handler fires as expected, but when we use Spring’s jta-transaction-manager, the registered envers beforeCompletion() event handler does not fire. In this post, after sketching our configuration, I discuss what I see working the same and different between the HibernateTransactionManager and jta-transaction-manager scenarios, and close by pointing out where I think the problem might be in Spring.
We’ve configured the Hibernate sessionFactory bean to hook it up to envers’ VersionsEventListener (which implements org.hibernate.event.PostInsertEventListener):
<bean id="versionsEventListener" class="org.jboss.envers.event.VersionsEventListener" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> ... <property name="eventListeners"> <map> <entry key="post-insert"> <ref local="versionsEventListener" /> </entry> ... </map> </property> ... </bean>
With this configuration, every time a new row is inserted into the main table,
- org.jboss.envers.event.VersionsEventListener.onPos tInsert(PostInsertEvent event) is called
- onPostInsert() passes event.getSession() to org.jboss.envers.synchronization.VersionsSyncManag er.get(EventSource)
- VersionsSyncManager.get() then calls getTransaction() (which returns a JDBCTransaction) on the EventSource
- Finally, VersionsSyncManager.get() calls registerSynchronization() on this transaction to register a javax.transaction.Synchronization object whose beforeCompletion() method should be called during the transaction commit.
It is the code in this beforeCompletion() method that does the writing of the versioning records.
HibernateTransactionManager vs. JtaTransactionManager
When I set up my .war project’s Spring beans to use the Hibernate transaction manager:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
then the VersionsEventListener.onPostInsert() call happens as I have descibed, and later the registered beforeCompletion() hook is called and envers writes my audit records to the database. But when I remove the above bean and use the JTA transaction manager:
the VersionsEventListener.onPostInsert() call still happens and I do see registerSynchronization() being called; but this time the envers beforeCompletion() hook never gets called (and thus the audit records are not written to the database).
Registering with a JDBCTransaction, Committing a UserTransaction?
That’s the main symptom. I’ll now speculate on the cause… : )
When I’m using the HibernateTransactionManager, I notice that its doCommit() method makes this call:
Here, getTransaction() returns an org.hibernate.transaction.JDBCTransaction (the same one that envers called registerSynchronization() on earlier). As part of this commit(), the JDBCTransaction calls its notifyLocalSynchsBeforeTransactionCompletion() method, which calls the javax.transaction.Synchonization.beforeCompletion( ) registered by envers. So I see how the hook works on the HibernateTransactionManager side. On the JtaTransactionManager side though, org.springframework.transaction.jta.JtaTransaction Manager.doCommit() makes this call:
Here, getUserTransaction() returns the javax.transaction.UserTransaction, and then we call commit() on that. This commit() call does not result in the envers javax.transaction.Synchronization.beforeCompletion () being called. Something I notice here is that envers’ onPostInsert() method caused registerSynchronization() to be called on a JDBCTransaction object, not the UserTransaction object. This JDBCTransaction object appears to be unused for the purposes of committing.
Why I think it might be an issue in Spring
Envers’ VersionsEventListener.onPostInsert(PostInsertEvent event) method receives an org.hibernate.event.PostInsertEvent, and it is from this event that envers eventually calls (effectively):
Envers is registering its Synchronization object with the transaction given to it by the PostInsertEvent — I don’t immediately see how it could do any differently (though I’m far from an expert at these things)…Meanwhile, I don’t see where Spring is informing the UserTransaction about these registered synchronizations, and I don’t see Spring taking care of them itself either. It seems to my uneducated eyes that Spring should be doing one or the other of these, so that the registered callbacks aren’t just lost. Is there something I’m missing?
Thank you for your patience with this long post!