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

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

萧鹏云
2023-03-14

我有一个包含在API中的Spring Boot应用程序。它公开了一个endpoint/api/persons/:id。在引擎盖下,Spring靴附带了Hibernate和Hikaricp。我的数据库是PostgreSQL 10。该应用程序是在Kotlin中构建的。

当API同时接收请求时,我发现了一个问题,似乎应用程序需要2个活动连接来执行endpoint调用的操作findPersonbyId()

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

另外,如果我同时只发送4个请求,所有的事情都是例外的,它以5个活动连接和3个挂起开始,所有的请求都返回例外的结果。

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

@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)
    }

}
@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()
    }
}
@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
    }
}
@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)
            }
        }
}

共有1个答案

郜驰
2023-03-14

我没有运行您的代码,但我非常确信潜在的问题是:

您的jdbihandleManager是多余的,并导致问题(打开了2个连接)。为什么?因为TransactionAwareDataSourceProxy已经处理连接的打开和关闭。当Spring遇到一个“事务性”的方法调用(通过注释或方面)时,一个连接被打开并绑定到当前线程。

这意味着只使用jdbi.withHandle(...)是完全足够的,因为打开连接的下一个调用返回活动的事务连接,并且对close的调用是代理的,因为spring会自行关闭连接。

这里我的建议是删除您的自定义代码,并完全依赖TransactionAwareDataSourceProxy的正确操作

 类似资料:
  • 我尝试使用HikariCP和mariaDB数据库,但是当我尝试初始化的时候,我得到了下一个错误。 由以下原因引起:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 我的MariaDBDatabase类: 我的数据库类: 我的家伙.xml

  • Tomcat在使用后不释放连接的原因可能是什么? 这是我的配置

  • 我正在使用hikaricp(这可能也适用于任何其他数据库连接池)。我有一个DBPool类,在其中我实例化了一个HikariDataSource(使用HikariConfig对象)。对于这个DBPool,我使用lazyholder习惯用法来限制每个VM一个池实例。但是,一旦获得对池的引用,就可以检索连接对象(无需任何进一步的锁/同步/信号量检查),因为我认为连接池会处理我的连接对象限制。每次通过数据

  • 我对R2DBC池有一个奇怪的行为:我们碰巧创建了大量线程并将它们发送到R2DBC池以获取DB连接。当池中的所有R2DBC连接都在使用时,我们创建的线程排队等待空闲连接可用,这发生在释放之前使用的连接时。如果我们在等待空闲连接时取消这些线程,会发生以下行为: 即使它们被取消,也有一些线程获得连接并通过其正常的DB进程 因此,一些连接不会返回到空闲状态。它们保持被获取状态,并阻止后续连接请求获取这些特

  • 问题内容: 想象以下Perl代码(此处为伪代码): 在这种情况下,由于Perl脚本在第2行结束,因此我不会释放该锁。在这种情况下,操作系统是否曾经释放过该锁?它是否看到“嘿,获取锁的脚本崩溃了”并释放了锁?它会立即释放锁吗?另外,是否为每个脚本运行一个Perl实例,以便清楚地知道哪个脚本崩溃/停止而不释放锁? 问题答案: 在那种情况下,操作系统是否释放过该锁? 它是否看到“嘿,获取锁的脚本崩溃了”

  • 我使用的是spring-webflux-5.2.8。释放,此操作“正常”: 返回错误时,不会出现任何问题,因为连接已被破坏,并且不会放回连接池: 调试r.n.resources。PooledConnectionProvider-[id:0xa23f78ad,L:/127.0.0.1:7524!R:localhost/127.0.0.1:8443]通道已关闭,现在有0个活动连接和0个非活动连接 但当