当前位置: 首页 > 知识库问答 >
问题:

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

袁志专
2023-03-14

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

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

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

然后在Spring中配置ThreadPoolTaskExec在

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

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

我已经能够通过在@Transactional方法中执行所有操作来解决它,但我很快就知道这不是一个很好的解决方案,因为这不允许我使用对Web请求运行良好的方法。

谢谢

共有3个答案

孟征
2023-03-14

方法#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());

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

邵弘义
2023-03-14

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

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

您可以将< code>SessionFactory注入到代码中,并使用< code > session factory . opensession()方法。< br >问题是,您必须管理您的事务。

东方旭东
2023-03-14

有了Spring,你不需要自己的执行程序。一个简单的注释< code>@Async将为您完成这项工作。只需用它在服务中注释您的< code>heavyMethod并返回void或< code>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
    }
}
 类似资料:
  • 问题内容: 我正在构建一个使用Spring Data和Hibernate的简单Tomcat Web应用程序。有一个端点要做很多工作,因此我想将工作卸载到后台线程,以便在完成工作时Web请求不会挂起10分钟以上。因此,我在一个组件扫描的程序包中编写了一个新服务: 然后在Spring中进行配置: 这一切都很好。但是,问题来自于Hibernate。在我的可运行内部,查询仅完成一半。我可以: 但是,如果我

  • 问题内容: 我可以选择让用户从FileChooser提交多个文件,以通过一些代码进行处理。结果将是读取文件的IO,然后是对存储数据的实际大量计算。允许用户选择多个文件,并且由于文件处理不依赖于所选的任何其他文件,因此使我的工作变得更加轻松。 此外,用户需要具有按钮列表,每个要取消的任务一个按钮以及“全部取消”按钮。因此,我必须考虑选择性或集体杀死一项或所有任务的能力。 最后一个要求是,我不允许用户

  • null 所以你可以不受任何限制地做背景工作。尽管由于这些原因您应该使用服务,但链接。 这是做背景工作的另一种方式(当然不是更好,但仍然是一种方式)吗?我错了吗?

  • 问题内容: WatchService听起来像一个令人兴奋的主意…不幸的是,它看起来像在tutorial / api中警告的那样低级,而且实际上不适合Swing事件模型(或者我错过了一些显而易见的东西,概率不为零) 以本教程中 WatchDir 示例中的代码(仅处理单个目录)为例,我基本上结束了 扩展SwingWorker 在构造函数中进行注册 将无限循环放在doInBackground中等待键 通

  • 我试图更好地理解JPA/Hibernate和SQL服务器的注释。 我创建了一个简单的项目:一个名为“Articles”的抽象类。有两个类继承了它:Ramette添加了权重,Pen添加了颜色。下面的代码无效,我无法更正错误。你有什么想法吗?非常感谢。

  • 我正在编写一个简单的代码,用javaFX显示表的内容。我希望程序在每次显示新内容时暂停。 问题是,无法按计划工作。事实上,程序甚至在显示内容之前都会暂停。 我如何纠正这个问题?