0

I'm using Spring 4.3.8.RELEASE with Hibernate 5.1. I have a thread pool set up in my context

<bean id="myprojectThreadFactory" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
    <constructor-arg value="myproject-"/>
</bean>
<bean id="myprojectTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="threadFactory" ref="myprojectThreadFactory"/>
    <property name="corePoolSize" value="${myproject.core.thread.pool.size}" />
    <property name="maxPoolSize" value="${myproject.max.thread.pool.size}" />
</bean>

But how do I create a transaction within one of my task threads, which is outside of my main thread? I'm trying this

        // Cue up threads to execute
        for (final Organization org : allOrgs)
        {
            m_threadExecutor.execute(new Thread(new Runnable(){
                @Override
                @Transactional(propagation=Propagation.REQUIRES_NEW)
                public void run()
                {
                    System.out.println("started.");
                    processData(org.getId());
                    System.out.println("finished.");
                }
            }));

but this is resulting in the below exception

Exception in thread "myproject-1" Exception in thread "myproject-2" org.hibernate.SessionException: Session is closed!
    at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:132)
    at org.hibernate.internal.SessionImpl.setCacheMode(SessionImpl.java:1511)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:1109)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:1033)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
    at com.sun.proxy.$Proxy68.find(Unknown Source)
    at org.mainco.subco.core.repo.GenericDao.find(GenericDao.java:123)
    at org.mainco.subco.organization.repo.OrganizationDaoImpl.find(OrganizationDaoImpl.java:61)
    at org.mainco.subco.organization.service.OrganizationServiceImpl.getDescendantOrganizations(OrganizationServiceImpl.java:283)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy75.getDescendantOrganizations(Unknown Source)
    at org.mainco.subco.myproject.repo.myprojectClassDaoImpl.findBymyprojectOrg(myprojectClassDaoImpl.java:89)
    at org.mainco.subco.myproject.service.myprojectClassServiceImpl.find(myprojectClassServiceImpl.java:300)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy91.find(Unknown Source)
    at org.mainco.subco.myproject.quartz.ImportClassesWorker.preProcessData(ImportClassesWorker.java:56)
    at org.mainco.subco.myproject.quartz.AbstractImportDataWorker.processData(AbstractImportDataWorker.java:126)
    at org.mainco.subco.myproject.quartz.AbstractImportDataWorker.access$0(AbstractImportDataWorker.java:123)
    at org.mainco.subco.myproject.quartz.AbstractImportDataWorker$1.run(AbstractImportDataWorker.java:110)
    at java.lang.Thread.run(Thread.java:745)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Dave
  • 15,639
  • 133
  • 442
  • 830

2 Answers2

4

Use TransactionTemplate for the logic. Something like this

final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
   taskExecutor.execute(new Runnable() {
        @Override
        public void run() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                System.out.println("started.");
                processData(org.getId());
                System.out.println("finished.");
                }
            });
        }
    });  
StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • Thanks. Is there a way to mark @Service methods as transactional while specifying what transaction template to use? I ask because the "processData" method takes over 30 minutes, so I odn't want to make it a single transaction. It calls out to service methods, and I wish for each of those to be separate transactions. – Dave Jun 15 '17 at 14:52
1

Spring doesn't know anything about your Runnable instance. Therefore adding annotation on the run method doesn't make any sense. Instead what you want to do is to put @Transactional on processData method. But note that it should be the method of a Spring bean!

UPDATE: Just tried to mark a business method with both @Transactional and @Async and it worked for me. I used spring-boot of version 1.5.4.RELEASE that comes with spring 4.3.9.RELEASE and hibernate 5.0.12.Final. For my @Async configuration, I just used @EnableAsync. Here what I've got:

@Test
@Transactional
public void test() throws InterruptedException {
    beanA.testMethodA();

    //Sleeping, since after test is completed application is down which leaves @Async transaction not completed.
    Thread.sleep(3000);

    System.out.println("Thread in test method: "  + Thread.currentThread().getName());
    System.out.println("Transaction in test method: "  + TransactionSynchronizationManager.getCurrentTransactionName());
}

Here is a method of BeanA. It is transactional with a default propagation level which makes it bounded to a transaction started by the test:

@Override
@Transactional
public void testMethodA() {
    beanB.testMethodB();

    System.out.println("Thread in method A: " + Thread.currentThread().getName());
    System.out.println("Transaction in method A: " + TransactionSynchronizationManager.getCurrentTransactionName());
}

And the last part - method of a BeanB which is marked as @Async and @Transactional. It does run in a separate thread and in a separate transaction:

@Async
@Transactional
@Override
public void testMethodB() {
    System.out.println("Thread in method B: "  + Thread.currentThread().getName());
    System.out.println("Transaction in method B: "  + TransactionSynchronizationManager.getCurrentTransactionName());
}

After I run the test I get the following output:

Thread in test method: main
Transaction in test method: com.example.stackoverflow.TransactionTest.test

Thread in method A: main
Transaction in method A: com.example.stackoverflow.TransactionTest.test

Thread in method B: SimpleAsyncTaskExecutor-1
Transaction in method B: com.example.stackoverflow.BeanBImpl.testMethodB

Note the last two statements. The method marked as @Async and @Transactional is running in its own transaction and its own thread.

Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
  • THat doesn't work. processData is already invoking an autowired @Service class tha tis marked as Transactional. I added the Transactional around the "run" block because the way I just described was producing the same error. – Dave Jun 14 '17 at 19:55
  • Hmm.. strange... Btw, maybe a better way of making it asynchronous to use @Async instead? Is it feasible for you? Some info: http://www.baeldung.com/spring-async – Danylo Zatorsky Jun 14 '17 at 20:01
  • In this case, you could follow this answer: https://stackoverflow.com/questions/29258436/nested-transactional-methods-with-async – Danylo Zatorsky Jun 14 '17 at 20:05
  • Thanks, but why do you find htat strange? @Transactional always binds to the main thread so new threads need to figure out another way to create transactions. From the link you posted Async, does not create a new transaction, unless I'm misunderstanding something. – Dave Jun 14 '17 at 20:16
  • `@Transactional` doesn't always bind to main thread as my updated example shows. Yeah, you are right - `@Async` itself doesn't create a new transaction. In this case `TransactionSynchronizationManager.getCurrentTransactionName()` shows me null, which means that the method is running without transaction at all, even if calling bean is transactional. – Danylo Zatorsky Jun 15 '17 at 20:33