使用 Spring Data 和 Hibernate 时如何正确执行后台线程? [英] How do I properly do a background thread when using Spring Data and Hibernate?

查看:17
本文介绍了使用 Spring Data 和 Hibernate 时如何正确执行后台线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个使用 Spring Data 和 Hibernate 的简单 Tomcat Web 应用程序.有一个端点做了很多工作,所以我想将工作卸载到后台线程,以便在工作完成时 Web 请求不会挂起 10 分钟以上.所以我在组件扫描的包中编写了一个新服务:

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

然后在Spring中配置ThreadPoolTask​​Executor:

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>

这一切都很好.但是,问题来自Hibernate.在我的 runnable 中,查询只工作了一半.我可以:

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

我收到以下错误:

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

所以在我(Hibernate 新手)看来,新线程在这些自制线程上没有会话,但 Spring Data 会自动为 HTTP 请求线程创建新会话.

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.

  • 有没有办法从会话中手动启动新会话?
  • 或者告诉线程池为我做这件事的方法?
  • 从事此类工作的标准做法是什么?

我已经能够通过在 @Transactional 方法内部做所有事情来稍微解决它,但是我很快发现这不是一个很好的解决方案,因为这不会让我使用的方法适用于网络请求.

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.

谢谢.

推荐答案

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

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

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

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.

我自己不合并 @Async@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
    }
}

这篇关于使用 Spring Data 和 Hibernate 时如何正确执行后台线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆