在使用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。