发生错误后连接未释放到池中 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
的正确操作
我有一个包含在 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