发生错误后连接未释放到池中 Spring Boot 2.1.4 + JDBI + HikariCP + PostgreSQL

Connection not released to the pool after an error occured Spring Boot 2.1.4 + JDBI + HikariCP + PostgreSQL

我有一个包含在 API 中的 Spring 启动应用程序。它公开了一个端点 /api/persons/:id。 在幕后 Spring Boot 带有 Hibernate 和 HikariCP。我的数据库是 PostgreSQL 10。 该应用程序是用 Kotlin 构建的。

我发现 API 收到并发请求时出现问题,应用似乎需要 2 个活动连接才能执行端点调用的操作 findpersonById()

池是这样配置的: spring.datasource.hikari.maximumPoolSize=5

当我向 /api/persons/:id 发送 5 个同时请求时,从池中获取 5 个连接来执行后面的请求,其中 5 个处于挂起状态。 池最终抛出异常,因为 5 个挂起的连接一直在等待 connectionTimeout 时间段,请求也失败了。

我面临的问题是,在那之后 HikariCP 仍然说池中有 5 个活动连接。如果我查看 PostgreSQL 统计信息,所有连接都是空闲的。

仅供参考,如果我同时仅发送 4 个请求,一切都会正常进行,它以 5 个活动连接和 3 个待处理以及所有请求 returns 异常结果开始。

我尝试将池更改为 Tomcat JDBC,但结果完全相同。

我不明白为什么首先需要 2 个连接。

如果有人知道我做错了什么,我会在此处传递一些代码。

@RestController
@RequestMapping("/api/person")
class PersonResource(private val personService: PersonService,
                     private val personUpdateService: PersonUpdateService,
                     private val identityManagementService: IdentityWithManagementService) : PersonApi {

    @GetMapping("/{id}")
    override fun findPersonById(@PathVariable id: String): PersonDto? {
        return personService.findFull(id)
    }

}

personService

@Service
class PersonService(private val documentsService: DocumentService,
                    private val postalAddressService: PostalAddressService) : EntityService<Person>(repository) {

    fun findFull(personId: String): PersonDto? {
        return find(personId)?.let { person ->
            PersonDto(
                    person,
                    postalAddressService.findByPersonId(personId).map { it.toDto() },
                    documentsService.findByPersonId(personId).map { it.toDto() }
            )
        }
    }
}

PersonPostgresRepository:

@Repository
class PersonPostgresRepository : AbstractPostgresRepository(), PersonEntityRepository {

    override fun find(id: String): Person? {
        return withHandle<Person?, Exception> {
            it.createQuery(
                    "select * " +
                            "from identiti_person " +
                            "where id = :id")
                    .bind("id", id)
                    .map(PersonRowMapper())
                    .firstOrNull()
        }
    }
}

AbstractPostgresRepository:

abstract class AbstractPostgresRepository {

    @Autowired
    private lateinit var handleManager: JdbiHandleManager

    @Autowired
    private lateinit var jdbi: Jdbi

    protected fun <R, X : Exception> withHandle(callback: (handle: Handle) -> R): R {
        val handle = handleManager.handle
        return if (handle.isPresent) {
            callback.invoke(handle.get())
        } else {
            jdbi.withHandle(HandleCallback<R, X> {
                callback.invoke(it)
            })
        }
    }
}

还有 JdbiHandleManager 如果你问:

@Component
@Scope("singleton")
class JdbiHandleManager(private val jdbi: Jdbi) {

    private val currentHandle = ThreadLocal<Handle>()

    val handle: Optional<Handle>
        get() = Optional.ofNullable(currentHandle.get())

    internal fun openHandle(): Handle {
        val handle = jdbi.open()
        currentHandle.set(handle)
        return handle
    }

    internal fun closeHandle() {
        val handle = currentHandle.get()
        currentHandle.remove()
        handle?.close()
    }
}

并且 JdbiConfig 初始化 Jdbi:

@Configuration
open class JdbiConfig {

    @Bean
    open fun jdbi(dataSource: DataSource): Jdbi {
        // JDBI wants to control the Connection wrap the datasource in a proxy
        // That is aware of the Spring managed transaction
        val dataSourceProxy = TransactionAwareDataSourceProxy(dataSource)
        val jdbi = Jdbi.create(dataSourceProxy)
        jdbi.installPlugins()

        return jdbi
    }
}

我的所有 @Service 都是交易性的,感谢这个:

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    override fun transactionAttributeSource(): TransactionAttributeSource {
        /*
            Defines an annotation transaction source (the default) which consider that @Service components
            are transactional by default (making the use of @Transactional optional on those classes).
            Note that the class has to be processed by a TransactionInterceptor for that source to be applied,
            this is the responsibility of the auto proxy creator below.
         */
        return object : AnnotationTransactionAttributeSource() {
            override fun findTransactionAttribute(clazz: Class<*>): TransactionAttribute? {
                return if (clazz.getAnnotation(Service::class.java) != null && clazz.getAnnotation(Transactional::class.java) == null) {
                    DefaultTransactionAttribute(TransactionAttribute.PROPAGATION_REQUIRED)
                } else super.findTransactionAttribute(clazz)
            }
        }
}

我没有 运行 你的代码,但我非常确信潜在的问题如下:

您的 JdbiHandleManager 是多余的并且会导致问题(打开 2 个连接)。为什么?因为 TransactionAwareDataSourceProxy 已经处理了连接的打开和关闭。当 Spring 遇到方法调用 "transactional" (通过注释或方面)时,连接将打开并绑定到当前线程。

这意味着仅使用 jdbi.withHandle(...) 就足够了,因为下一次打开连接的调用将返回活动的事务连接,并且代理对 close 的调用,因为 spring 将自行关闭连接。

自从您实施了自己的 "Manager" 以来,这种情况发生了两次,一次是 spring 一次是您。两个线程本地连接(一个已经包裹在 Handle 中)

我这里的建议是去掉你的自定义代码,完全依赖TransactionAwareDataSourceProxy

的正确操作