且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在使用Spring Data和Hibernate时正确地创建后台线程?

更新时间:2023-02-16 12:47:44

有了Spring,你不需要你自己的执行者。一个简单的注释 @Async 将为您完成这项工作。只需在你的服务中使用它注释 heavyMethod ,并返回void或一个 Future 对象,你将得到一个后台线程。我会避免在控制器级别使用异步注释,因为这会在请求池执行器中创建一个异步线程,并且可能会耗尽'请求接受者'。



你懒惰的异常的问题来自你怀疑没有会话的新线程。为了避免这个问题,你的异步方法应该处理完整的工作。不要提供先前加载的实体作为参数。该服务可以使用EntityManager,也可以是事务性的。



我自己不要合并 @Async @Transactional 所以我可以以任何方式运行服务。我只是在服务周围创建异步包装,并根据需要使用此包装。 (这简化了测试,例如)

  @Service 
public class AsyncService {

@ Autowired
私人服务服务;

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



@Service
public class Service {

@PersistenceContext
private EntityManager em;

@Transactional
public void doHeavy(int entityId){
//一些长时间运行的工作
}
}


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.***.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.

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
    }
}