Announcement Announcement Module
Collapse
No announcement yet.
Transactions not reentrant? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Transactions not reentrant?

    I have a service method, executing within a transaction, that in a roundabout way ends up calling another transactional service. All the @Transactional annotations use the default REQUIRED propagation, which is supposed to use the current transaction if exists. However, instead I get this stack trace:

    Code:
    org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.orm.hibernate3.SessionHolder@e5b230] for key [org.hibernate.impl.SessionFactoryImpl@10cdea0] bound to thread ["http-bio-8080"-exec-2]
    	at org.springframework.orm.hibernate3.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:596)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    	at $Proxy73.getSiteLabelForKey(Unknown Source)
    	at com.bc.intl.service.sitelabel.SiteLabelService.get(SiteLabelService.java:67)
    	at com.bc.intl.service.sitelabel.SiteLabelMessageSource.resolveCodeWithoutArguments(SiteLabelMessageSource.java:29)
    	at org.springframework.context.support.AbstractMessageSource.getMessageInternal(AbstractMessageSource.java:193)
    	at com.bc.intl.service.sitelabel.SiteLabelMessageSource.getMessageInternal(SiteLabelMessageSource.java:50001)
    	at org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:127)
    	at com.bc.intl.service.sitelabel.CompositeMessageSource.getMessage(CompositeMessageSource.java:41)
    	at org.springframework.context.support.MessageSourceResourceBundle.handleGetObject(MessageSourceResourceBundle.java:75)
    	at java.util.ResourceBundle.getObject(ResourceBundle.java:368)
    	at java.util.ResourceBundle.getString(ResourceBundle.java:334)
    	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.resolveParameter(ResourceBundleMessageInterpolator.java:220)
    	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.replaceVariables(ResourceBundleMessageInterpolator.java:187)
    	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateMessage(ResourceBundleMessageInterpolator.java:147)
    	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:111)
    	at org.springframework.validation.beanvalidation.LocaleContextMessageInterpolator.interpolate(LocaleContextMessageInterpolator.java:49)
    	at org.hibernate.validator.engine.ValidationContext.createConstraintViolation(ValidationContext.java:150)
    	at org.hibernate.validator.engine.ValidationContext.createConstraintViolations(ValidationContext.java:170)
    	at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:159)
    	at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:140)
    	at org.hibernate.validator.metadata.MetaConstraint.validateConstraint(MetaConstraint.java:121)
    	at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:327)
    	at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForRedefinedDefaultGroup(ValidatorImpl.java:273)
    	at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:256)
    	at org.hibernate.validator.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:210)
    	at org.hibernate.validator.engine.ValidatorImpl.validate(ValidatorImpl.java:119)
    	at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:145)
    	at com.bc.intl.service.content.author.AuthoringService.validateBatchItem(AuthoringService.java:483)
    	at com.bc.intl.service.content.author.AuthoringService.processBatchUpload(AuthoringService.java:419)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    	at $Proxy68.processBatchUpload(Unknown Source)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    	at com.bc.util.spring.interceptor.PerformanceMonitorInterceptor.invokeUnderTrace(PerformanceMonitorInterceptor.java:109)
    	at org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke(AbstractTraceInterceptor.java:110)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    	at $Proxy69.processBatchUpload(Unknown Source)
    	at com.bc.intl.service.content.author.batchupload.BatchUploadService.parseData(BatchUploadService.java:38)
    	at com.bc.intl.web.author.BatchUploaderController.upload(BatchUploaderController.java:49)
    I am using Spring 3.0.5 and Hibernate 3.5.6.

    Is there something else I must do to get PROPAGATION_REQUIRES to work correctly?

  • #2
    One more clue, debugging through it, it appears that it's because the SessionHolder is marked as "isSynchronizedWithTransaction". I'm not sure what that means exactly, but in the HibernateTransactionManager, it causes it to try to create a new Hibernate Session regardless of whether one exists already or not.

    Comment


    • #3
      Hint: Your transaction is thread bound.

      What does your transaction configuration look like?

      Comment


      • #4
        I know the transaction is thread-bound. Under propagation REQUIRED the second entry into a transactional service should join the existing transaction, but it doesn't.

        The issue is clearly seen in HibernateTransactionManager in 3.0.5.

        Starting at line 488
        Code:
        if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
        	Interceptor entityInterceptor = getEntityInterceptor();
        	Session newSession = (entityInterceptor != null ? getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession());
        	if (logger.isDebugEnabled()) {
        		logger.debug("Opened new Session [" + SessionFactoryUtils.toString(newSession) + "] for Hibernate transaction");
        	}
        	txObject.setSession(newSession);
        }
        this shows that when the transaction interceptor is activated, if there's an existing SessionHolder with isSynchronizedWithTransaction(), it will force the creation of a new Hibernate Session.

        Then, at line 580, at the end of doBegin(), we see
        Code:
        			txObject.getSessionHolder().setSynchronizedWithTransaction(true);
        which shows that isSynchronizedWithTransaction is always set to true.

        So how is PROPAGATION_REQUIRED supposed to work?

        The transaction configuration is totally standard. Both classes are annotated with @Transactional at the class level. There's a session factory and a transaction manager.
        Code:
        <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        	<property name="sessionFactory" ref="sessionFactory" />
        </bean>

        Comment


        • #5
          OK, after looking at it for quite a while, all I can say is that it does look like a bug, and I have experienced transaction management bugs with Spring.

          The odd thing is that there's no 'caused by' appended to the exception - did you remove it?
          If you trimmed the log, than the cause may shed some more light on this issue.

          In any case, it looks like you have a design cycle there. Fixing it may help.

          Comment


          • #6
            Wow, I finally figured it out. In our main app, we use a delegating wrapper (JDK Proxy) around our Hibernate Session Factory. In Spring's TransactionSynchronizationManager, it is using the SessionFactory as a key to the map that holds the SessionHolder. But sometimes it was using the actual SessionFactoryImpl, and sometimes it was using our JDK proxy instance. So it was not finding the correct SessionHolder, it thought it didn't have a transaction, and was attempting to open new ones.

            My solution was to have the JDK proxy's InvocationHandler also implement Spring's InfrastructureProxy, and for the getWrappedObject() return the delegate.

            Wow, that's a lot more than I ever wanted to know about Spring's transaction infrastructure.

            Comment

            Working...
            X