Skip to content
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

Nested transaction support via savepoints is broken in Oracle database #33987

Closed
minjun-jin opened this issue Nov 29, 2024 · 1 comment
Closed
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: regression A bug that is also a regression
Milestone

Comments

@minjun-jin
Copy link

minjun-jin commented Nov 29, 2024

Related to pull request:

org.springframework.jdbc.datasource.JdbcTransactionObjectSupport releaseSavepoint

The function was only handling exceptions as a log, but when it was modified to throw an exception, the nested transaction in the Oracle database was broken.

The Oracle driver does not support explicit releaseSavepoint. It releases automatically.

A modification is needed to revert to the existing logic that does not throw exceptions or to prevent releaseSavepoint calls when using an Oracle database.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 29, 2024
@FabianB98
Copy link

We've also stumbled upon this change when trying to upgrade from Spring Boot 3.3.5 (Spring 6.1.14) to Spring Boot 3.4.0 (Spring 6.2.0).

To add some more information to this issue: Our setup consists of a Spring Boot application communicating via JOOQ to an Oracle 19c SQL Database. Specifically, there are a few operations where a nested transaction is needed and the code for this looks something like this:

@Transactional
public void doStuff() {
    // Some stuff to be performed in the outer transaction
    
    // An insert that needs to be committed to the database before the outer transaction is committed
    dslContext.transaction((tr) -> DSL.using(tr)
        .insertInto(
            Tables.SOME_TABLE,
            Tables.SOME_TABLE.IDENTIFIER,
            Tables.SOME_TABLE.FOO,
            Tables.SOME_TABLE.BAR)
        .values(
            id,
            foo,
            bar)
        .execute());

    // Some more stuff to be performed in the outer transaction
}

In Spring 6.1.14 this has worked as expected, however while trying to upgrade to Spring 6.2.0 we noticed that there were a few tests which started to fail due to an exception being thrown when the nested transaction is being committed:

org.springframework.transaction.TransactionSystemException: Could not explicitly release JDBC savepoint
        at org.springframework.jdbc.datasource.JdbcTransactionObjectSupport.releaseSavepoint(JdbcTransactionObjectSupport.java:183)
        at org.springframework.transaction.support.AbstractTransactionStatus.releaseHeldSavepoint(AbstractTransactionStatus.java:177)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:786)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:758)
        at org.springframework.data.transaction.MultiTransactionStatus.commit(MultiTransactionStatus.java:74)
        at org.springframework.data.transaction.ChainedTransactionManager.commit(ChainedTransactionManager.java:161)
        at org.springframework.boot.autoconfigure.jooq.SpringTransactionProvider.commit(SpringTransactionProvider.java:54)
        at org.jooq.impl.DefaultDSLContext.lambda$transactionResult0$3(DefaultDSLContext.java:534)
        at org.jooq.impl.Tools$3$1.block(Tools.java:6370)
        at java.base/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)
        at java.base/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)
        at org.jooq.impl.Tools$3.get(Tools.java:6367)
        at org.jooq.impl.DefaultDSLContext.transactionResult0(DefaultDSLContext.java:578)
        at org.jooq.impl.DefaultDSLContext.transactionResult(DefaultDSLContext.java:502)
        at org.jooq.impl.DefaultDSLContext.transaction(DefaultDSLContext.java:591)
        at [our class].doStuff([line number pinpointing to the call of dslContext.transaction])
Caused by: java.sql.SQLFeatureNotSupportedException: Nicht unterstützte Funktion: releaseSavepoint
        at oracle.jdbc.driver.PhysicalConnection.releaseSavepoint(PhysicalConnection.java:4283)
        at com.zaxxer.hikari.pool.HikariProxyConnection.releaseSavepoint(HikariProxyConnection.java)
        at org.springframework.jdbc.datasource.JdbcTransactionObjectSupport.releaseSavepoint(JdbcTransactionObjectSupport.java:180)
        ... 106 common frames omitted

As we investigated the cause of this failing test, we've found out that there was in fact a debug log with the same message being logged in Spring 6.1.14, whereas now with Spring 6.2.0 this exception is being thrown instead of being swallowed. We've pinpointed the origin of that change down to the same PR (#32992) as @minjun-jin did.

The reason for this PR seems reasonable in a way that clients of spring-jdbc should be informed about such database errors. However, this exception stems from a level so deep that there is no way how we could handle this in our application code while also ensuring that the nested transaction doesn't rollback. We believe that this exception should be handled from within spring-tx (presumably from within AbstractPlatformTransactionManager::processCommit) as any Spring application using a transaction from within spring-tx is only a transitive client of spring-jdbc. Furthermore, the logic being performed within spring-tx seems like a place where this exception could actually be handled in a useful way without causing the whole transaction to rollback.

Another point to mention here is the fact that the implementation of AbstractPlatformTransactionManager::processCommit tries to release the savepoint as soon as it detects that there is a savepoint without further checking whether the underlying database actually supports releasing a savepoint. Instead of trying to handle or swallow this exception it could also be reasonable to only call status.releaseHeldSavepoint() if this feature is supported by the database. While Oracle databases support the creation of savepoints, they don't support the explicit premature release of savepoints (this may or may not apply to other databases as well).

@bclozel bclozel added the in: data Issues in data modules (jdbc, orm, oxm, tx) label Nov 29, 2024
@jhoeller jhoeller added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Nov 29, 2024
@jhoeller jhoeller self-assigned this Nov 29, 2024
@jhoeller jhoeller added this to the 6.2.1 milestone Nov 29, 2024
@jhoeller jhoeller changed the title Spring Boot 3.4.0 Nested transaction support is broken in Oracle database. Nested transaction support via savepoints is broken in Oracle database Nov 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

5 participants