Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I properly do a background thread when using Spring Data and Hibernate?

I'm building a simple Tomcat webapp that's using Spring Data and Hibernate. There's one end point that does a lot of work, so I want to offload the work to a background thread so that the web request doesn't hang for 10+ minutes while the work is being done. So I wrote a new Service in a component-scan'd package:

@Service
public class BackgroundJobService {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    public void startJob(Runnable runnable) {
         threadPoolTaskExecutor.execute(runnable);
    }
}

Then have the ThreadPoolTaskExecutor configured in Spring:

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="5" />
    <property name="maxPoolSize" value="10" />
    <property name="queueCapacity" value="25" />
</bean>

This is all working great. However, the problem comes from Hibernate. Inside my runnable, queries only half work. I can do:

MyObject myObject = myObjectRepository.findOne()
myObject.setSomething("something");
myObjectRepository.save(myObject);

But if I have lazy loaded fields, it fails:

MyObject myObject = myObjectRepository.findOne()
List<Lazy> lazies = myObject.getLazies();
for(Lazy lazy : lazies) { // Exception
    ...
}

I get the following error:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.stackoverflow.MyObject.lazies, could not initialize proxy - no Session

So it looks like to me (Hibernate newbie) that the new thread doesn't have a session on these home-made threads, but Spring Data is automatically creating new sessions for HTTP Request threads.

  • Is there a way to start a new session manually from within the session?
  • Or a way to tell the thread pool to do it for me?
  • What's the standard practice for doing this kind of work?

I've been able to work around it a little by doing everything from inside a @Transactional method, but I'm quickly learning that's not a very good solution, as that doesn't let me use methods that work just fine for web requests.

Thanks.

like image 730
Joel Avatar asked Jul 23 '14 16:07

Joel


People also ask

Can we use Spring data JPA and Hibernate together?

You cant actually use both of them in the same application.

Can we use hibernate inside Spring data?

With Spring Data, you may use Hibernate, EclipseLink, or any other JPA provider.

How do I run a Java process in the background?

You can read the input stream(i.e br. readLine() ) in a thread. That way, it's always running in the background. ReaderThread should continue reading the output of the process you have started, as long as it lasts.


1 Answers

With Spring you don't need your own executor. A simple annotation @Async will do the work for you. Just annotate your heavyMethod in your service with it and return void or a Future object and you will get a background thread. I would avoid using the async annotation on the controller level, as this will create an asynchronous thread in the request pool executor and you might run out of 'request acceptors'.

The problem with your lazy exception comes as you suspected from the new thread which does not have a session. To avoid this issue your async method should handle the complete work. Don't provide previously loaded entities as parameters. The service can use an EntityManager and can also be transactional.

I for myself dont merge @Async and @Transactional so i can run the service in either way. I just create async wrapper around the service and use this one instead if needed. (This simplifies testing for example)

@Service
public class AsyncService {

    @Autowired
    private Service service;

    @Async
    public void doAsync(int entityId) {
        service.doHeavy(entityId);
    }
}

@Service
public class Service {

    @PersistenceContext
    private EntityManager em;

    @Transactional
    public void doHeavy(int entityId) {
        // some long running work
    }
}
like image 195
Martin Frey Avatar answered Oct 18 '22 09:10

Martin Frey