Spring + Hibernate+ HikariCP:如何在进行长时间运行的 REST 调用时处理数据库连接?

Posted

技术标签:

【中文标题】Spring + Hibernate+ HikariCP:如何在进行长时间运行的 REST 调用时处理数据库连接?【英文标题】:Spring + Hibernate+ HikariCP: how to handle DB connection while doing long running REST call? 【发布时间】:2018-09-27 17:10:18 【问题描述】:

我有一个项目在 Spring Boot 1.3.8、Hikari CP 2.6.1 和 Hibernate (Spring ORM 4.2.8) 上运行。服务层代码如下:

public void doStuff() 
    A a = dao.findByWhatever();
    if (a.hasProperty()) 
        B b = restService.doRemoteRequestWithRetries(); // May take long time
    
    a.setProp(b.getSomethig());
    dao.save(b);

Hikari 配置有这个:spring.datasource.leakDetectionThreshold=2000。 问题是外部 REST 服务非常慢并且通常需要 2 秒以上的时间来响应,因此我们看到很多 java.lang.Exception: Apparent connection leak detected 都是假阴性,尽管问题可以很明显地看到:我们保持 DB 连接我们执行休息请求的时间。

问题是:如何正确解耦 DB 和 REST 的东西?或者如何告诉休眠在两者之间释放连接?这样我们就可以在等待 REST 响应时将 DB 连接返回到池中。

我已经尝试设置hibernate.connection.release_mode=AFTER_TRANSACTION 并且它有点帮助,至少我们没有连接泄漏异常。唯一的问题是我们的测试开始显示:

2018-04-17 15:48:03.438  WARN 94029 --- [           main] o.s.orm.jpa.vendor.HibernateJpaDialect   : JDBC Connection to reset not identical to originally prepared Connection - please make sure to use connection release mode ON_CLOSE (the default) and to run against Hibernate 4.2+ (or switch HibernateJpaDialect's prepareConnection flag to false`

测试使用注入的 DAO 在 DB 中插入记录,然后通过应用程序 API 检查它们。它们没有使用@Transactional 进行注释,并且侦听器列表如下所示:

@TestExecutionListeners(
    DependencyInjectionTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    TransactionDbUnitTestExecutionListener.class
)

有什么想法可能是测试的问题吗?

【问题讨论】:

您的@Service@Controller 是否带有@Transactional 注释?您提到测试不是,但生产代码中的事务边界在哪里?希望您没有在 Java 代码中管理事务,例如通过自动连线txManager bean。 不,我没有在代码中使用txManager。我的 bean 都没有用 @Transactional 注释,我最终注释了 DAO。 【参考方案1】:

在代码中

public void doStuff() 
    A a = dao.findByWhatever();
    if (a.hasProperty()) 
        B b = restService.doRemoteRequestWithRetries(); // May take long time
    
    a.setProp(b.getSomethig());
    dao.save(b);

我在这里看到三个任务 - 获取实体 A、连接到远程服务和更新实体 A。还有all these are in same transaction, so the underlying connection will be held till the method is complete

所以我们的想法是将任务一和三拆分成单独的事务,在调用远程服务之前允许释放连接。

基本上,使用 spring boot 你需要添加spring.jpa.open-in-view=false。这不会注册 OpenEntityManagerInViewInterceptor,因此 entityManager(轮流连接)不会绑定到当前线程/请求。

随后,使用@Transactional 将三个任务拆分为单独的方法。这有助于我们将 entityManager 绑定到事务范围并在事务方法结束时释放连接。

注意:并确保在调用这些方法之前(即调用者 - 如控制器等)没有任何事务已启动/正在进行。否则目的就失败了,这些新的@Transactional 方法将像以前一样在同一个事务中运行。


所以高级方法可能如下所示:

在spring boot application.properties中添加属性spring.jpa.open-in-view=false。 接下来您需要将doStuff 方法拆分为新服务类中的三个方法。目的是确保他们使用不同的交易。 第一个方法是@Transactionalwill call A a = dao.findByWhatever();`。 第二种方法进行远程调用。 第三种方法@Transactionalwill call rest of the code with JPA merge or hibernate saveOrUpdate on objecta`。 现在在您当前的代码中自动装配此新服务并调用 3 个方法。

【讨论】:

工作就像一个魅力,荣誉@madhusudana-reddy-sunnapu

以上是关于Spring + Hibernate+ HikariCP:如何在进行长时间运行的 REST 调用时处理数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Spring 在使用 Hibernate 3 时推迟关闭 Hibernate 会话

Spring + Hibernate + Spring Security 配置

Spring框架学习spring整合hibernate

spring整合hibernate

Spring4.* 中整合 Hibernate

hibernate与spring整合