14

(Still a bit new to Spring)

I need to have a service method that is at the same time @Scheduled and @Transactional, so that I get to call a DAO in it.

Declarative transactions are enabled, the transaction manager is a org.springframework.orm.hibernate3.HibernateTransactionManager based on a hibernate session factory.

The service class does not implement any interface so a CGLIB proxy is used.

This setup works fine in general (methods called from the web stack i.e. Struts) but this method raises an exception when called by the scheduler.

Here are the relevant bits of code :

The service method (the class is called ClientWakeAndTerminateManager) :

@Scheduled(initialDelay = 5000, fixedRateString = "${rmi.server.threads.clientsScheduleManagement.rate}")
    @Transactional(readOnly = true)
    public void runCheck(){

        //Call a read-only DAO method (the DAO is @Autowired as a class field)

        //do some stuff with the data loaded from DB

    }

Relevant parts of my application context :

<!-- switch on the transactional infrastructure -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

    <!-- Utility class to execute transactional code where use of annotation is not possible -->
    <bean class="org.springframework.transaction.support.TransactionTemplate" id="txTemplate">
        <constructor-arg name="transactionManager" ref="transactionManager"/>
    </bean>

    <!-- Transaction manager based on Hibernate -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="hibernateSessionFactory"/>
    </bean>

Exception stack trace :

[ERROR] : Unexpected error occurred in scheduled task.
org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction; nested exception is org.hibernate.TransactionException: Transaction not successfully started
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:661)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
    at ch.unine.sitel.lis.rmi.shared.ClientWakeAndTerminateManager$$EnhancerByCGLIB$$d8be4f34.runCheck(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)
Caused by: org.hibernate.TransactionException: Transaction not successfully started
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:127)
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:657)
    ... 22 more

The stack trace seems to tell me that a transactional proxy is indeed used so I don't understand this exception. Help !

EDIT:

I tried to separate the @Transactional and the @Scheduled annotations by :

  • Create a new class/bean that contaisn the @Scheduled method
  • Inject my service to this bean
  • Removed the @Scheduled from my original method but left the @Transactional

But I still get the same exception. I also tried to put the @Transactional on my DAO method and remove it from my service method : same result.

Pierre Henry
  • 16,658
  • 22
  • 85
  • 105
  • this may happen when already commited or rolled back transaction tries to commit or rollback – wedens Aug 23 '13 at 13:19
  • can you show your dao method? – wedens Aug 23 '13 at 13:21
  • 1
    OK, my DAO method was the guilty one. I was starting to suspect it and then your comment made me look twice. So the method contained some left-over manual session commit (I am recently modernizing and "springifying" this app), so when the transactional proxy was calling commit it failed because it was commited already ! Thanks for your time. If you edit your answer to mention the DAO method I'll accept it. – Pierre Henry Aug 23 '13 at 13:34

2 Answers2

31

create separate class with method annotated with @Transactional and call this method in your @Scheduled annotated method. spring will do a call through proxy and handle @Transactional correctly.

EDIT: also look at your DAO method and make sure it not commits or rolls back transaction manually

wedens
  • 1,782
  • 14
  • 18
  • Thanks @wedens for this answer ,Its work for me.Can you explain why its not working when method is in same class. – hayat Nov 09 '15 at 07:48
  • 1
    I know, very very old post but I hate to leave the question hanging. The reason is because of the way the proxy works. The most simple answer is that it can see what is coming in and going out but not what is going on inside. This is due to the nature of a proxy. – Wes Dec 31 '15 at 00:55
  • It's been almost 10 years since that question was raised. Is there a solution in the mean time except for creating another class and calling its methods? – d00d Sep 13 '22 at 06:59
1

In my case

@EnableTransactionManagement

was not used. After I add this annotation to one of my @Configuration classes it start to work.

degr
  • 1,559
  • 1
  • 19
  • 37