我正在构建一个使用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
:
<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中,查询只有一半工作。我能做到:
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 Request线程创建新会话。
我已经能够通过在@Transactional
方法中做一切来解决它,但我很快就知道这不是一个很好的解决方案,因为这不会让我使用的方法对网络请求工作得很好。
感谢。
答案 0 :(得分:20)
使用Spring,您不需要自己的执行程序。一个简单的注释@Async
将为您完成工作。只需使用它注释您服务中的heavyMethod
并返回void或Future
对象,您将获得一个后台主题。我会避免在控制器级别使用异步注释,因为这将在请求池执行器中创建一个异步线程,并且您可能会用完“请求接受器”。
您的延迟异常的问题出现在您没有会话的新线程中。为避免此问题,您的异步方法应该处理完整的工作。不要提供以前加载的实体作为参数。该服务可以使用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
}
}
答案 1 :(得分:0)
可能会发生的事情是,您的DAO代码上有事务处理,而Spring在事务结束时关闭会话。
您应该将所有业务逻辑压缩到单个事务中。
您可以将SessionFactory
注入代码并使用SessionFactory.openSession()
方法
问题是,您必须管理您的交易。
答案 2 :(得分:-1)
方法#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();
<your database changes>
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。