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

时间:2014-07-23 16:34:34

标签: java multithreading spring hibernate spring-data

我正在构建一个使用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方法中做一切来解决它,但我很快就知道这不是一个很好的解决方案,因为这不会让我使用的方法对网络请求工作得很好。

感谢。

3 个答案:

答案 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。