diff --git a/framework-docs/modules/ROOT/pages/data-access/dao.adoc b/framework-docs/modules/ROOT/pages/data-access/dao.adoc index 4c2844e9549..9ca9666f541 100644 --- a/framework-docs/modules/ROOT/pages/data-access/dao.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/dao.adoc @@ -151,10 +151,10 @@ Kotlin:: ---- ====== -The last example we show here is for typical JDBC support. You could have the -`DataSource` injected into an initialization method or a constructor, where you would create a -`JdbcTemplate` and other data access support classes (such as `SimpleJdbcCall` and others) by using -this `DataSource`. The following example autowires a `DataSource`: +The last example we show here is for typical JDBC support. You could have the `DataSource` +injected into an initialization method or a constructor, where you would create a `JdbcTemplate` +and other data access support classes (such as `SimpleJdbcCall` and others) by using this +`DataSource`. The following example autowires a `DataSource`: [tabs] ====== diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc index ce43becb612..04533c9cb82 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc @@ -9,13 +9,13 @@ to the database. [[jdbc-batch-classic]] == Basic Batch Operations with `JdbcTemplate` -You accomplish `JdbcTemplate` batch processing by implementing two methods of a special -interface, `BatchPreparedStatementSetter`, and passing that implementation in as the second parameter +You accomplish `JdbcTemplate` batch processing by implementing two methods of a special interface, +`BatchPreparedStatementSetter`, and passing that implementation in as the second parameter in your `batchUpdate` method call. You can use the `getBatchSize` method to provide the size of the current batch. You can use the `setValues` method to set the values for the parameters of -the prepared statement. This method is called the number of times that you -specified in the `getBatchSize` call. The following example updates the `t_actor` table -based on entries in a list, and the entire list is used as the batch: +the prepared statement. This method is called the number of times that you specified in the +`getBatchSize` call. The following example updates the `t_actor` table based on entries in a list, +and the entire list is used as the batch: [tabs] ====== diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc index e8e60ac527f..7ebb4a44a68 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/connections.adoc @@ -10,7 +10,7 @@ This section covers: * xref:data-access/jdbc/connections.adoc#jdbc-SingleConnectionDataSource[Using `SingleConnectionDataSource`] * xref:data-access/jdbc/connections.adoc#jdbc-DriverManagerDataSource[Using `DriverManagerDataSource`] * xref:data-access/jdbc/connections.adoc#jdbc-TransactionAwareDataSourceProxy[Using `TransactionAwareDataSourceProxy`] -* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager`] +* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager` / `JdbcTransactionManager`] [[jdbc-datasource]] @@ -125,8 +125,12 @@ The following example shows C3P0 configuration: == Using `DataSourceUtils` The `DataSourceUtils` class is a convenient and powerful helper class that provides -`static` methods to obtain connections from JNDI and close connections if necessary. It -supports thread-bound connections with, for example, `DataSourceTransactionManager`. +`static` methods to obtain connections from JNDI and close connections if necessary. +It supports a thread-bound JDBC `Connection` with `DataSourceTransactionManager` but +also with `JtaTransactionManager` and `JpaTransactionManager`. + +Note that `JdbcTemplate` implies `DataSourceUtils` connection access, using it +behind every JDBC operation, implicitly participating in an ongoing transaction. [[jdbc-SmartDataSource]] @@ -165,7 +169,6 @@ In contrast to `DriverManagerDataSource`, it reuses the same connection all the avoiding excessive creation of physical connections. - [[jdbc-DriverManagerDataSource]] == Using `DriverManagerDataSource` @@ -201,29 +204,44 @@ javadoc for more details. [[jdbc-DataSourceTransactionManager]] -== Using `DataSourceTransactionManager` +== Using `DataSourceTransactionManager` / `JdbcTransactionManager` The `DataSourceTransactionManager` class is a `PlatformTransactionManager` -implementation for single JDBC data sources. It binds a JDBC connection from the -specified data source to the currently executing thread, potentially allowing for one -thread connection per data source. +implementation for a single JDBC `DataSource`. It binds a JDBC `Connection` +from the specified `DataSource` to the currently executing thread, potentially +allowing for one thread-bound `Connection` per `DataSource`. -Application code is required to retrieve the JDBC connection through -`DataSourceUtils.getConnection(DataSource)` instead of Jakarta EE's standard +Application code is required to retrieve the JDBC `Connection` through +`DataSourceUtils.getConnection(DataSource)` instead of Java EE's standard `DataSource.getConnection`. It throws unchecked `org.springframework.dao` exceptions -instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use this -strategy implicitly. If not used with this transaction manager, the lookup strategy -behaves exactly like the common one. Thus, it can be used in any case. +instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use +this strategy implicitly. If not used with a transaction manager, the lookup strategy +behaves exactly like `DataSource.getConnection` and can therefore be used in any case. -The `DataSourceTransactionManager` class supports custom isolation levels and timeouts -that get applied as appropriate JDBC statement query timeouts. To support the latter, -application code must either use `JdbcTemplate` or call the -`DataSourceUtils.applyTransactionTimeout(..)` method for each created statement. +The `DataSourceTransactionManager` class supports savepoints (`PROPAGATION_NESTED`), +custom isolation levels, and timeouts that get applied as appropriate JDBC statement +query timeouts. To support the latter, application code must either use `JdbcTemplate` or +call the `DataSourceUtils.applyTransactionTimeout(..)` method for each created statement. -You can use this implementation instead of `JtaTransactionManager` in the single-resource -case, as it does not require the container to support JTA. Switching between -both is just a matter of configuration, provided you stick to the required connection lookup -pattern. JTA does not support custom isolation levels. +You can use `DataSourceTransactionManager` instead of `JtaTransactionManager` in the +single-resource case, as it does not require the container to support a JTA transaction +coordinator. Switching between these transaction managers is just a matter of configuration, +provided you stick to the required connection lookup pattern. Note that JTA does not support +savepoints or custom isolation levels and has a different timeout mechanism but otherwise +exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management. + +NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds +exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`). +Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException` +(analogous to JTA), `JdbcTransactionManager` translates database locking failures etc to +corresponding `DataAccessException` subclasses. Note that application code needs to be +prepared for such exceptions, not exclusively expecting `TransactionSystemException`. +In scenarios where that is the case, `JdbcTransactionManager` is the recommended choice. + +In terms of exception behavior, `JdbcTransactionManager` is roughly equivalent to +`JpaTransactionManager` and also to `R2dbcTransactionManager`, serving as an immediate +companion/replacement for each other. `DataSourceTransactionManager` on the other hand +is equivalent to `JtaTransactionManager` and can serve as a direct replacement there. diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc index 31bbecce2f2..4112a918a47 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc @@ -718,12 +718,22 @@ See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` between ``SQLException``s and Spring's own `org.springframework.dao.DataAccessException`, which is agnostic in regard to data access strategy. Implementations can be generic (for example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error -codes) for greater precision. +codes) for greater precision. This exception translation mechanism is used behind the +the common `JdbcTemplate` and `JdbcTransactionManager` entry points which do not +propagate `SQLException` but rather `DataAccessException`. + +NOTE: As of 6.0, the default exception translator is `SQLExceptionSubclassTranslator`, +detecting JDBC 4 `SQLException` subclasses with a few extra checks, and with a fallback +to `SQLState` introspection through `SQLStateSQLExceptionTranslator`. This is usually +sufficient for common database access and does not require vendor-specific detection. +For backwards compatibility, consider using `SQLErrorCodeSQLExceptionTranslator` as +described below, potentially with custom error code mappings. `SQLErrorCodeSQLExceptionTranslator` is the implementation of `SQLExceptionTranslator` -that is used by default. This implementation uses specific vendor codes. It is more -precise than the `SQLState` implementation. The error code translations are based on -codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and +that is used by default when a file named `sql-error-codes.xml` is present in the root +of the classpath. This implementation uses specific vendor codes. It is more precise than +`SQLState` or `SQLException` subclass translation. The error code translations are based +on codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and populated by an `SQLErrorCodesFactory`, which (as the name suggests) is a factory for creating `SQLErrorCodes` based on the contents of a configuration file named `sql-error-codes.xml`. This file is populated with vendor codes and based on the @@ -744,8 +754,8 @@ The `SQLErrorCodeSQLExceptionTranslator` applies matching rules in the following translator. If this translation is not available, the next fallback translator is the `SQLStateSQLExceptionTranslator`. -NOTE: The `SQLErrorCodesFactory` is used by default to define `Error` codes and custom exception -translations. They are looked up in a file named `sql-error-codes.xml` from the +NOTE: The `SQLErrorCodesFactory` is used by default to define error codes and custom +exception translations. They are looked up in a file named `sql-error-codes.xml` from the classpath, and the matching `SQLErrorCodes` instance is located based on the database name from the database metadata of the database in use. @@ -784,12 +794,12 @@ Kotlin:: ---- ====== -In the preceding example, the specific error code (`-12345`) is translated, while other errors are -left to be translated by the default translator implementation. To use this custom -translator, you must pass it to the `JdbcTemplate` through the method -`setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the data access -processing where this translator is needed. The following example shows how you can use this custom -translator: +In the preceding example, the specific error code (`-12345`) is translated while +other errors are left to be translated by the default translator implementation. +To use this custom translator, you must pass it to the `JdbcTemplate` through the +method `setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the +data access processing where this translator is needed. The following example shows +how you can use this custom translator: [tabs] ====== @@ -800,7 +810,6 @@ Java:: private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { - // create a JdbcTemplate and set data source this.jdbcTemplate = new JdbcTemplate(); this.jdbcTemplate.setDataSource(dataSource); @@ -809,7 +818,6 @@ Java:: CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator(); tr.setDataSource(dataSource); this.jdbcTemplate.setExceptionTranslator(tr); - } public void updateShippingCharge(long orderId, long pct) { diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/packages.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/packages.adoc index e6719fec871..4eba396012b 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/packages.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/packages.adoc @@ -3,30 +3,30 @@ The Spring Framework's JDBC abstraction framework consists of four different packages: -* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class and its -various callback interfaces, plus a variety of related classes. A subpackage named -`org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and +* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class +and its various callback interfaces, plus a variety of related classes. A subpackage +named `org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and `SimpleJdbcCall` classes. Another subpackage named `org.springframework.jdbc.core.namedparam` contains the `NamedParameterJdbcTemplate` class and the related support classes. See xref:data-access/jdbc/core.adoc[Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling], xref:data-access/jdbc/advanced.adoc[JDBC Batch Operations], and xref:data-access/jdbc/simple.adoc[Simplifying JDBC Operations with the `SimpleJdbc` Classes]. -* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class for easy -`DataSource` access and various simple `DataSource` implementations that you can use for -testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage -named `org.springfamework.jdbc.datasource.embedded` provides support for creating +* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class +for easy `DataSource` access and various simple `DataSource` implementations that you can +use for testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage +named `org.springframework.jdbc.datasource.embedded` provides support for creating embedded databases by using Java database engines, such as HSQL, H2, and Derby. See xref:data-access/jdbc/connections.adoc[Controlling Database Connections] and xref:data-access/jdbc/embedded-database-support.adoc[Embedded Database Support]. -* `object`: The `org.springframework.jdbc.object` package contains classes that represent RDBMS -queries, updates, and stored procedures as thread-safe, reusable objects. See +* `object`: The `org.springframework.jdbc.object` package contains classes that represent +RDBMS queries, updates, and stored procedures as thread-safe, reusable objects. See xref:data-access/jdbc/object.adoc[Modeling JDBC Operations as Java Objects]. This approach is modeled by JDO, although objects returned by queries are naturally disconnected from the database. This higher-level of JDBC abstraction depends on the lower-level abstraction in the `org.springframework.jdbc.core` package. -* `support`: The `org.springframework.jdbc.support` package provides `SQLException` translation -functionality and some utility classes. Exceptions thrown during JDBC processing are -translated to exceptions defined in the `org.springframework.dao` package. This means +* `support`: The `org.springframework.jdbc.support` package provides `SQLException` +translation functionality and some utility classes. Exceptions thrown during JDBC processing +are translated to exceptions defined in the `org.springframework.dao` package. This means that code using the Spring JDBC abstraction layer does not need to implement JDBC or RDBMS-specific error handling. All translated exceptions are unchecked, which gives you the option of catching the exceptions from which you can recover while letting other diff --git a/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc b/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc index 0f60323febc..9dbf42a56d7 100644 --- a/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc @@ -495,7 +495,7 @@ features supported by Spring, usually in a vendor-specific manner: * Applying specific transaction semantics (such as custom isolation level or transaction timeout) * Retrieving the transactional JDBC `Connection` (for exposure to JDBC-based DAOs) -* Advanced translation of `PersistenceExceptions` to Spring `DataAccessExceptions` +* Advanced translation of `PersistenceException` to Spring's `DataAccessException` This is particularly valuable for special transaction semantics and for advanced translation of exception. The default implementation (`DefaultJpaDialect`) does diff --git a/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc b/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc index f5735720ad5..65e30ce8ae0 100644 --- a/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc @@ -718,19 +718,15 @@ javadoc for more details. === Using `R2dbcTransactionManager` The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for -single R2DBC data sources. It binds an R2DBC connection from the specified connection factory -to the subscriber `Context`, potentially allowing for one subscriber connection for each -connection factory. +a single R2DBC `ConnectionFactory`. It binds an R2DBC `Connection` from the specified +`ConnectionFactory` to the subscriber `Context`, potentially allowing for one subscriber +`Connection` for each `ConnectionFactory`. -Application code is required to retrieve the R2DBC connection through +Application code is required to retrieve the R2DBC `Connection` through `ConnectionFactoryUtils.getConnection(ConnectionFactory)`, instead of R2DBC's standard -`ConnectionFactory.create()`. - -All framework classes (such as `DatabaseClient`) use this strategy implicitly. -If not used with this transaction manager, the lookup strategy behaves exactly like the common one. -Thus, it can be used in any case. - -The `R2dbcTransactionManager` class supports custom isolation levels that get applied to the connection. +`ConnectionFactory.create()`. All framework classes (such as `DatabaseClient`) use this +strategy implicitly. If not used with a transaction manager, the lookup strategy behaves +exactly like `ConnectionFactory.create()` and can therefore be used in any case. diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc index a43d6e06fb4..8a762b791c5 100644 --- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc @@ -124,7 +124,6 @@ In XML configuration, the `` tag provides similar conveni ---- <1> The line that makes the bean instance transactional. - TIP: You can omit the `transaction-manager` attribute in the `` tag if the bean name of the `TransactionManager` that you want to wire in has the name `transactionManager`. If the `TransactionManager` bean that you want to dependency-inject @@ -522,17 +521,17 @@ The following listing shows the bean declarations: ---- - + ... - + ... - + ... diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java index 9fe2e1140e6..26e6bc75477 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java @@ -30,7 +30,6 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; import org.springframework.transaction.reactive.AbstractReactiveTransactionManager; import org.springframework.transaction.reactive.GenericReactiveTransaction; import org.springframework.transaction.reactive.TransactionSynchronizationManager; @@ -170,7 +169,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager } @Override - protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager) throws TransactionException { + protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager) { ConnectionFactoryTransactionObject txObject = new ConnectionFactoryTransactionObject(); ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(obtainConnectionFactory()); txObject.setConnectionHolder(conHolder, false); @@ -184,7 +183,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager @Override protected Mono doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction, - TransactionDefinition definition) throws TransactionException { + TransactionDefinition definition) { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; @@ -275,9 +274,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager } @Override - protected Mono doSuspend(TransactionSynchronizationManager synchronizationManager, Object transaction) - throws TransactionException { - + protected Mono doSuspend(TransactionSynchronizationManager synchronizationManager, Object transaction) { return Mono.defer(() -> { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; txObject.setConnectionHolder(null); @@ -287,7 +284,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager @Override protected Mono doResume(TransactionSynchronizationManager synchronizationManager, - @Nullable Object transaction, Object suspendedResources) throws TransactionException { + @Nullable Object transaction, Object suspendedResources) { return Mono.defer(() -> { synchronizationManager.bindResource(obtainConnectionFactory(), suspendedResources); @@ -297,7 +294,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager @Override protected Mono doCommit(TransactionSynchronizationManager TransactionSynchronizationManager, - GenericReactiveTransaction status) throws TransactionException { + GenericReactiveTransaction status) { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); if (status.isDebug()) { @@ -309,7 +306,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager @Override protected Mono doRollback(TransactionSynchronizationManager TransactionSynchronizationManager, - GenericReactiveTransaction status) throws TransactionException { + GenericReactiveTransaction status) { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); if (status.isDebug()) { @@ -321,7 +318,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager @Override protected Mono doSetRollbackOnly(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException { + GenericReactiveTransaction status) { return Mono.fromRunnable(() -> { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java index f15e2f3700c..6122d0906d4 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,10 +31,10 @@ import org.springframework.lang.Nullable; * template methods for specific states of the underlying transaction, * for example: begin, suspend, resume, commit. * - *

The default implementations of this strategy interface are - * {@link org.springframework.transaction.jta.JtaTransactionManager} and - * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}, - * which can serve as an implementation guide for other transaction strategies. + *

A classic implementation of this strategy interface is + * {@link org.springframework.transaction.jta.JtaTransactionManager}. However, + * in common single-resource scenarios, Spring's specific transaction managers + * for e.g. JDBC, JPA, JMS are preferred choices. * * @author Rod Johnson * @author Juergen Hoeller @@ -81,12 +81,9 @@ public interface PlatformTransactionManager extends TransactionManager { *

Note that when the commit call completes, no matter if normally or * throwing an exception, the transaction must be fully completed and * cleaned up. No rollback call should be expected in such a case. - *

If this method throws an exception other than a TransactionException, - * then some before-commit error caused the commit attempt to fail. For - * example, an O/R Mapping tool might have tried to flush changes to the - * database right before commit, with the resulting DataAccessException - * causing the transaction to fail. The original exception will be - * propagated to the caller of this commit method in such a case. + *

Depending on the concrete transaction manager setup, {@code commit} + * may propagate {@link org.springframework.dao.DataAccessException} as well, + * either from before-commit flushes or from the actual commit step. * @param status object returned by the {@code getTransaction} method * @throws UnexpectedRollbackException in case of an unexpected rollback * that the transaction coordinator initiated @@ -110,6 +107,8 @@ public interface PlatformTransactionManager extends TransactionManager { * The transaction will already have been completed and cleaned up when commit * returns, even in case of a commit exception. Consequently, a rollback call * after commit failure will lead to an IllegalTransactionStateException. + *

Depending on the concrete transaction manager setup, {@code rollback} + * may propagate {@link org.springframework.dao.DataAccessException} as well. * @param status object returned by the {@code getTransaction} method * @throws TransactionSystemException in case of rollback or system errors * (typically caused by fundamental resource failures) diff --git a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java index 64341ebb031..d55a13472dd 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/ReactiveTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,8 @@ public interface ReactiveTransactionManager extends TransactionManager { *

An exception to the above rule is the read-only flag, which should be * ignored if no explicit read-only mode is supported. Essentially, the * read-only flag is just a hint for potential optimization. + *

Note: In contrast to {@link PlatformTransactionManager}, exceptions + * are propagated through the reactive pipeline returned from this method. * @param definition the TransactionDefinition instance, * describing propagation behavior, isolation level, timeout etc. * @return transaction status object representing the new or current transaction @@ -58,8 +60,7 @@ public interface ReactiveTransactionManager extends TransactionManager { * @see TransactionDefinition#getTimeout * @see TransactionDefinition#isReadOnly */ - Mono getReactiveTransaction(@Nullable TransactionDefinition definition) - throws TransactionException; + Mono getReactiveTransaction(@Nullable TransactionDefinition definition); /** * Commit the given transaction, with regard to its status. If the transaction @@ -69,14 +70,12 @@ public interface ReactiveTransactionManager extends TransactionManager { * has been suspended to be able to create a new one, resume the previous * transaction after committing the new one. *

Note that when the commit call completes, no matter if normally or - * throwing an exception, the transaction must be fully completed and + * propagating an exception, the transaction must be fully completed and * cleaned up. No rollback call should be expected in such a case. - *

If this method throws an exception other than a TransactionException, - * then some before-commit error caused the commit attempt to fail. For - * example, an O/R Mapping tool might have tried to flush changes to the - * database right before commit, with the resulting DataAccessException - * causing the transaction to fail. The original exception will be - * propagated to the caller of this commit method in such a case. + *

Note: In contrast to {@link PlatformTransactionManager}, exceptions + * are propagated through the reactive pipeline returned from this method. + * Also, depending on the transaction manager implementation, {@code commit} + * may propagate {@link org.springframework.dao.DataAccessException} as well. * @param transaction object returned by the {@code getTransaction} method * @throws UnexpectedRollbackException in case of an unexpected rollback * that the transaction coordinator initiated @@ -88,7 +87,7 @@ public interface ReactiveTransactionManager extends TransactionManager { * is already completed (that is, committed or rolled back) * @see ReactiveTransaction#setRollbackOnly */ - Mono commit(ReactiveTransaction transaction) throws TransactionException; + Mono commit(ReactiveTransaction transaction); /** * Perform a rollback of the given transaction. @@ -96,16 +95,20 @@ public interface ReactiveTransactionManager extends TransactionManager { * participation in the surrounding transaction. If a previous transaction * has been suspended to be able to create a new one, resume the previous * transaction after rolling back the new one. - *

Do not call rollback on a transaction if commit threw an exception. + *

Do not call rollback on a transaction if commit failed. * The transaction will already have been completed and cleaned up when commit * returns, even in case of a commit exception. Consequently, a rollback call * after commit failure will lead to an IllegalTransactionStateException. + *

Note: In contrast to {@link PlatformTransactionManager}, exceptions + * are propagated through the reactive pipeline returned from this method. + * Also, depending on the transaction manager implementation, {@code rollback} + * may propagate {@link org.springframework.dao.DataAccessException} as well. * @param transaction object returned by the {@code getTransaction} method * @throws TransactionSystemException in case of rollback or system errors * (typically caused by fundamental resource failures) * @throws IllegalTransactionStateException if the given transaction * is already completed (that is, committed or rolled back) */ - Mono rollback(ReactiveTransaction transaction) throws TransactionException; + Mono rollback(ReactiveTransaction transaction); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java index 3afa07db8a9..ebeeeb5f201 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/reactive/AbstractReactiveTransactionManager.java @@ -95,9 +95,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @see #doBegin */ @Override - public final Mono getReactiveTransaction(@Nullable TransactionDefinition definition) - throws TransactionException { - + public final Mono getReactiveTransaction(@Nullable TransactionDefinition definition) { // Use defaults if no transaction definition given. TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); @@ -165,7 +163,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * Create a ReactiveTransaction for an existing transaction. */ private Mono handleExistingTransaction(TransactionSynchronizationManager synchronizationManager, - TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { + TransactionDefinition definition, Object transaction, boolean debugEnabled) { if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { return Mono.error(new IllegalTransactionStateException( @@ -277,7 +275,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @see #resume */ private Mono suspend(TransactionSynchronizationManager synchronizationManager, - @Nullable Object transaction) throws TransactionException { + @Nullable Object transaction) { if (synchronizationManager.isSynchronizationActive()) { Mono> suspendedSynchronizations = doSuspendSynchronization(synchronizationManager); @@ -325,8 +323,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @see #suspend */ private Mono resume(TransactionSynchronizationManager synchronizationManager, - @Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) - throws TransactionException { + @Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) { Mono resume = Mono.empty(); @@ -403,7 +400,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @see #rollback */ @Override - public final Mono commit(ReactiveTransaction transaction) throws TransactionException { + public final Mono commit(ReactiveTransaction transaction) { if (transaction.isCompleted()) { return Mono.error(new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction")); @@ -426,10 +423,9 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * Rollback-only flags have already been checked and applied. * @param synchronizationManager the synchronization manager bound to the current transaction * @param status object representing the transaction - * @throws TransactionException in case of commit failure */ private Mono processCommit(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException { + GenericReactiveTransaction status) { AtomicBoolean beforeCompletionInvoked = new AtomicBoolean(); @@ -489,7 +485,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @see #doSetRollbackOnly */ @Override - public final Mono rollback(ReactiveTransaction transaction) throws TransactionException { + public final Mono rollback(ReactiveTransaction transaction) { if (transaction.isCompleted()) { return Mono.error(new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction")); @@ -505,7 +501,6 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * The completed flag has already been checked. * @param synchronizationManager the synchronization manager bound to the current transaction * @param status object representing the transaction - * @throws TransactionException in case of rollback failure */ private Mono processRollback(TransactionSynchronizationManager synchronizationManager, GenericReactiveTransaction status) { @@ -544,11 +539,10 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @param synchronizationManager the synchronization manager bound to the current transaction * @param status object representing the transaction * @param ex the thrown application exception or error - * @throws TransactionException in case of rollback failure * @see #doRollback */ private Mono doRollbackOnCommitException(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status, Throwable ex) throws TransactionException { + GenericReactiveTransaction status, Throwable ex) { return Mono.defer(() -> { if (status.isNewTransaction()) { @@ -716,14 +710,12 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @return the current transaction object * @throws org.springframework.transaction.CannotCreateTransactionException * if transaction support is not available - * @throws TransactionException in case of lookup or system errors * @see #doBegin * @see #doCommit * @see #doRollback * @see GenericReactiveTransaction#getTransaction */ - protected abstract Object doGetTransaction(TransactionSynchronizationManager synchronizationManager) - throws TransactionException; + protected abstract Object doGetTransaction(TransactionSynchronizationManager synchronizationManager); /** * Check if the given transaction object indicates an existing transaction @@ -737,10 +729,9 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * Subclasses are of course encouraged to provide such support. * @param transaction the transaction object returned by doGetTransaction * @return if there is an existing transaction - * @throws TransactionException in case of system errors * @see #doGetTransaction */ - protected boolean isExistingTransaction(Object transaction) throws TransactionException { + protected boolean isExistingTransaction(Object transaction) { return false; } @@ -759,12 +750,11 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @param transaction the transaction object returned by {@code doGetTransaction} * @param definition a TransactionDefinition instance, describing propagation * behavior, isolation level, read-only flag, timeout, and transaction name - * @throws TransactionException in case of creation or system errors * @throws org.springframework.transaction.NestedTransactionNotSupportedException * if the underlying transaction does not support nesting (e.g. through savepoints) */ protected abstract Mono doBegin(TransactionSynchronizationManager synchronizationManager, - Object transaction, TransactionDefinition definition) throws TransactionException; + Object transaction, TransactionDefinition definition); /** * Suspend the resources of the current transaction. @@ -777,11 +767,10 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * (will be kept unexamined for passing it into doResume) * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException * if suspending is not supported by the transaction manager implementation - * @throws TransactionException in case of system errors * @see #doResume */ protected Mono doSuspend(TransactionSynchronizationManager synchronizationManager, - Object transaction) throws TransactionException { + Object transaction) { throw new TransactionSuspensionNotSupportedException( "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); @@ -798,11 +787,10 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * as returned by doSuspend * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException * if suspending is not supported by the transaction manager implementation - * @throws TransactionException in case of system errors * @see #doSuspend */ protected Mono doResume(TransactionSynchronizationManager synchronizationManager, - @Nullable Object transaction, Object suspendedResources) throws TransactionException { + @Nullable Object transaction, Object suspendedResources) { throw new TransactionSuspensionNotSupportedException( "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); @@ -832,11 +820,10 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * contained in the passed-in status. * @param synchronizationManager the synchronization manager bound to the current transaction * @param status the status representation of the transaction - * @throws TransactionException in case of commit or system errors * @see GenericReactiveTransaction#getTransaction */ protected abstract Mono doCommit(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException; + GenericReactiveTransaction status); /** * Perform an actual rollback of the given transaction. @@ -845,11 +832,10 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * will be performed on the transaction object contained in the passed-in status. * @param synchronizationManager the synchronization manager bound to the current transaction * @param status the status representation of the transaction - * @throws TransactionException in case of system errors * @see GenericReactiveTransaction#getTransaction */ protected abstract Mono doRollback(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException; + GenericReactiveTransaction status); /** * Set the given transaction rollback-only. Only called on rollback @@ -859,10 +845,9 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * supported. Subclasses are of course encouraged to provide such support. * @param synchronizationManager the synchronization manager bound to the current transaction * @param status the status representation of the transaction - * @throws TransactionException in case of system errors */ protected Mono doSetRollbackOnly(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException { + GenericReactiveTransaction status) { throw new IllegalTransactionStateException( "Participating in existing transactions is not supported - when 'isExistingTransaction' " + @@ -880,13 +865,12 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran * @param synchronizationManager the synchronization manager bound to the current transaction * @param transaction the transaction object returned by {@code doGetTransaction} * @param synchronizations a List of TransactionSynchronization objects - * @throws TransactionException in case of system errors * @see #invokeAfterCompletion(TransactionSynchronizationManager, List, int) * @see TransactionSynchronization#afterCompletion(int) * @see TransactionSynchronization#STATUS_UNKNOWN */ protected Mono registerAfterCompletionWithExistingTransaction(TransactionSynchronizationManager synchronizationManager, - Object transaction, List synchronizations) throws TransactionException { + Object transaction, List synchronizations) { logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'");