Skip to content

Commit

Permalink
Spring transaction connection leak. JetBrains#1167
Browse files Browse the repository at this point in the history
  • Loading branch information
Tapac authored and SchweinchenFuntik committed Oct 23, 2021
1 parent ab7141c commit a7b1f21
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 10 deletions.
1 change: 1 addition & 0 deletions exposed-spring-boot-starter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ tasks.withType(Test::class.java) {
showStandardStreams = true
exceptionFormat = TestExceptionFormat.FULL
}
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jetbrains.exposed.`jdbc-template`

import org.jetbrains.exposed.dao.UUIDEntity
import org.jetbrains.exposed.dao.UUIDEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.UUIDTable
import java.util.UUID

object AuthorTable : UUIDTable("authors") {
val description = text("description")
}

object BookTable : UUIDTable("books") {
val description = text("description")
}

class Book(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<Book>(AuthorTable)
var description by AuthorTable.description
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jetbrains.exposed.`jdbc-template`

import org.jetbrains.exposed.sql.transactions.transaction
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionOperations
import java.util.UUID

@Component
open class BookService(
@Qualifier("operations1")
private val operations1: TransactionOperations,
@Qualifier("operations2")
private val operations2: TransactionOperations,
private val jdbcTemplate: JdbcTemplate
) {

fun testWithSpringAndExposedTransactions() {
transaction {
Book.new { description = "123" }
}
operations1.execute {
val id = UUID.randomUUID().toString()
val query = "insert into authors(id, description) values ('$id', '234234')"
jdbcTemplate.execute(query)
}
}

fun testWithSpringTransaction() {
operations1.execute {
val id = UUID.randomUUID().toString()
val query = "insert into authors(id, description) values ('$id', '234234')"
jdbcTemplate.execute(query)
}
}

fun testWithExposedTransaction() {
transaction {
Book.new { description = "1234" }
}
}

fun testWithoutSpringTransaction() {
transaction {
Book.new { description = "1234" }
}
operations2.execute {
val id = UUID.randomUUID().toString()
val query = "insert into authors(id, description) values ('$id', '234234')"
jdbcTemplate.execute(query)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jetbrains.exposed.`jdbc-template`

import org.jetbrains.exposed.spring.SpringTransactionManager
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.support.TransactionOperations
import org.springframework.transaction.support.TransactionTemplate

@Configuration
open class JdbcConfiguration {
@Bean
@Qualifier("operations1")
open fun operations1(transactionManager: SpringTransactionManager): TransactionOperations {
return TransactionTemplate(transactionManager)
}

@Bean
@Qualifier("operations2")
open fun operations2() = TransactionOperations.withoutTransaction()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jetbrains.exposed.`jdbc-template`

import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.jupiter.api.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.event.annotation.BeforeTestClass

@SpringBootApplication
open class JdbcTemplateApplication

@SpringBootTest(
classes = [JdbcTemplateApplication::class],
properties = ["spring.datasource.url=jdbc:h2:mem:test", "spring.datasource.driver-class-name=org.h2.Driver", "spring.exposed.generate-ddl=true"]
)
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class JdbcTemplateTests {

@BeforeTestClass
fun beforeTests() {
transaction {
SchemaUtils.create(AuthorTable, BookTable)
}
}

@Autowired
lateinit var bookService: BookService

@Order(1)
@RepeatedTest(15, name = "Without spring transaction: {currentRepetition}/{totalRepetitions}")
fun testWithoutSpringTransaction() {
bookService.testWithoutSpringTransaction()
}

@Order(2)
@RepeatedTest(15, name = "With spring transaction: {currentRepetition}/{totalRepetitions}")
fun testWithSpringTransaction() {
bookService.testWithSpringTransaction()
}

@Order(3)
@RepeatedTest(15, name = "With exposed transaction: {currentRepetition}/{totalRepetitions}")
fun testWithExposedTransaction() {
bookService.testWithExposedTransaction()
}

@Order(4)
@RepeatedTest(15, name = "With spring and exposed transactions: {currentRepetition}/{totalRepetitions}")
fun testWithSpringAndExposedTransactions() {
bookService.testWithSpringAndExposedTransactions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.sql.DataSource

class SpringTransactionManager(
private val _dataSource: DataSource,
_dataSource: DataSource,
@Volatile override var defaultRepetitionAttempts: Int = DEFAULT_REPETITION_ATTEMPTS
) : DataSourceTransactionManager(_dataSource), TransactionManager {

Expand All @@ -40,7 +40,7 @@ class SpringTransactionManager(
override fun doBegin(transaction: Any, definition: TransactionDefinition) {
super.doBegin(transaction, definition)

if (TransactionSynchronizationManager.hasResource(_dataSource)) {
if (TransactionSynchronizationManager.hasResource(obtainDataSource())) {
currentOrNull() ?: initTransaction()
}
if (!TransactionSynchronizationManager.hasResource(springTxKey)) {
Expand All @@ -50,8 +50,9 @@ class SpringTransactionManager(

override fun doCleanupAfterCompletion(transaction: Any) {
super.doCleanupAfterCompletion(transaction)
if (!TransactionSynchronizationManager.hasResource(_dataSource)) {
if (!TransactionSynchronizationManager.hasResource(obtainDataSource())) {
TransactionSynchronizationManager.unbindResourceIfPossible(this)
TransactionSynchronizationManager.unbindResource(springTxKey)
}
if (TransactionSynchronizationManager.isSynchronizationActive() && TransactionSynchronizationManager.getSynchronizations().isEmpty()) {
TransactionSynchronizationManager.clearSynchronization()
Expand Down Expand Up @@ -89,7 +90,7 @@ class SpringTransactionManager(
}

private fun initTransaction(): Transaction {
val connection = (TransactionSynchronizationManager.getResource(_dataSource) as ConnectionHolder).connection
val connection = (TransactionSynchronizationManager.getResource(obtainDataSource()) as ConnectionHolder).connection

val transactionImpl = SpringTransaction(JdbcConnectionImpl(connection), db, defaultIsolationLevel, currentOrNull())
TransactionManager.resetCurrent(this)
Expand Down Expand Up @@ -120,11 +121,7 @@ class SpringTransactionManager(
) : TransactionInterface {

override fun commit() {
connection.run {
if (!autoCommit) {
commit()
}
}
connection.commit()
}

override fun rollback() {
Expand All @@ -135,7 +132,6 @@ class SpringTransactionManager(
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.getResource(springTxKey)?.let { springTx ->
this@SpringTransactionManager.doCleanupAfterCompletion(springTx)
TransactionSynchronizationManager.unbindResource(springTxKey)
}
}
}
Expand Down

0 comments on commit a7b1f21

Please sign in to comment.