Spring Boot 2.1.4 + JDBI + HikariCP + PostgreSQL 发生错误后连接未释放到池
Posted
技术标签:
【中文标题】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 【发布时间】:2020-03-15 00:55:19 【问题描述】:我有一个包含在 API 中的 Spring Boot 应用程序。它公开了一个端点/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 个待处理开始,所有请求都返回例外结果。
我尝试将 Pool 更改为 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)
【问题讨论】:
您没有交易。您的服务必须是@Transactional
才能正常工作。
是的,对不起,我忘了解释他们是一个捕手,它会自动将@Transactionnal
注释添加到我所有的@Service
。只是为了确保我在我的服务上添加了注释,但这并没有改变任何东西..
【参考方案1】:
我没有运行您的代码,但我确信根本问题如下:
您的JdbiHandleManager
是多余的并导致问题(打开了2 个连接)。为什么?因为TransactionAwareDataSourceProxy
已经处理了连接的打开和关闭。当 Spring 遇到“事务性”(通过注解或方面)的方法调用时,会打开连接并绑定到当前线程。
这意味着只使用jdbi.withHandle(...)
就足够了,因为下一个打开连接的调用将返回活动的事务连接,并且对close
的调用被代理,因为 spring 会自行关闭连接。
由于您实现了自己的“管理器”,这种情况发生了两次,一次是在春季,一次是您自己。两个线程本地连接(一个已经包裹在 Handle
中)
这里我的建议是去掉你的自定义代码,完全依赖TransactionAwareDataSourceProxy
的正确操作
【讨论】:
谢谢@winson。我忘了更新票,但您准确地描述了解决方案。只需要删除这个JdbiHandleManager
和 JdbiSetHandleInterceptor
并让 Spring 处理连接。以上是关于Spring Boot 2.1.4 + JDBI + HikariCP + PostgreSQL 发生错误后连接未释放到池的主要内容,如果未能解决你的问题,请参考以下文章
我们如何使用 jdbi 在 Dropwizard 中强制资源(控制器)级别的事务?