-
Notifications
You must be signed in to change notification settings - Fork 38k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow global default for @Transactional rollback behavior on checked exceptions #23473
Comments
@ilyastam as stated here: https://docs.spring.io/spring/docs/5.2.0.RC1/spring-framework-reference/data-access.html#transaction-declarative-rolling-back |
Kotlin doesn't distinguish between RuntimeException (unchecked) and Exception (checked), meaning Kotlin code can freely throw either type. Spring transactional support does distinguish between the two by not rolling back any checked exception by default (an exception subclassing Exception). One can choose to only use RuntimeException derived exceptions in Kotlin to avoid that issue, but it can also arise when Kotlin code calls into a Java method that throws a checked Exception. Essentially it's a "foot gun", one mistake can lead to the unexpected behavior of a transaction not being rolled back. |
The issue is that in a transactional method we can call a method which can throw Why not to rollback by default by |
How about specifying rollback behavior in a |
@elab that would mark all public methods in the class to be transactional. |
It would be really beneficial to have this as a configurable option (maybe even set by default when choosing Kotlin on |
Here is what we did to work around the issue in our Kotlin Spring Boot app: import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Role
import org.springframework.core.KotlinDetector.isKotlinType
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
import org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
import org.springframework.transaction.interceptor.DelegatingTransactionAttribute
import org.springframework.transaction.interceptor.TransactionAttribute
import org.springframework.transaction.interceptor.TransactionAttributeSource
import java.lang.reflect.AnnotatedElement
import java.lang.reflect.Method
@Configuration
class TransactionConfig : ProxyTransactionManagementConfiguration() {
/**
* Define a custom [TransactionAttributeSource] that will roll back transactions
* on checked Exceptions if the annotated method or class is written in Kotlin.
*
* Kotlin doesn't have a notion of checked exceptions, but [Transactional] assumes
* Java semantics and does *not* roll back on checked exceptions. This can become
* an issue if a Kotlin class explicitly throws Exception or calls into a Java
* method which throws checked exceptions.
*
* @see: https://github.com/spring-projects/spring-framework/issues/23473
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
override fun transactionAttributeSource(): TransactionAttributeSource {
return object : AnnotationTransactionAttributeSource() {
override fun determineTransactionAttribute(element: AnnotatedElement): TransactionAttribute? {
val txAttr = super.determineTransactionAttribute(element)
?: return null
val isKotlinClass = when (element) {
is Class<*> -> isKotlinType(element)
is Method -> isKotlinType(element.declaringClass)
else -> false
}
if (isKotlinClass) {
return object : DelegatingTransactionAttribute(txAttr) {
override fun rollbackOn(ex: Throwable): Boolean {
return super.rollbackOn(ex) || ex is Exception
}
}
}
return txAttr
}
}
}
} Essentially extremely cautious, and only rolls back for Exception by default if the annotated class is written in Kotlin. I feel this fix could be rolled into spring-tx, as it seems pretty surgical / low risk (only change behavior for Kotlin classes). |
The config infrastructure could be shared by work on #24291 |
It should be |
I'm introducing a For any further customization needs at the global level, |
In Kotlin all exceptions are effectively unchecked. Therefore, transactions will not be rolled back for functions like this:
to achieve a correct behavior, the above code needs to be refactored as follows:
this isn't very intuitive and can lead to unexpected results. Furthermore, even if a developer is aware of this, he/she should not forget to specify
rollbackFor
for every@Transactional
annotation.It should be possible to configure application-wide defaults for
rollbackFor
andnoRollbackFor
The text was updated successfully, but these errors were encountered: