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

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

@Service public class BackgroundJobService { @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; public void startJob(Runnable runnable) { threadPoolTaskExecutor.execute(runnable); } } 

然后在Spring中配置ThreadPoolTaskExecutor

      

这一切都很有效。 但问题来自Hibernate。 在我的runnable中,查询只有一半工作。 我可以:

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

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

 MyObject myObject = myObjectRepository.findOne() List 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 Request线程创建新会话。

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

我已经能够通过在@Transactional方法中做一些事情来解决它,但我很快就知道这不是一个非常好的解决方案,因为这不允许我使用适用于Web请求的方法。

谢谢。

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

您的延迟exception的问题出现在您没有会话的新线程中。 为避免此问题,您的异步方法应该处理完整的工作。 不要提供以前加载的实体作为参数。 该服务可以使用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 } } 

可能的情况是,您的DAO代码上有事务,Spring在事务结束时关闭会话。

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

您可以将SessionFactory注入到代码中并使用SessionFactory.openSession()方法。
问题是,您必须管理您的交易。

方法#1:JPA实体经理

在后台线程中:注入实体管理器或从Spring上下文获取它或将其作为引用传递:

 @PersistenceContext private EntityManager entityManager; 

然后创建一个新的实体管理器,以避免使用共享管理器:

 EntityManager em = entityManager.getEntityManagerFactory().createEntityManager(); 

现在您可以启动事务并使用Spring DAO,Repository,JPA等

 private void save(EntityManager em) { try { em.getTransaction().begin();  em.getTransaction().commit(); } catch(Throwable th) { em.getTransaction().rollback(); throw th; } } 

方法#2:JdbcTemplate

如果您需要进行低级别更改或任务很简单,可以使用JDBC和手动查询:

 @Autowired private JdbcTemplate jdbcTemplate; 

然后在你的方法的某个地方:

 jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId()); 

旁注:除非您使用JTA或依赖JpaTransactionManager,否则我建议远离@Transactional。