使用Spring Data和Hibernate时,如何正确执行后台线程?

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

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

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

然后在春季配置:ThreadPoolTaskExecutor

<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。在我的可运行中,查询只有一半的工作。我可以做:

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

但是如果我有延迟加载的字段,它会失败:

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请求线程创建新会话。

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

我已经能够通过从方法内部执行所有操作来解决这个问题,但我很快就发现这不是一个很好的解决方案,因为这不允许我使用对Web请求有效的方法。@Transactional

谢谢。


答案 1

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

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

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

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

答案 2

发生的事情可能是,你的DAO代码段上有交易,而Spring在交易关闭时关闭会话。

您应该将所有业务逻辑压缩到单个事务中。

您可以注入到代码和使用方法中。
问题是,您将不得不管理您的交易。SessionFactorySessionFactory.openSession()