PDA

View Full Version : Exception/tx-rollback in @Transactional invoked by @Transactional method?


memelet
Jul 11th, 2006, 11:07 AM
I have an class such that one @Transactional method invokes another @Transactional method.

When the called method throws an exception the transaction is rolled back by the after throwing advice (which invokes TransactionalAspectSupport.doCloseTransactionAfter Throwing). But then when the stack unwindds to the calling method, the after advice is again invoked and the transaction attempted to rollback again -- which fails.

Is this the expected behavior? If so, doesn't this limit the usefulness of transaction propagation on @Transactional methods?

memelet
Jul 11th, 2006, 06:49 PM
Well, as one should expect, this is not a spring problem. Spring does indeed detect whether the transaction should be rolledback based on whether the info was due to a new transaction.

What is happening is that hibernate fails in to close its entity manager after setRollbackOnly has been invoked (it won't synchronize). Ok, this is all vague, I'll post details once I get a grip on this as someone else is sure to hit this issue at some point.

Costin Leau
Jul 12th, 2006, 02:34 AM
Could you post some info (i.e. stacktrace and the Hibernate version used). Being aware of such problems helps as we can advice users who run into the same issue.

memelet
Jul 12th, 2006, 09:02 AM
versions:
- spring 2.0RC1
- hibernate/annotations/entitymanager 3.2.0CR1

persistence.xml:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">

<persistence-unit name="OpenTrader" transaction-type="JTA"/>

</persistence>


Spring LCEMFB config:

<bean id="persistence.dataSource" class="org.apache.commons.dbcp.BasicDataSource" >
...
</bean>

<bean id="persistence.loadTimeWeaver" class="org.springframework.instrument.classloading.Instru mentationLoadTimeWeaver"/>

<!-- For reasons not yet known, hibernate needs this in order to find the JTA TransactionManager -->
<bean id="persistence.transactionManagerLookup" class="org.opentrader.infra.hibernate.TransactionManagerH olderLookup">
<property name="transactionManager" ref="txn.userTransactionManager"/>
</bean>

<!-- NOTE We depends-on 'persistence.transactionManagerLookup' to ensure that it the concrete bean. Otherwise hibernate gets very upset. -->
<bean id="persistence.entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityMa nagerFactoryBean"
depends-on="persistence.transactionManagerLookup">
<property name="allowRedeploymentWithSameName" value="true"/>
<property name="dataSource" ref="persistence.dataSource"/>
<property name="loadTimeWeaver" ref="persistence.loadTimeWeaver"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVen dorAdapter">
<property name="database" value="${persistence.jpa.database}"/>
<property name="showSql" value="${hibernate.show_sql}"/>
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="hibernate.ejb.use_class_enhancer">false</prop>
<prop key="hibernate.transaction.manager_lookup_class">org.opentrader.infra.hibernate.TransactionManagerH olderLookup</prop>
<prop key="hibernate.current_session_context_class">jta</prop>
</props>
</property>
</bean>


The first concern which may or may not give some clues, is that hibernate does not find the transaction manager unless I provide it with a "hibernate.transaction.manager_lookup_class":


public class TransactionManagerHolderLookup implements TransactionManagerLookup {

private static TransactionManager transactionManager;

public void setTransactionManager(TransactionManager transactionManager) {
Assert.notNull(transactionManager, "transactionManager must not be null");
TransactionManagerHolderLookup.transactionManager = transactionManager;
}

public TransactionManager getTransactionManager(Properties props) throws HibernateException {
return transactionManager;
}

public String getUserTransactionName() {
return null;
}
}


I have no idea why this mechanism is required, but hibernate fails to initialize without it.

I'm using the Geronimo transaction manager packaged with Jencks. I have had no problems with transactions when using straight hibernate (ie, pre JPA). Here is the tail end of the config for transaction related beans:


...
<bean id="txn.userTransactionManager"
class="org.jencks.factory.GeronimoTransactionManagerFacto ryBean">
<property name="transactionContextManager" ref="tnx.jencks.transactionContextManager"/>
</bean>

<bean id="txn.platformTransactionManager" lazy-init="true"
class="org.springframework.transaction.jta.JtaTransaction Manager">
<property name="userTransaction" ref="txn.userTransactionManager" />
</bean>

<bean
class="org.springframework.transaction.aspectj.Annotation TransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="txn.platformTransactionManager" />
</bean>


Ok, now for the actual code. The test that is failing is related to a class which provides a Hibernate impl of ACEGI UserDetails -- its simple so I'll just show snippets of it instead of creating a dummy example (later, if I'm not doing something stupid, I'll create a isolated repeatable test that I can submit to jira).


@Transactional
public class UserService implements UserDetailsService {

@PersistenceContext
private EntityManager em;

...

public User findUserByUsername(String username) throws UsernameNotFoundException {
try {
User user = (User)em.createNamedQuery("opentrader.security.findUserByUsername")
.setParameter("username", username)
.getSingleResult();
return user;
} catch (NoResultException e) {
throw new UsernameNotFoundException("User not found: username=" + username);
}
}

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return findUserByUsername(username);
}

}


The test is testing the failure of the method "loadUserByUsername" which simply delegates to "findUserByUsername":


public void testLoadUserByUsername_NotExists() {
try {
service.loadUserByUsername(usernames.generate());
fail("expected UsernameNotFoundException");
} catch (UsernameNotFoundException e) {
// expected
}
}


Here is the sequence:
1. the test invokes service.loadUserByUsername
2. via the txn aspect, TransactionAspectSupport.createTransactionIfNecess ary is invoked and a new transaction is created
3. service.loadUserByUsername invokes this.findUserByUsername
4. again via the txn aspect, TransactionAspectSupport.createTransactionIfNecess ary is invoked yet this time spring sees that the a transaction is already in progress and the transactionStatus has newSynchronization=false and newTransaction=false
5. findUserByUsername fails and throws an exception (as per the test setup)
6. via the txn aspect, TransactionAspectSupport.doCloseTransactionAfterTh rowing gets invoked, which leads to AbstractPlatformTransactionManager.triggerBeforeCo mpletion. This method sees that the txn status is not a new-sync, and shorts out.
7. the expection propagates to loadUserByUsername, which triggers the txn advice and leads back to AbstractPlatformTransactionManager.triggerBeforeCo mpletion. This time the status /is/ for a new-sync, which happens to be EntityManagerFactoryUtils$EntityManagerSynchroniza tion.
8. EntityManagerFactoryUtils$EntityManagerSynchroniza tion.beforeCompletion is invoked, which first calls TransactionSynchronizationManager.unbindResource(t his.entityManagerFactory) and then calls this.entityManagerHolder.getEntityManager().close( )

Ok, its this last invocation, this.entityManagerHolder.getEntityManager().close( ), where the problem lies. This method call leads to hibernate EntityManagerImpl.close()
- which invokes getSession().getTransaction().registerSynchronizat ion(...)
- which invokes CMTTransaction.registerSynchronization()
- which invokes getTransaction().registerSynchronization(sync)

This last goes into Geronimo, which throws a RollbackException with the message "Transaction is marked for rollback". All this perculates back up to AbstractPlatformTransactionManager which catchs the exception, eats it and logs an error.

So, not being a JTA guru, all I can surmize is that either some protocol is being violated by spring/hibernate/geronimo, or some part of the code is doing something in the wrong order.

This is all very complex. And when I switch back to RESOURCE_LOCAL, my database commits never happen. Buts that another story.

memelet
Jul 12th, 2006, 09:04 AM
And here is the actual stacktrace (it was allowed in the above post)


09:49:18,953 DEBUG-org.springframework.transaction.aspectj.Annotation TransactionAspect - Invoking rollback for transaction on org.opentrader.domain.security.UserService.findUse rByUsername due to throwable [org.acegisecurity.userdetails.UsernameNotFoundExce ption: User not found: username=org.opentrader.domain.security.User.usern ame1] [][main][org.springframework.transaction.aspectj.Annotation TransactionAspect]
09:51:22,859 DEBUG-org.springframework.transaction.aspectj.Annotation TransactionAspect - Invoking rollback for transaction on org.opentrader.domain.security.UserService.loadUse rByUsername due to throwable [org.acegisecurity.userdetails.UsernameNotFoundExce ption: User not found: username=org.opentrader.domain.security.User.usern ame1] [][main][org.springframework.transaction.aspectj.Annotation TransactionAspect]
10:03:14,593 ERROR-org.springframework.transaction.jta.JtaTransaction Manager - TransactionSynchronization.beforeCompletion threw exception [][main][org.springframework.transaction.jta.JtaTransaction Manager]
org.hibernate.TransactionException: Could not register synchronization
at org.hibernate.transaction.CMTTransaction.registerS ynchronization(CMTTransaction.java:159)
at org.hibernate.ejb.EntityManagerImpl.close(EntityMa nagerImpl.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknow n Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.orm.jpa.ExtendedEntityManagerC reator$ExtendedEntityManagerInvocationHandler.invo ke(ExtendedEntityManagerCreator.java:210)
at $Proxy35.close(Unknown Source)
at org.springframework.orm.jpa.EntityManagerFactoryUt ils$EntityManagerSynchronization.beforeCompletion( EntityManagerFactoryUtils.java:244)
at org.springframework.transaction.support.AbstractPl atformTransactionManager.triggerBeforeCompletion(A bstractPlatformTransactionManager.java:717)
at org.springframework.transaction.support.AbstractPl atformTransactionManager.processRollback(AbstractP latformTransactionManager.java:611)
at org.springframework.transaction.support.AbstractPl atformTransactionManager.rollback(AbstractPlatform TransactionManager.java:599)
at org.springframework.transaction.interceptor.Transa ctionAspectSupport.doCloseTransactionAfterThrowing (TransactionAspectSupport.java:293)
at org.springframework.transaction.aspectj.AbstractTr ansactionAspect.ajc$afterThrowing$org_springframew ork_transaction_aspectj_AbstractTransactionAspect$ 2$2a73e96c(AbstractTransactionAspect.aj:72)
at org.opentrader.domain.security.UserService.loadUse rByUsername(UserService.java:53)
at org.opentrader.domain.security.UserServiceTest.tes tLoadUserByUsername_NotExists(UserServiceTest.java :64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknow n Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at junit.framework.TestCase.runTest(TestCase.java:164 )
at junit.framework.TestCase.runBare(TestCase.java:130 )
at org.springframework.test.ConditionalTestCase.runBa re(ConditionalTestCase.java:69)
at org.opentrader.itest.IntegrationAspectjDependencyI njectionSpringContextTestCase.runBare(IntegrationA spectjDependencyInjectionSpringContextTestCase.jav a:105)
at org.opentrader.itest.IntegrationAspectjDependencyI njectionSpringContextTestCase.runBare(IntegrationA spectjDependencyInjectionSpringContextTestCase.jav a:184)
at junit.framework.TestResult$1.protect(TestResult.ja va:110)
at junit.framework.TestResult.runProtected(TestResult .java:128)
at junit.framework.TestResult.run(TestResult.java:113 )
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:2 28)
at junit.framework.TestSuite.run(TestSuite.java:223)
at org.junit.internal.runners.OldTestClassRunner.run( OldTestClassRunner.java:35)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestR eference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecutio n.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRu nner.main(RemoteTestRunner.java:196)
Caused by: javax.transaction.RollbackException: Transaction is marked for rollback
at org.apache.geronimo.transaction.manager.Transactio nImpl.registerSynchronization(TransactionImpl.java :118)
at org.apache.geronimo.transaction.context.Inheritabl eTransactionContext.registerSynchronization(Inheri tableTransactionContext.java:111)
at org.apache.geronimo.transaction.context.GeronimoTr ansactionDelegate.registerSynchronization(Geronimo TransactionDelegate.java:67)
at org.hibernate.transaction.CMTTransaction.registerS ynchronization(CMTTransaction.java:156)
... 37 more

Costin Leau
Jul 13th, 2006, 03:30 AM
I'll divide your reply into pieces and try to address each one separately:

versions:
- spring 2.0RC1
- hibernate/annotations/entitymanager 3.2.0CR1
I would recommend to upgrade to Spring 2.0 RC2 (there are some small changes) and to hibernate 3.2.0 CR3 - from what I've seen there are quite a number of significant changes between hibernate releases and I was 'bitten' several times when doing upgrade. I think your problems come from the fact that you are using mixed JTA and non-JTA resources.

LocalContainerEntityManagerFactoryBean
should be used when local resources are used - i.e. local transaction managers and local datasources. If one uses JTA then most of the configuration will happen inside the application container (including configuration of the datasource).

The datasource you are passing (which by the way doesn't support two-phase commit) will be registered as non-jta datasource and not as a JTA datasource which means that will be driving a jta transaction w/o a jta datasource which is definitely a 'bad' situation.

If you are not using a LoadTimeWeaver then simply don't specify one - the one you mentioned uses an agent which really is not required in this situation as Hibernate doesn't uses instrumentation.
If you really want to use JTA then yes, you need to specify a lookup property to Hibernate but then again - you are better off using LocalEntityManagerFactoryBean.


The first concern which may or may not give some clues, is that hibernate does not find the transaction manager unless I provide it with a "hibernate.transaction.manager_lookup_class":
I wasn't aware of that - I'll check this requirement and get back to you - it might be something added in the latests HB versions or maybe some misconfiguration.

This is all very complex. And when I switch back to RESOURCE_LOCAL, my database commits never happen. Buts that another story.
If you want to use RESOURCE_LOCAL which makes sense in your case as you are using only one datasource, you have to get rid of the JTA.

My advice is to first give it a try using LocalEntityManagerFactoryBean - basically letting the container do most of the stuff including driving transactions. You are better off using straight JTA without JCA (i.e. Jencks) since as the Hibernate documentation states - the JCA support is considered experimental.
I'm not sure what application server you are using but HB comes with plenty of lookup classes for various transaction managers - use those classes to get a hold of the tx manager.

After experimenting with this scenario, I would definitely try to switch from JTA to local resource management - if you are using only one datasource then there is really no need for JTA.
And once again - if you are using JTA TM you also need a proper JTA datasource (including support from the underlying jdbc driver) - see geronimo docs for more info - commons dbcp is not one of them.

Costin Leau
Jul 13th, 2006, 06:15 AM
I've looked at the sources and the docs and it seems that when JTA is used Hibernate EntityManager indeed requires these properties to be set (which in return are passed to hibernate). Hibernate supports already provides a wrapper class so you don't have to have redundant configuration when using JTA.
I'm not sure at this point what the solution since with JPA, the transaction manager is aware of the entityManagerFactory and not the other way around.

memelet
Jul 13th, 2006, 07:49 PM
Wow, thanks Costin.

While the example I give does not require JTA and uses a non-JTA datasource, we will indeed require two phase commit for some parts of our system. Both to commit against two databases (one is third party so I don't have any option there) and for db+jms (in this case we could code around non-XA, as we have done with other systems). I didn't think using the non-JTA datasource would cause problems with this simple example, but I did see that it got registered as non-JTA. We will probably be using the sybase driver, which does support XA. Any recommendations an XA datasource?

Good to know on the weaver. I'll remove that when using Hibernate.

As for the lookup class, it didn't look like any of the hibernate provided ones would find the TM defined in the spring context.

We don't plan on deploying to an app server if we can help it. Of if we do, it won't be for all deployments. So really we need to create the JTA TM directly via spring as well as the datasource. The use of jencks is really just to get a packaging of the geronimo transaction manager and some factory beans. Are you saying that even for this use jencks is introducting JCA to connect in the transaction manager? (We will probably swap over to arjuna once I get some time to learn how to configure it.)

I do have a configuration that uses RESOURCE_LOCAL and does not exhibit the problem I have described. But for some reason the database changes never get comitted. I have not even looked into why yet, but surely its user error.

The one thing I am still a bit confused about is the distinction/purpose betwen LocalContainerEMFB vs just LocalEMFB. Since we want the full power of JPA with all the Entity lifecycle hooks (eg, @PrePersist) I was under the impression that the container version was required.

I'll dig deeper to get a better understanding betweed the EMFBs.

thanks!!

Costin Leau
Jul 14th, 2006, 02:56 AM
Any recommendations an XA datasource?
Usually the application server (if you are using one) provides such a thing. As for the connection pooling - I think c3p0 is a better choice - I haven't worked with XA lately but a proper connection pooling should work okay if the underlying driver properly supports the XA spec.
As for the lookup class, it didn't look like any of the hibernate provided ones would find the TM defined in the spring context.
No they don't. However, you could publish your TM inside a JNDI and then use a lookup class to retrieve it. I'm thinking of finding an easy solution to the problem though this issue seems to be so far particular to Hibernate.


We don't plan on deploying to an app server if we can help it. Of if we do, it won't be for all deployments. So really we need to create the JTA TM directly via spring as well as the datasource. The use of jencks is really just to get a packaging of the geronimo transaction manager and some factory beans. Are you saying that even for this use jencks is introducting JCA to connect in the transaction manager? (We will probably swap over to arjuna once I get some time to learn how to configure it.)

Oh, okay. I haven't use Jencks that much but from my experience with it - its purpose is to provide a JCA connector. Have you tried configuring the geronimo TM by yourself - it should work standalone without any need of jencks.

I do have a configuration that uses RESOURCE_LOCAL and does not exhibit the problem I have described. But for some reason the database changes never get comitted. I have not even looked into why yet, but surely its user error.

First try simplifying your configuration and add a lot of logging to see what seems to be the problem. feel free to post on the forum if you need help.

The one thing I am still a bit confused about is the distinction/purpose betwen LocalContainerEMFB vs just LocalEMFB. Since we want the full power of JPA with all the Entity lifecycle hooks (eg, @PrePersist) I was under the impression that the container version was required.


LocalEMFB will delegate everything to the underlying JPA container. This is let's a very non-intrusive case. The main advantages of LocalContainerEMFB are the weaving and loading process through Spring's ResourceLoader as well as setting the datasource.
However, Spring is not an application container and that's why the datasource setting happens only at non-jta level. It's the same as with transaction managers - Spring covers the (uncovered) local case - for JTA usage one has already application servers (mainly) to deal with the situation.
As you are not using any of the advantages of LocalContainerEMFB I would suggest to switch to LocalEMFB. Let me know if you make any progress!

memelet
Jul 14th, 2006, 06:52 AM
As you are not using any of the advantages of LocalContainerEMFB I would suggest to switch to LocalEMFB. Let me know if you make any progress!

One desired goal is to do /all/ configuration in the spring config file -- as opposed to putting some info in the persistence.xml. Our test and deployment environment is very dynamic, and even the loss of PropertyConfigurators would be kill us. I would really really hate to have to drop out to ant just to perform substitutions.

But I will take a closer look at the LocalEMFB and report back how things work out. Thanks again.

memelet
Jul 14th, 2006, 07:33 AM
There is no way I can use LocalEMFB, as I am performing the magic we discussed in another thread (http://forum.springframework.org/showthread.php?t=26365) -- that is I am overrding PersistenceUnitReader which scans the whole classpath to detect "jars" that contribute entities and adds them the PersistenceUnit's set of jars. (Remember, I want a single PersistenceContext that can be "extended" by components that add entities to the context. The title of that thread asked for exactly what was provided, but the body of the thread headed in a slightly different direction.)

BTW, in the RC2 release, the changes to LocalContainerEMFB do not seem allow this at all. We need only /one/ PersistenceContext with a single name, but which includes entities from the entire classpath. I don't want to have to explicity specify the classpath, but rather have the PersistenceUnitReader scan the classpath for classpath roots. In our system, components/plugins can contribute entities to the single appliction-level context (adding relationships and even extending the entities via aspectj introductions). Simply creating a bunch of contexts will not work for our plugin model.

What I need from LocalContainerEMFB is:

- for parsePersistenceUnitReader() /not/ to create a concrete PersistenceUnitReader, but rather to invoke a hook methods - eg, createPersistenceUnitReader - so I can provide my own reader.

- PersistenceUnitReader to be made a public class

- SpringPersistenceUnitInfo to be made a public class

Right now I am working with modified sources -- not a happy position. Should I move this request over to Jira? If this fully internal change doesn't make into 2.0, we're gonna be working with modfied sources for some time.

Costin Leau
Jul 14th, 2006, 09:53 AM
I haven't forgotten the thread - however, the classes you mentioned are, as you noticed, internal to Spring. Opening them requires some though (i.e. maybe there is another solution). Please raise an issue on JIRA - this way it's going to be easily tracked by both you and the Spring team.
As for the PersistenceContext - I'm not sure to what degree adding entities dynamically to the context is legal. I understand your case but still, the spec has to be followed.

memelet
Jul 14th, 2006, 11:55 AM
As for the PersistenceContext - I'm not sure to what degree adding entities dynamically to the context is legal. I understand your case but still, the spec has to be followed.

Do you consider what I am doing adding entities dynamically? I could have just easily added entiries the persistence.xml indicating the mutliple jars (or in test environment, classpath roots which just happens to work with hibernate). All I am doing is, instead of hard-coding the jars in persistence.xml, I am collecting them at runtime. After that, the they never change.

For pratical purposes, I could have an ant task that does the work of finding the classpath roots, inserts the entires in persistence.xml, which then would get parsed into the PersistenceUnit. So as far as the JPA providor is concerned, it could tell the difference.

memelet
Jul 14th, 2006, 12:02 PM
FYI: http://opensource.atlassian.com/projects/spring/browse/SPR-2301

Costin Leau
Jul 14th, 2006, 12:07 PM
Do you consider what I am doing adding entities dynamically? I could have just easily added entiries the persistence.xml indicating the mutliple jars (or in test environment, classpath roots which just happens to work with hibernate). All I am doing is, instead of hard-coding the jars in persistence.xml, I am collecting them at runtime. After that, the they never change.
I understood but again such functionality is not specified inside the specs AFAIK. The ant task sounds like a good idea regarding compatibility - could you try that as a simple test with the LocalEMFB?
Once Juergen comes from his well deserved vacation, I'll discuss this requirement with him and ping you back. I'd like the JPA support to be as flexible as easy but 'legal' in regards to the spec - right now I would say we could open up the API (make the classes protected) but I'm not sure if we are going to support persistenceUnit concatenation - it's just too dangerous.