From 086c5a1d2d6438652bb069309159def3ffb0aefd Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 21 Jul 2020 14:21:43 +0200 Subject: [PATCH] #368 - Use Spring R2DBC. We now use Spring R2DBC DatabaseClient and utilities to implement Spring Data R2DBC functionality. Original pull request: #412. --- src/main/asciidoc/new-features.adoc | 1 + .../asciidoc/reference/r2dbc-upgrading.adoc | 10 + .../config/AbstractR2dbcConfiguration.java | 39 +- .../ConnectionFactoryUtils.java | 218 +------ .../R2dbcTransactionManager.java | 532 +----------------- .../r2dbc/convert/MappingR2dbcConverter.java | 11 +- .../data/r2dbc/core/DatabaseClient.java | 8 + .../r2dbc/core/DefaultDatabaseClient.java | 12 +- .../DefaultReactiveDataAccessStrategy.java | 16 +- .../r2dbc/core/R2dbcEntityOperations.java | 1 + .../data/r2dbc/core/R2dbcEntityTemplate.java | 203 ++++++- .../core/ReactiveDataAccessStrategy.java | 5 +- .../core/ReactiveSelectOperationSupport.java | 1 + .../data/r2dbc/core/StatementMapper.java | 37 +- .../data/r2dbc/mapping/OutboundRow.java | 35 +- .../data/r2dbc/mapping/SettableValue.java | 8 + .../repository/query/AbstractR2dbcQuery.java | 51 +- .../r2dbc/repository/query/BindableQuery.java | 8 +- .../ExpressionEvaluatingParameterBinder.java | 28 +- .../repository/query/PartTreeR2dbcQuery.java | 2 +- .../query/PreparedOperationBindableQuery.java | 23 +- .../repository/query/R2dbcQueryExecution.java | 2 +- .../query/StringBasedR2dbcQuery.java | 9 +- .../support/R2dbcRepositoryFactory.java | 1 + .../support/R2dbcRepositoryFactoryBean.java | 1 + .../support/SimpleR2dbcRepository.java | 27 +- .../data/r2dbc/config/H2IntegrationTests.java | 7 +- .../R2dbcConfigurationIntegrationTests.java | 2 +- .../R2dbcTransactionManagerUnitTests.java | 488 ---------------- ...eConnectionConnectionFactoryUnitTests.java | 17 - .../MappingR2dbcConverterUnitTests.java | 26 +- ...ostgresMappingR2dbcConverterUnitTests.java | 4 +- ...ctionalDatabaseClientIntegrationTests.java | 17 +- ...ctionalDatabaseClientIntegrationTests.java | 3 +- ...ctionalDatabaseClientIntegrationTests.java | 3 +- ...ctionalDatabaseClientIntegrationTests.java | 3 +- ...stgresReactiveDataAccessStrategyTests.java | 13 +- .../core/R2dbcEntityTemplateUnitTests.java | 43 +- ...ReactiveDataAccessStrategyTestSupport.java | 4 +- .../ReactiveDeleteOperationUnitTests.java | 3 +- .../ReactiveInsertOperationUnitTests.java | 3 +- .../ReactiveUpdateOperationUnitTests.java | 6 +- ...ertingR2dbcRepositoryIntegrationTests.java | 5 +- .../R2dbcRepositoriesRegistrarTests.java | 2 +- .../query/PartTreeR2dbcQueryUnitTests.java | 15 +- ...eparedOperationBindableQueryUnitTests.java | 10 +- .../query/StringBasedR2dbcQueryUnitTests.java | 4 +- ...SimpleR2dbcRepositoryIntegrationTests.java | 2 +- .../R2dbcRepositoryFactoryUnitTests.java | 2 +- .../data/r2dbc/testing/StatementRecorder.java | 14 +- 50 files changed, 535 insertions(+), 1450 deletions(-) delete mode 100644 src/test/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManagerUnitTests.java diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index d4bb2a1..0bf96c1 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -4,6 +4,7 @@ [[new-features.1-2-0]] == What's New in Spring Data R2DBC 1.2.0 +* Deprecate Spring Data R2DBC `DatabaseClient` and move off deprecated API in favor of Spring R2DBC. Consult the <> for further details. * Support for <>. * <> through `@EnableR2dbcAuditing`. diff --git a/src/main/asciidoc/reference/r2dbc-upgrading.adoc b/src/main/asciidoc/reference/r2dbc-upgrading.adoc index 2bda475..0d22916 100644 --- a/src/main/asciidoc/reference/r2dbc-upgrading.adoc +++ b/src/main/asciidoc/reference/r2dbc-upgrading.adoc @@ -1,4 +1,5 @@ [appendix] +[[migration-guide]] = Migration Guide The following sections explain how to migrate to a newer version of Spring Data R2DBC. @@ -12,6 +13,10 @@ Spring Framework 5.3 ships with a new module: Spring R2DBC. `spring-r2dbc` ships core R2DBC functionality (a slim variant of `DatabaseClient`, Transaction Manager, Connection Factory initialization, Exception translation) that was initially provided by Spring Data R2DBC. The 1.2.0 release aligns with what's provided in Spring R2DBC by making several changes outlined in the following sections. +Spring R2DBC's `DatabaseClient` is a more lightweight implementation that encapsulates a pure SQL-oriented interface. +You will notice that the method to run SQL statements changed from `DatabaseClient.execute(…)` to `DatabaseClient.sql(…)`. +The fluent API for CRUD operations has moved into `R2dbcEntityTemplate`. + [[upgrading.1.1-1.2.deprecation]] === Deprecations @@ -40,6 +45,11 @@ Specifically the following classes are affected: We recommend that you review your imports if you work with these types directly. +=== Breaking Changes + +* `OutboundRow` and statement mappers switched from using `SettableValue` to `Parameter` +* Repository factory support requires `o.s.r2dbc.core.DatabaseClient` instead of `o.s.data.r2dbc.core.DatabaseClient`. + [[upgrading.1.1-1.2.dependencies]] === Dependency Changes diff --git a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java index 379c5df..019aed3 100644 --- a/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java +++ b/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java @@ -30,19 +30,18 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.CustomConversions.StoreConversions; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.DialectResolver; import org.springframework.data.r2dbc.dialect.R2dbcDialect; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; -import org.springframework.data.r2dbc.support.R2dbcExceptionSubclassTranslator; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; /** @@ -98,24 +97,34 @@ public abstract class AbstractR2dbcConfiguration implements ApplicationContextAw * @throws IllegalArgumentException if any of the required args is {@literal null}. */ @Bean({ "r2dbcDatabaseClient", "databaseClient" }) - public DatabaseClient databaseClient(ReactiveDataAccessStrategy dataAccessStrategy) { + public DatabaseClient databaseClient() { - Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!"); - - SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); - if (context != null) { - projectionFactory.setBeanFactory(context); - projectionFactory.setBeanClassLoader(context.getClassLoader()); - } + ConnectionFactory connectionFactory = lookupConnectionFactory(); return DatabaseClient.builder() // - .connectionFactory(lookupConnectionFactory()) // - .dataAccessStrategy(dataAccessStrategy) // - .exceptionTranslator(new R2dbcExceptionSubclassTranslator()) // - .projectionFactory(projectionFactory) // + .connectionFactory(connectionFactory) // + .bindMarkers(getDialect(connectionFactory).getBindMarkersFactory()) // .build(); } + /** + * Register {@link R2dbcEntityTemplate} using {@link #databaseClient()} and {@link #connectionFactory()}. + * + * @param databaseClient must not be {@literal null}. + * @param dataAccessStrategy must not be {@literal null}. + * @return + * @since 1.2 + */ + @Bean + public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient, + ReactiveDataAccessStrategy dataAccessStrategy) { + + Assert.notNull(databaseClient, "DatabaseClient must not be null!"); + Assert.notNull(dataAccessStrategy, "ReactiveDataAccessStrategy must not be null!"); + + return new R2dbcEntityTemplate(databaseClient, dataAccessStrategy); + } + /** * Register a {@link R2dbcMappingContext} and apply an optional {@link NamingStrategy}. * diff --git a/src/main/java/org/springframework/data/r2dbc/connectionfactory/ConnectionFactoryUtils.java b/src/main/java/org/springframework/data/r2dbc/connectionfactory/ConnectionFactoryUtils.java index 731e425..9cb1107 100644 --- a/src/main/java/org/springframework/data/r2dbc/connectionfactory/ConnectionFactoryUtils.java +++ b/src/main/java/org/springframework/data/r2dbc/connectionfactory/ConnectionFactoryUtils.java @@ -22,11 +22,8 @@ import reactor.core.publisher.Mono; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.core.Ordered; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.lang.Nullable; -import org.springframework.transaction.NoTransactionException; -import org.springframework.transaction.reactive.TransactionSynchronization; import org.springframework.transaction.reactive.TransactionSynchronizationManager; import org.springframework.util.Assert; @@ -68,8 +65,7 @@ public abstract class ConnectionFactoryUtils { * @see #releaseConnection */ public static Mono getConnection(ConnectionFactory connectionFactory) { - return doGetConnection(connectionFactory) - .onErrorMap(e -> new DataAccessResourceFailureException("Failed to obtain R2DBC Connection", e)); + return org.springframework.r2dbc.connection.ConnectionFactoryUtils.getConnection(connectionFactory); } /** @@ -83,64 +79,7 @@ public abstract class ConnectionFactoryUtils { * @return a R2DBC {@link io.r2dbc.spi.Connection} from the given {@link io.r2dbc.spi.ConnectionFactory}. */ public static Mono doGetConnection(ConnectionFactory connectionFactory) { - - Assert.notNull(connectionFactory, "ConnectionFactory must not be null!"); - - return TransactionSynchronizationManager.forCurrentTransaction().flatMap(synchronizationManager -> { - - ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(connectionFactory); - if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { - conHolder.requested(); - if (!conHolder.hasConnection()) { - - if (logger.isDebugEnabled()) { - logger.debug("Fetching resumed R2DBC Connection from ConnectionFactory"); - } - return fetchConnection(connectionFactory).doOnNext(conHolder::setConnection); - } - return Mono.just(conHolder.getConnection()); - } - // Else we either got no holder or an empty thread-bound holder here. - - if (logger.isDebugEnabled()) { - logger.debug("Fetching R2DBC Connection from ConnectionFactory"); - } - - Mono con = fetchConnection(connectionFactory); - - if (synchronizationManager.isSynchronizationActive()) { - - return con.flatMap(it -> { - - return Mono.just(it).doOnNext(conn -> { - - // Use same Connection for further R2DBC actions within the transaction. - // Thread-bound object will get removed by synchronization at transaction completion. - ConnectionHolder holderToUse = conHolder; - if (holderToUse == null) { - holderToUse = new ConnectionHolder(conn); - } else { - holderToUse.setConnection(conn); - } - holderToUse.requested(); - synchronizationManager - .registerSynchronization(new ConnectionSynchronization(holderToUse, connectionFactory)); - holderToUse.setSynchronizedWithTransaction(true); - if (holderToUse != conHolder) { - synchronizationManager.bindResource(connectionFactory, holderToUse); - } - }).onErrorResume(e -> { - // Unexpected exception from external delegation call -> close Connection and rethrow. - return releaseConnection(it, connectionFactory).then(Mono.error(e)); - }); - }); - } - - return con; - }) // - .onErrorResume(NoTransactionException.class, e -> { - return Mono.from(connectionFactory.create()); - }); + return org.springframework.r2dbc.connection.ConnectionFactoryUtils.doGetConnection(connectionFactory); } /** @@ -183,17 +122,8 @@ public abstract class ConnectionFactoryUtils { public static Mono doReleaseConnection(io.r2dbc.spi.Connection connection, ConnectionFactory connectionFactory) { - return TransactionSynchronizationManager.forCurrentTransaction().flatMap(it -> { - - ConnectionHolder conHolder = (ConnectionHolder) it.getResource(connectionFactory); - if (conHolder != null && connectionEquals(conHolder, connection)) { - // It's the transactional Connection: Don't close it. - conHolder.released(); - } - return Mono.from(connection.close()); - }).onErrorResume(NoTransactionException.class, e -> { - return doCloseConnection(connection, connectionFactory); - }); + return org.springframework.r2dbc.connection.ConnectionFactoryUtils.doReleaseConnection(connection, + connectionFactory); } /** @@ -246,37 +176,7 @@ public abstract class ConnectionFactoryUtils { */ public static Mono currentConnectionFactory(ConnectionFactory connectionFactory) { - return TransactionSynchronizationManager.forCurrentTransaction() - .filter(TransactionSynchronizationManager::isSynchronizationActive).filter(it -> { - - ConnectionHolder conHolder = (ConnectionHolder) it.getResource(connectionFactory); - if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { - return true; - } - return false; - }).map(it -> connectionFactory); - } - - /** - * Determine whether the given two {@link io.r2dbc.spi.Connection}s are equal, asking the target - * {@link io.r2dbc.spi.Connection} in case of a proxy. Used to detect equality even if the user passed in a raw target - * Connection while the held one is a proxy. - * - * @param conHolder the {@link .ConnectionHolder} for the held {@link io.r2dbc.spi.Connection} (potentially a proxy). - * @param passedInCon the {@link io.r2dbc.spi.Connection} passed-in by the user (potentially a target - * {@link io.r2dbc.spi.Connection} without proxy). - * @return whether the given Connections are equal - * @see #getTargetConnection - */ - private static boolean connectionEquals(ConnectionHolder conHolder, Connection passedInCon) { - - if (!conHolder.hasConnection()) { - return false; - } - Connection heldCon = conHolder.getConnection(); - // Explicitly check for identity too: for Connection handles that do not implement - // "equals" properly). - return (heldCon == passedInCon || heldCon.equals(passedInCon) || getTargetConnection(heldCon).equals(passedInCon)); + return org.springframework.r2dbc.connection.ConnectionFactoryUtils.currentConnectionFactory(connectionFactory); } /** @@ -317,112 +217,4 @@ public abstract class ConnectionFactoryUtils { return order; } - /** - * Callback for resource cleanup at the end of a non-native R2DBC transaction. - */ - private static class ConnectionSynchronization implements TransactionSynchronization, Ordered { - - private final ConnectionHolder connectionHolder; - - private final ConnectionFactory connectionFactory; - - private int order; - - private boolean holderActive = true; - - ConnectionSynchronization(ConnectionHolder connectionHolder, ConnectionFactory connectionFactory) { - this.connectionHolder = connectionHolder; - this.connectionFactory = connectionFactory; - this.order = getConnectionSynchronizationOrder(connectionFactory); - } - - @Override - public int getOrder() { - return this.order; - } - - @Override - public Mono suspend() { - if (this.holderActive) { - - return TransactionSynchronizationManager.forCurrentTransaction().flatMap(it -> { - - it.unbindResource(this.connectionFactory); - if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) { - // Release Connection on suspend if the application doesn't keep - // a handle to it anymore. We will fetch a fresh Connection if the - // application accesses the ConnectionHolder again after resume, - // assuming that it will participate in the same transaction. - return releaseConnection(this.connectionHolder.getConnection(), this.connectionFactory) - .doOnTerminate(() -> this.connectionHolder.setConnection(null)); - } - return Mono.empty(); - }); - } - - return Mono.empty(); - } - - @Override - public Mono resume() { - if (this.holderActive) { - return TransactionSynchronizationManager.forCurrentTransaction().doOnNext(it -> { - it.bindResource(this.connectionFactory, this.connectionHolder); - }).then(); - } - return Mono.empty(); - } - - @Override - public Mono beforeCompletion() { - - // Release Connection early if the holder is not open anymore - // (that is, not used by another resource - // that has its own cleanup via transaction synchronization), - // to avoid issues with strict transaction implementations that expect - // the close call before transaction completion. - if (!this.connectionHolder.isOpen()) { - return TransactionSynchronizationManager.forCurrentTransaction().flatMap(it -> { - - it.unbindResource(this.connectionFactory); - this.holderActive = false; - if (this.connectionHolder.hasConnection()) { - return releaseConnection(this.connectionHolder.getConnection(), this.connectionFactory); - } - return Mono.empty(); - }); - } - - return Mono.empty(); - } - - @Override - public Mono afterCompletion(int status) { - - // If we haven't closed the Connection in beforeCompletion, - // close it now. The holder might have been used for other - // cleanup in the meantime, for example by a Hibernate Session. - if (this.holderActive) { - // The thread-bound ConnectionHolder might not be available anymore, - // since afterCompletion might get called from a different thread. - return TransactionSynchronizationManager.forCurrentTransaction().flatMap(it -> { - - it.unbindResourceIfPossible(this.connectionFactory); - this.holderActive = false; - if (this.connectionHolder.hasConnection()) { - return releaseConnection(this.connectionHolder.getConnection(), this.connectionFactory) - .doOnTerminate(() -> { - // Reset the ConnectionHolder: It might remain bound to the context. - this.connectionHolder.setConnection(null); - }); - } - - return Mono.empty(); - }); - } - - this.connectionHolder.reset(); - return Mono.empty(); - } - } } diff --git a/src/main/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManager.java b/src/main/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManager.java index b6324a1..686e121 100644 --- a/src/main/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManager.java +++ b/src/main/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManager.java @@ -17,27 +17,9 @@ package org.springframework.data.r2dbc.connectionfactory; import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.IsolationLevel; -import io.r2dbc.spi.R2dbcException; -import io.r2dbc.spi.Result; -import reactor.core.publisher.Mono; - -import java.time.Duration; import org.springframework.beans.factory.InitializingBean; -import org.springframework.dao.DataAccessException; import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.support.R2dbcExceptionSubclassTranslator; -import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; -import org.springframework.lang.Nullable; -import org.springframework.transaction.CannotCreateTransactionException; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.TransactionSystemException; -import org.springframework.transaction.reactive.AbstractReactiveTransactionManager; -import org.springframework.transaction.reactive.GenericReactiveTransaction; -import org.springframework.transaction.reactive.TransactionSynchronizationManager; -import org.springframework.util.Assert; /** * {@link org.springframework.transaction.ReactiveTransactionManager} implementation for a single R2DBC @@ -73,16 +55,12 @@ import org.springframework.util.Assert; * @see ConnectionFactoryUtils#releaseConnection * @see TransactionAwareConnectionFactoryProxy * @see DatabaseClient - * @deprecated since 1.2 in favor of Spring R2DBC. Use {@link org.springframework.r2dbc.connection} instead. + * @deprecated since 1.2 in favor of Spring R2DBC. Use + * {@link org.springframework.r2dbc.connection.R2dbcTransactionManager} instead. */ @Deprecated -public class R2dbcTransactionManager extends AbstractReactiveTransactionManager implements InitializingBean { - - private ConnectionFactory connectionFactory; - - private boolean enforceReadOnly = false; - - private R2dbcExceptionTranslator exceptionTranslator = new R2dbcExceptionSubclassTranslator(); +public class R2dbcTransactionManager extends org.springframework.r2dbc.connection.R2dbcTransactionManager + implements InitializingBean { /** * Create a new @link ConnectionFactoryTransactionManager} instance. A ConnectionFactory has to be set to be able to @@ -102,506 +80,4 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager setConnectionFactory(connectionFactory); afterPropertiesSet(); } - - /** - * Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions for. - *

- * This will typically be a locally defined {@link ConnectionFactory}, for example an connection pool. - *

- * The {@link ConnectionFactory} specified here should be the target {@link ConnectionFactory} to manage transactions - * for, not a TransactionAwareConnectionFactoryProxy. Only data access code may work with - * TransactionAwareConnectionFactoryProxy, while the transaction manager needs to work on the underlying target - * {@link ConnectionFactory}. If there's nevertheless a TransactionAwareConnectionFactoryProxy passed in, it will be - * unwrapped to extract its target {@link ConnectionFactory}. - *

- * The {@link ConnectionFactory} passed in here needs to return independent {@link Connection}s. The - * {@link Connection}s may come from a pool (the typical case), but the {@link ConnectionFactory} must not return - * scoped {@link Connection} or the like. - * - * @see TransactionAwareConnectionFactoryProxy - */ - public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - } - - /** - * Return the R2DBC {@link ConnectionFactory} that this instance manages transactions for. - */ - @Nullable - public ConnectionFactory getConnectionFactory() { - return this.connectionFactory; - } - - /** - * Set the exception translator for this instance. - *

- * If no custom translator is provided, a default {@link R2dbcExceptionSubclassTranslator} is used which translates - * {@link R2dbcException}'s subclasses into Springs {@link DataAccessException} hierarchy. - * - * @see R2dbcExceptionSubclassTranslator - * @since 1.1 - */ - public void setExceptionTranslator(R2dbcExceptionTranslator exceptionTranslator) { - this.exceptionTranslator = exceptionTranslator; - } - - /** - * Obtain the {@link ConnectionFactory} for actual use. - * - * @return the {@link ConnectionFactory} (never {@literal null}) - * @throws IllegalStateException in case of no ConnectionFactory set - */ - protected ConnectionFactory obtainConnectionFactory() { - ConnectionFactory connectionFactory = getConnectionFactory(); - Assert.state(connectionFactory != null, "No ConnectionFactory set"); - return connectionFactory; - } - - /** - * Specify whether to enforce the read-only nature of a transaction (as indicated by - * {@link TransactionDefinition#isReadOnly()} through an explicit statement on the transactional connection: "SET - * TRANSACTION READ ONLY" as understood by Oracle, MySQL and Postgres. - *

- * The exact treatment, including any SQL statement executed on the connection, can be customized through through - * {@link #prepareTransactionalConnection}. - * - * @see #prepareTransactionalConnection - */ - public void setEnforceReadOnly(boolean enforceReadOnly) { - this.enforceReadOnly = enforceReadOnly; - } - - /** - * Return whether to enforce the read-only nature of a transaction through an explicit statement on the transactional - * connection. - * - * @see #setEnforceReadOnly - */ - public boolean isEnforceReadOnly() { - return this.enforceReadOnly; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() { - if (getConnectionFactory() == null) { - throw new IllegalArgumentException("Property 'connectionFactory' is required"); - } - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doGetTransaction(org.springframework.transaction.reactive.TransactionSynchronizationManager) - */ - @Override - protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager) - throws TransactionException { - - ConnectionFactoryTransactionObject txObject = new ConnectionFactoryTransactionObject(); - ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(obtainConnectionFactory()); - txObject.setConnectionHolder(conHolder, false); - return txObject; - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#isExistingTransaction(java.lang.Object) - */ - @Override - protected boolean isExistingTransaction(Object transaction) { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doBegin(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, org.springframework.transaction.TransactionDefinition) - */ - @Override - protected Mono doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction, - TransactionDefinition definition) throws TransactionException { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - - return Mono.defer(() -> { - - Mono connection = null; - - if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { - Mono newCon = Mono.from(obtainConnectionFactory().create()); - - connection = newCon.doOnNext(it -> { - - if (this.logger.isDebugEnabled()) { - this.logger.debug("Acquired Connection [" + newCon + "] for R2DBC transaction"); - } - txObject.setConnectionHolder(new ConnectionHolder(it), true); - }); - } else { - txObject.getConnectionHolder().setSynchronizedWithTransaction(true); - connection = Mono.just(txObject.getConnectionHolder().getConnection()); - } - - return connection.flatMap(con -> { - - return prepareTransactionalConnection(con, definition, transaction).then(Mono.from(con.beginTransaction())) - .doOnSuccess(v -> { - txObject.getConnectionHolder().setTransactionActive(true); - - Duration timeout = determineTimeout(definition); - if (!timeout.isNegative() && !timeout.isZero()) { - txObject.getConnectionHolder().setTimeoutInMillis(timeout.toMillis()); - } - - // Bind the connection holder to the thread. - if (txObject.isNewConnectionHolder()) { - synchronizationManager.bindResource(obtainConnectionFactory(), txObject.getConnectionHolder()); - } - }).thenReturn(con).onErrorResume(e -> { - - if (txObject.isNewConnectionHolder()) { - return ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory()).doOnTerminate(() -> { - - txObject.setConnectionHolder(null, false); - }).then(Mono.error(e)); - } - return Mono.error(e); - }); - }).onErrorResume(e -> { - - CannotCreateTransactionException ex = new CannotCreateTransactionException( - "Could not open R2DBC Connection for transaction", - e instanceof R2dbcException ? potentiallyTranslateException("Open R2DBC Connection", (R2dbcException) e) - : e); - - return Mono.error(ex); - }); - }).then(); - } - - /** - * Determine the actual timeout to use for the given definition. Will fall back to this manager's default timeout if - * the transaction definition doesn't specify a non-default value. - * - * @param definition the transaction definition - * @return the actual timeout to use - * @see org.springframework.transaction.TransactionDefinition#getTimeout() - */ - protected Duration determineTimeout(TransactionDefinition definition) { - if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { - return Duration.ofSeconds(definition.getTimeout()); - } - return Duration.ZERO; - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSuspend(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object) - */ - @Override - protected Mono doSuspend(TransactionSynchronizationManager synchronizationManager, Object transaction) - throws TransactionException { - - return Mono.defer(() -> { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - txObject.setConnectionHolder(null); - return Mono.justOrEmpty(synchronizationManager.unbindResource(obtainConnectionFactory())); - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doResume(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, java.lang.Object) - */ - @Override - protected Mono doResume(TransactionSynchronizationManager synchronizationManager, Object transaction, - Object suspendedResources) throws TransactionException { - - return Mono.defer(() -> { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - txObject.setConnectionHolder(null); - synchronizationManager.bindResource(obtainConnectionFactory(), suspendedResources); - - return Mono.empty(); - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doCommit(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) - */ - @Override - protected Mono doCommit(TransactionSynchronizationManager TransactionSynchronizationManager, - GenericReactiveTransaction status) throws TransactionException { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); - Connection connection = txObject.getConnectionHolder().getConnection(); - if (status.isDebug()) { - this.logger.debug("Committing R2DBC transaction on Connection [" + connection + "]"); - } - - return Mono.from(connection.commitTransaction()) - .onErrorMap(R2dbcException.class, ex -> translateException("R2DBC commit", ex)); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doRollback(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) - */ - @Override - protected Mono doRollback(TransactionSynchronizationManager TransactionSynchronizationManager, - GenericReactiveTransaction status) throws TransactionException { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); - Connection connection = txObject.getConnectionHolder().getConnection(); - if (status.isDebug()) { - this.logger.debug("Rolling back R2DBC transaction on Connection [" + connection + "]"); - } - - return Mono.from(connection.rollbackTransaction()) - .onErrorMap(R2dbcException.class, ex -> translateException("R2DBC rollback", ex)); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSetRollbackOnly(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) - */ - @Override - protected Mono doSetRollbackOnly(TransactionSynchronizationManager synchronizationManager, - GenericReactiveTransaction status) throws TransactionException { - - return Mono.fromRunnable(() -> { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); - - if (status.isDebug()) { - this.logger - .debug("Setting R2DBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only"); - } - txObject.setRollbackOnly(); - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doCleanupAfterCompletion(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object) - */ - @Override - protected Mono doCleanupAfterCompletion(TransactionSynchronizationManager synchronizationManager, - Object transaction) { - - return Mono.defer(() -> { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - - // Remove the connection holder from the context, if exposed. - if (txObject.isNewConnectionHolder()) { - synchronizationManager.unbindResource(obtainConnectionFactory()); - } - - // Reset connection. - Connection con = txObject.getConnectionHolder().getConnection(); - - Mono afterCleanup = Mono.empty(); - - if (txObject.isMustRestoreAutoCommit()) { - afterCleanup = afterCleanup.then(Mono.from(con.setAutoCommit(true))); - } - - if (txObject.getPreviousIsolationLevel() != null) { - afterCleanup = afterCleanup - .then(Mono.from(con.setTransactionIsolationLevel(txObject.getPreviousIsolationLevel()))); - } - - return afterCleanup.then(Mono.defer(() -> { - - try { - if (txObject.isNewConnectionHolder()) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Releasing R2DBC Connection [" + con + "] after transaction"); - } - return ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory()); - } - } finally { - txObject.getConnectionHolder().clear(); - } - - return Mono.empty(); - })); - }); - } - - /** - * Prepare the transactional {@link Connection} right after transaction begin. - *

- * The default implementation executes a "SET TRANSACTION READ ONLY" statement if the {@link #setEnforceReadOnly - * "enforceReadOnly"} flag is set to {@literal true} and the transaction definition indicates a read-only transaction. - *

- * The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres and may work with other databases as - * well. If you'd like to adapt this treatment, override this method accordingly. - * - * @param con the transactional R2DBC Connection - * @param definition the current transaction definition - * @param transaction the transaction object - * @see #setEnforceReadOnly - */ - protected Mono prepareTransactionalConnection(Connection con, TransactionDefinition definition, - Object transaction) { - - ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - - Mono prepare = Mono.empty(); - - if (isEnforceReadOnly() && definition.isReadOnly()) { - - prepare = Mono.from(con.createStatement("SET TRANSACTION READ ONLY").execute()) // - .flatMapMany(Result::getRowsUpdated) // - .then(); - } - - // Apply specific isolation level, if any. - IsolationLevel isolationLevelToUse = resolveIsolationLevel(definition.getIsolationLevel()); - if (isolationLevelToUse != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { - - if (this.logger.isDebugEnabled()) { - this.logger - .debug("Changing isolation level of R2DBC Connection [" + con + "] to " + isolationLevelToUse.asSql()); - } - IsolationLevel currentIsolation = con.getTransactionIsolationLevel(); - if (!currentIsolation.asSql().equalsIgnoreCase(isolationLevelToUse.asSql())) { - - txObject.setPreviousIsolationLevel(currentIsolation); - prepare = prepare.then(Mono.from(con.setTransactionIsolationLevel(isolationLevelToUse))); - } - } - - // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, - // so we don't want to do it unnecessarily (for example if we've explicitly - // configured the connection pool to set it already). - if (con.isAutoCommit()) { - txObject.setMustRestoreAutoCommit(true); - if (this.logger.isDebugEnabled()) { - this.logger.debug("Switching R2DBC Connection [" + con + "] to manual commit"); - } - prepare = prepare.then(Mono.from(con.setAutoCommit(false))); - } - - return prepare; - } - - /** - * Resolve the {@link TransactionDefinition#getIsolationLevel() isolation level constant} to a R2DBC - * {@link IsolationLevel}. If you'd like to extend isolation level translation for vendor-specific - * {@link IsolationLevel}s, override this method accordingly. - * - * @param isolationLevel the isolation level to translate. - * @return the resolved isolation level. Can be {@literal null} if not resolvable or the isolation level should remain - * {@link TransactionDefinition#ISOLATION_DEFAULT default}. - * @see TransactionDefinition#getIsolationLevel() - */ - @Nullable - protected IsolationLevel resolveIsolationLevel(int isolationLevel) { - - switch (isolationLevel) { - case TransactionDefinition.ISOLATION_READ_COMMITTED: - return IsolationLevel.READ_COMMITTED; - case TransactionDefinition.ISOLATION_READ_UNCOMMITTED: - return IsolationLevel.READ_UNCOMMITTED; - case TransactionDefinition.ISOLATION_REPEATABLE_READ: - return IsolationLevel.REPEATABLE_READ; - case TransactionDefinition.ISOLATION_SERIALIZABLE: - return IsolationLevel.SERIALIZABLE; - } - - return null; - } - - /** - * Translate the given R2DBC commit/rollback exception to a common Spring exception to propagate from the - * {@link #commit}/{@link #rollback} call. - *

- * The default implementation throws a {@link TransactionSystemException}. Subclasses may specifically identify - * concurrency failures etc. - * - * @param task the task description (commit or rollback). - * @param ex the SQLException thrown from commit/rollback. - * @return the translated exception to throw, either a {@link org.springframework.dao.DataAccessException} or a - * {@link org.springframework.transaction.TransactionException} - * @since 1.1 - */ - protected RuntimeException translateException(String task, R2dbcException ex) { - - Exception translated = potentiallyTranslateException(task, ex); - return new TransactionSystemException(task + " failed", translated); - } - - private Exception potentiallyTranslateException(String task, R2dbcException ex) { - - DataAccessException translated = exceptionTranslator.translate(task, null, ex); - return translated != null ? translated : ex; - } - - /** - * ConnectionFactory transaction object, representing a ConnectionHolder. Used as transaction object by - * ConnectionFactoryTransactionManager. - */ - private static class ConnectionFactoryTransactionObject { - - private @Nullable ConnectionHolder connectionHolder; - - private @Nullable IsolationLevel previousIsolationLevel; - - private boolean newConnectionHolder; - - private boolean mustRestoreAutoCommit; - - void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) { - setConnectionHolder(connectionHolder); - this.newConnectionHolder = newConnectionHolder; - } - - boolean isNewConnectionHolder() { - return this.newConnectionHolder; - } - - void setRollbackOnly() { - getConnectionHolder().setRollbackOnly(); - } - - public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) { - this.connectionHolder = connectionHolder; - } - - public ConnectionHolder getConnectionHolder() { - Assert.state(this.connectionHolder != null, "No ConnectionHolder available"); - return this.connectionHolder; - } - - public boolean hasConnectionHolder() { - return (this.connectionHolder != null); - } - - public void setPreviousIsolationLevel(@Nullable IsolationLevel previousIsolationLevel) { - this.previousIsolationLevel = previousIsolationLevel; - } - - @Nullable - public IsolationLevel getPreviousIsolationLevel() { - return this.previousIsolationLevel; - } - - public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) { - this.mustRestoreAutoCommit = mustRestoreAutoCommit; - } - - public boolean isMustRestoreAutoCommit() { - return this.mustRestoreAutoCommit; - } - } } diff --git a/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java b/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java index 965875b..80951fd 100644 --- a/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java +++ b/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java @@ -32,7 +32,7 @@ import org.springframework.data.convert.CustomConversions; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ParameterValueProvider; @@ -48,6 +48,7 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.Parameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -357,7 +358,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R } private void writeSimpleInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) { - sink.put(property.getColumnName(), SettableValue.from(getPotentiallyConvertedSimpleWrite(value))); + sink.put(property.getColumnName(), Parameter.from(getPotentiallyConvertedSimpleWrite(value))); } private void writePropertyInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) { @@ -374,7 +375,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R } List collectionInternal = createCollection(asCollection(value), property); - sink.put(property.getColumnName(), SettableValue.from(collectionInternal)); + sink.put(property.getColumnName(), Parameter.from(collectionInternal)); return; } @@ -431,7 +432,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R private void writeNullInternal(OutboundRow sink, RelationalPersistentProperty property) { - sink.put(property.getColumnName(), SettableValue.empty(getPotentiallyConvertedSimpleNullType(property.getType()))); + sink.put(property.getColumnName(), Parameter.empty(getPotentiallyConvertedSimpleNullType(property.getType()))); } private Class getPotentiallyConvertedSimpleNullType(Class type) { @@ -640,7 +641,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R */ @Override @Nullable - public T getParameterValue(Parameter parameter) { + public T getParameterValue(PreferredConstructor.Parameter parameter) { RelationalPersistentProperty property = this.entity.getRequiredPersistentProperty(parameter.getName()); diff --git a/src/main/java/org/springframework/data/r2dbc/core/DatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/core/DatabaseClient.java index e1f0ae5..30d8798 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/DatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/core/DatabaseClient.java @@ -54,6 +54,14 @@ import org.springframework.util.Assert; @Deprecated public interface DatabaseClient { + /** + * Return the {@link ConnectionFactory} that this client uses. + * + * @return the connection factory. + * @since 1.2 + */ + ConnectionFactory getConnectionFactory(); + /** * Specify a static {@code sql} string to execute. Contract for specifying a SQL call along with options leading to * the exchange. The SQL string can contain either native parameter bind markers or named parameters (e.g. diff --git a/src/main/java/org/springframework/data/r2dbc/core/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/core/DefaultDatabaseClient.java index 5690ba6..aa9669c 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/core/DefaultDatabaseClient.java @@ -63,6 +63,7 @@ import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.CriteriaDefinition; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -73,7 +74,9 @@ import org.springframework.util.StringUtils; * @author Mark Paluch * @author Mingyuan Wu * @author Bogdan Ilchyshyn + * @deprecated since 1.2. */ +@Deprecated class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { private final Log logger = LogFactory.getLog(getClass()); @@ -105,6 +108,11 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { this.builder = builder; } + @Override + public ConnectionFactory getConnectionFactory() { + return this.connector; + } + @Override public Builder mutate() { return this.builder; @@ -1171,7 +1179,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { StatementMapper.InsertSpec insert = mapper.createInsert(this.table); for (SqlIdentifier column : outboundRow.keySet()) { - SettableValue settableValue = outboundRow.get(column); + Parameter settableValue = outboundRow.get(column); if (settableValue.hasValue()) { insert = insert.withColumn(column, settableValue); } @@ -1348,7 +1356,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { private UpdatedRowsFetchSpec exchange(SqlIdentifier table) { StatementMapper mapper = dataAccessStrategy.getStatementMapper(); - Map columns = dataAccessStrategy.getOutboundRow(this.objectToUpdate); + Map columns = dataAccessStrategy.getOutboundRow(this.objectToUpdate); List ids = dataAccessStrategy.getIdentifierColumns(this.typeToUpdate); if (ids.isEmpty()) { diff --git a/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java index 25a5e14..b91978e 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java @@ -207,10 +207,10 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra for (RelationalPersistentProperty property : entity) { - SettableValue value = row.get(property.getColumnName()); + Parameter value = row.get(property.getColumnName()); if (value != null && shouldConvertArrayValue(property, value)) { - SettableValue writeValue = getArrayValue(value, property); + Parameter writeValue = getArrayValue(value, property); row.put(property.getColumnName(), writeValue); } } @@ -218,7 +218,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra return row; } - private boolean shouldConvertArrayValue(RelationalPersistentProperty property, SettableValue value) { + private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) { if (!property.isCollectionLike()) { return false; @@ -235,7 +235,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra return false; } - private SettableValue getArrayValue(SettableValue value, RelationalPersistentProperty property) { + private Parameter getArrayValue(Parameter value, RelationalPersistentProperty property) { if (value.getType().equals(byte[].class)) { return value; @@ -250,8 +250,8 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra } Class actualType = null; - if (value instanceof Collection) { - actualType = CollectionUtils.findCommonElementType((Collection) value); + if (value.getValue() instanceof Collection) { + actualType = CollectionUtils.findCommonElementType((Collection) value.getValue()); } else if (value.getClass().isArray()) { actualType = value.getClass().getComponentType(); } @@ -265,10 +265,10 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra Class targetType = arrayColumns.getArrayType(actualType); int depth = actualType.isArray() ? ArrayUtils.getDimensionDepth(actualType) : 1; Class targetArrayType = ArrayUtils.getArrayClass(targetType, depth); - return SettableValue.empty(targetArrayType); + return Parameter.empty(targetArrayType); } - return SettableValue.fromOrEmpty(this.converter.getArrayValue(arrayColumns, property, value.getValue()), + return Parameter.fromOrEmpty(this.converter.getArrayValue(arrayColumns, property, value.getValue()), actualType); } diff --git a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java index e3ef233..f0cb450 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java +++ b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityOperations.java @@ -22,6 +22,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Update; +import org.springframework.r2dbc.core.DatabaseClient; /** * Interface specifying a basic set of reactive R2DBC operations using entities. Implemented by diff --git a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index a5dd2f8..82575cd 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -15,6 +15,8 @@ */ package org.springframework.data.r2dbc.core; +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; import reactor.core.publisher.Flux; @@ -27,6 +29,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.reactivestreams.Publisher; @@ -47,8 +50,8 @@ import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionInformation; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.r2dbc.dialect.R2dbcDialect; import org.springframework.data.r2dbc.mapping.OutboundRow; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback; import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback; import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback; @@ -64,8 +67,13 @@ import org.springframework.data.relational.core.sql.Functions; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.util.ProxyUtils; -import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.FetchSpec; +import org.springframework.r2dbc.core.Parameter; +import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.RowsFetchSpec; +import org.springframework.r2dbc.core.StatementFilterFunction; import org.springframework.util.Assert; /** @@ -96,8 +104,21 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw * Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient}. * * @param databaseClient must not be {@literal null}. + * @param dialect the dialect to use, must not be {@literal null}. + * @since 1.2 */ - public R2dbcEntityTemplate(DatabaseClient databaseClient) { + public R2dbcEntityTemplate(org.springframework.r2dbc.core.DatabaseClient databaseClient, R2dbcDialect dialect) { + this(databaseClient, new DefaultReactiveDataAccessStrategy(dialect)); + } + + /** + * Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient}. + * + * @param databaseClient must not be {@literal null}. + * @deprecated since 1.2, use {@link #R2dbcEntityTemplate(DatabaseClient, R2dbcDialect)} instead. + */ + @Deprecated + public R2dbcEntityTemplate(org.springframework.data.r2dbc.core.DatabaseClient databaseClient) { this(databaseClient, getDataAccessStrategy(databaseClient)); } @@ -105,8 +126,10 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw * Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient} and {@link ReactiveDataAccessStrategy}. * * @param databaseClient must not be {@literal null}. + * @since 1.2 */ - public R2dbcEntityTemplate(DatabaseClient databaseClient, ReactiveDataAccessStrategy strategy) { + public R2dbcEntityTemplate(org.springframework.r2dbc.core.DatabaseClient databaseClient, + ReactiveDataAccessStrategy strategy) { Assert.notNull(databaseClient, "DatabaseClient must not be null"); Assert.notNull(strategy, "ReactiveDataAccessStrategy must not be null"); @@ -117,6 +140,18 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw this.projectionFactory = new SpelAwareProxyProjectionFactory(); } + /** + * Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient} and {@link ReactiveDataAccessStrategy}. + * + * @param databaseClient must not be {@literal null}. + * @deprecated since 1.2, use {@link #R2dbcEntityTemplate(DatabaseClient, ReactiveDataAccessStrategy)} instead. + */ + @Deprecated + public R2dbcEntityTemplate(org.springframework.data.r2dbc.core.DatabaseClient databaseClient, + ReactiveDataAccessStrategy strategy) { + this(new DatabaseClientAdapter(databaseClient), strategy); + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#getDatabaseClient() @@ -251,7 +286,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw PreparedOperation operation = statementMapper.getMappedObject(selectSpec); - return this.databaseClient.execute(operation) // + return this.databaseClient.sql(operation) // .map((r, md) -> r.get(0, Long.class)) // .first() // .defaultIfEmpty(0L); @@ -290,7 +325,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw PreparedOperation operation = statementMapper.getMappedObject(selectSpec); - return this.databaseClient.execute(operation) // + return this.databaseClient.sql(operation) // .map((r, md) -> r) // .first() // .hasElement(); @@ -361,7 +396,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw rowMapper = dataAccessStrategy.getRowMapper(returnType); } - return this.databaseClient.execute(operation).map(rowMapper); + return this.databaseClient.sql(operation).map(rowMapper); } /* @@ -400,7 +435,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw } PreparedOperation operation = statementMapper.getMappedObject(selectSpec); - return this.databaseClient.execute(operation).fetch().rowsUpdated(); + return this.databaseClient.sql(operation).fetch().rowsUpdated(); } /* @@ -429,7 +464,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw } PreparedOperation operation = statementMapper.getMappedObject(selectSpec); - return this.databaseClient.execute(operation).fetch().rowsUpdated().defaultIfEmpty(0); + return this.databaseClient.sql(operation).fetch().rowsUpdated().defaultIfEmpty(0); } // ------------------------------------------------------------------------- @@ -454,14 +489,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw T entityWithVersion = setVersionIfNecessary(persistentEntity, entity); - return maybeCallBeforeConvert(entityWithVersion, tableName) - .flatMap(beforeConvert -> { + return maybeCallBeforeConvert(entityWithVersion, tableName).flatMap(beforeConvert -> { - OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(beforeConvert); + OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(beforeConvert); - return maybeCallBeforeSave(beforeConvert, outboundRow, tableName) // - .flatMap(entityToSave -> doInsert(entityToSave, tableName, outboundRow)); - }); + return maybeCallBeforeSave(beforeConvert, outboundRow, tableName) // + .flatMap(entityToSave -> doInsert(entityToSave, tableName, outboundRow)); + }); } private Mono doInsert(T entity, SqlIdentifier tableName, OutboundRow outboundRow) { @@ -470,7 +504,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw StatementMapper.InsertSpec insert = mapper.createInsert(tableName); for (SqlIdentifier column : outboundRow.keySet()) { - SettableValue settableValue = outboundRow.get(column); + Parameter settableValue = outboundRow.get(column); if (settableValue.hasValue()) { insert = insert.withColumn(column, settableValue); } @@ -478,7 +512,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw PreparedOperation operation = mapper.getMappedObject(insert); - return this.databaseClient.execute(operation) // + return this.databaseClient.sql(operation) // .filter(statement -> statement.returnGeneratedValues()) .map(this.dataAccessStrategy.getConverter().populateIdIfNecessary(entity)) // .first() // @@ -540,7 +574,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw .flatMap(entityToSave -> { SqlIdentifier idColumn = persistentEntity.getRequiredIdProperty().getColumnName(); - SettableValue id = outboundRow.remove(idColumn); + Parameter id = outboundRow.remove(idColumn); Criteria criteria = Criteria.where(dataAccessStrategy.toSql(idColumn)).is(id); if (matchingVersionCriteria != null) { @@ -563,7 +597,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw PreparedOperation operation = mapper.getMappedObject(updateSpec); - return this.databaseClient.execute(operation) // + return this.databaseClient.sql(operation) // .fetch() // .rowsUpdated() // .handle((rowsUpdated, sink) -> { @@ -720,7 +754,8 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw return query.getColumns().stream().map(table::column).collect(Collectors.toList()); } - private static ReactiveDataAccessStrategy getDataAccessStrategy(DatabaseClient databaseClient) { + private static ReactiveDataAccessStrategy getDataAccessStrategy( + org.springframework.data.r2dbc.core.DatabaseClient databaseClient) { Assert.notNull(databaseClient, "DatabaseClient must not be null"); @@ -733,4 +768,132 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw throw new IllegalStateException("Cannot obtain ReactiveDataAccessStrategy"); } + /** + * Adapter to adapt our deprecated {@link org.springframework.data.r2dbc.core.DatabaseClient} into Spring R2DBC + * {@link DatabaseClient}. + */ + private static class DatabaseClientAdapter implements DatabaseClient { + + private final org.springframework.data.r2dbc.core.DatabaseClient delegate; + + private DatabaseClientAdapter(org.springframework.data.r2dbc.core.DatabaseClient delegate) { + + Assert.notNull(delegate, "DatabaseClient must not be null"); + + this.delegate = delegate; + } + + @Override + public ConnectionFactory getConnectionFactory() { + return delegate.getConnectionFactory(); + } + + @Override + public GenericExecuteSpec sql(String sql) { + return new GenericExecuteSpecAdapter(delegate.execute(sql)); + } + + @Override + public GenericExecuteSpec sql(Supplier sqlSupplier) { + return new GenericExecuteSpecAdapter(delegate.execute(sqlSupplier)); + } + + @Override + public Mono inConnection(Function> action) throws DataAccessException { + return ((ConnectionAccessor) delegate).inConnection(action); + } + + @Override + public Flux inConnectionMany(Function> action) throws DataAccessException { + return ((ConnectionAccessor) delegate).inConnectionMany(action); + } + + static class GenericExecuteSpecAdapter implements GenericExecuteSpec { + + private final org.springframework.data.r2dbc.core.DatabaseClient.GenericExecuteSpec delegate; + + public GenericExecuteSpecAdapter(org.springframework.data.r2dbc.core.DatabaseClient.GenericExecuteSpec delegate) { + this.delegate = delegate; + } + + @Override + public GenericExecuteSpec bind(int index, Object value) { + return new GenericExecuteSpecAdapter(delegate.bind(index, value)); + } + + @Override + public GenericExecuteSpec bindNull(int index, Class type) { + return new GenericExecuteSpecAdapter(delegate.bindNull(index, type)); + } + + @Override + public GenericExecuteSpec bind(String name, Object value) { + return new GenericExecuteSpecAdapter(delegate.bind(name, value)); + } + + @Override + public GenericExecuteSpec bindNull(String name, Class type) { + return new GenericExecuteSpecAdapter(delegate.bindNull(name, type)); + } + + @Override + public GenericExecuteSpec filter(StatementFilterFunction filter) { + return new GenericExecuteSpecAdapter(delegate.filter(filter::filter)); + } + + @Override + public RowsFetchSpec map(BiFunction mappingFunction) { + return new RowFetchSpecAdapter<>(delegate.map(mappingFunction)); + } + + @Override + public FetchSpec> fetch() { + return new FetchSpecAdapter<>(delegate.fetch()); + } + + @Override + public Mono then() { + return delegate.then(); + } + } + + private static class RowFetchSpecAdapter implements RowsFetchSpec { + + private final org.springframework.data.r2dbc.core.RowsFetchSpec delegate; + + RowFetchSpecAdapter(org.springframework.data.r2dbc.core.RowsFetchSpec delegate) { + this.delegate = delegate; + } + + @Override + public Mono one() { + return delegate.one(); + } + + @Override + public Mono first() { + return delegate.first(); + } + + @Override + public Flux all() { + return delegate.all(); + } + } + + private static class FetchSpecAdapter extends RowFetchSpecAdapter implements FetchSpec { + + private final org.springframework.data.r2dbc.core.FetchSpec delegate; + + FetchSpecAdapter(org.springframework.data.r2dbc.core.FetchSpec delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public Mono rowsUpdated() { + return delegate.rowsUpdated(); + } + } + } } diff --git a/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java index f96a95a..90215da 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java @@ -53,7 +53,7 @@ public interface ReactiveDataAccessStrategy { List getIdentifierColumns(Class entityType); /** - * Returns a {@link OutboundRow} that maps column names to a {@link SettableValue} value. + * Returns a {@link OutboundRow} that maps column names to a {@link Parameter} value. * * @param object must not be {@literal null}. * @return @@ -101,7 +101,10 @@ public interface ReactiveDataAccessStrategy { * @param parameterProvider indexed parameter bindings. * @return the {@link PreparedOperation} encapsulating expanded SQL and namedBindings. * @throws org.springframework.dao.InvalidDataAccessApiUsageException if a named parameter value cannot be resolved. + * @deprecated since 1.2. {@link org.springframework.r2dbc.core.DatabaseClient} encapsulates named parameter handling + * entirely. */ + @Deprecated PreparedOperation processNamedParameters(String query, NamedParameterProvider parameterProvider); /** diff --git a/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java b/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java index 1cfabb4..46be18f 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java +++ b/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java @@ -21,6 +21,7 @@ import reactor.core.publisher.Mono; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.RowsFetchSpec; import org.springframework.util.Assert; /** diff --git a/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java b/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java index b21e687..c68d8ea 100644 --- a/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java +++ b/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java @@ -36,6 +36,7 @@ import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.relational.core.sql.render.RenderContext; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; /** @@ -425,9 +426,9 @@ public interface StatementMapper { class InsertSpec { private final SqlIdentifier table; - private final Map assignments; + private final Map assignments; - protected InsertSpec(SqlIdentifier table, Map assignments) { + protected InsertSpec(SqlIdentifier table, Map assignments) { this.table = table; this.assignments = assignments; } @@ -459,21 +460,49 @@ public interface StatementMapper { * @param column * @param value * @return the {@link InsertSpec}. + * @deprecated since 1.2, use {@link #withColumn(String, Parameter)} instead. */ + @Deprecated public InsertSpec withColumn(String column, SettableValue value) { return withColumn(SqlIdentifier.unquoted(column), value); } + /** + * Associate a column with a {@link Parameter} and create a new {@link InsertSpec}. + * + * @param column + * @param value + * @return the {@link InsertSpec}. + * @since 1.2 + */ + public InsertSpec withColumn(String column, Parameter value) { + return withColumn(SqlIdentifier.unquoted(column), value); + } + /** * Associate a column with a {@link SettableValue} and create a new {@link InsertSpec}. * * @param column * @param value * @return the {@link InsertSpec}. + * @deprecated since 1.2, use {@link #withColumn(SqlIdentifier, Parameter)} instead. */ + @Deprecated public InsertSpec withColumn(SqlIdentifier column, SettableValue value) { + return withColumn(column, value.toParameter()); + } - Map values = new LinkedHashMap<>(this.assignments); + /** + * Associate a column with a {@link Parameter} and create a new {@link InsertSpec}. + * + * @param column + * @param value + * @return the {@link InsertSpec}. + * @since 1.2 + */ + public InsertSpec withColumn(SqlIdentifier column, Parameter value) { + + Map values = new LinkedHashMap<>(this.assignments); values.put(column, value); return new InsertSpec(this.table, values); @@ -483,7 +512,7 @@ public interface StatementMapper { return this.table; } - public Map getAssignments() { + public Map getAssignments() { return Collections.unmodifiableMap(this.assignments); } } diff --git a/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java b/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java index fc1d776..640a052 100644 --- a/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java +++ b/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.function.BiConsumer; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.r2dbc.core.Parameter; import org.springframework.util.Assert; /** @@ -33,11 +34,11 @@ import org.springframework.util.Assert; * * @author Mark Paluch * @see SqlIdentifier - * @see SettableValue + * @see Parameter */ -public class OutboundRow implements Map { +public class OutboundRow implements Map { - private final Map rowAsMap; + private final Map rowAsMap; /** * Creates an empty {@link OutboundRow} instance. @@ -51,13 +52,13 @@ public class OutboundRow implements Map { * * @param map the map used to initialize the {@link OutboundRow}. */ - public OutboundRow(Map map) { + public OutboundRow(Map map) { Assert.notNull(map, "Map must not be null"); this.rowAsMap = new LinkedHashMap<>(map.size()); - map.forEach((s, settableValue) -> this.rowAsMap.put(SqlIdentifier.unquoted(s), settableValue)); + map.forEach((s, Parameter) -> this.rowAsMap.put(SqlIdentifier.unquoted(s), Parameter)); } /** @@ -67,7 +68,7 @@ public class OutboundRow implements Map { * @param value value. * @see SqlIdentifier#unquoted(String) */ - public OutboundRow(String key, SettableValue value) { + public OutboundRow(String key, Parameter value) { this(SqlIdentifier.unquoted(key), value); } @@ -78,7 +79,7 @@ public class OutboundRow implements Map { * @param value value. * @since 1.1 */ - public OutboundRow(SqlIdentifier key, SettableValue value) { + public OutboundRow(SqlIdentifier key, Parameter value) { this.rowAsMap = new LinkedHashMap<>(); this.rowAsMap.put(key, value); } @@ -96,7 +97,7 @@ public class OutboundRow implements Map { * @return this * @see SqlIdentifier#unquoted(String) */ - public OutboundRow append(String key, SettableValue value) { + public OutboundRow append(String key, Parameter value) { return append(SqlIdentifier.unquoted(key), value); } @@ -113,7 +114,7 @@ public class OutboundRow implements Map { * @return this * @since 1.1 */ - public OutboundRow append(SqlIdentifier key, SettableValue value) { + public OutboundRow append(SqlIdentifier key, Parameter value) { this.rowAsMap.put(key, value); return this; } @@ -159,7 +160,7 @@ public class OutboundRow implements Map { * @see java.util.Map#get(java.lang.Object) */ @Override - public SettableValue get(Object key) { + public Parameter get(Object key) { return this.rowAsMap.get(convertKeyIfNecessary(key)); } @@ -167,7 +168,7 @@ public class OutboundRow implements Map { * (non-Javadoc) * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ - public SettableValue put(String key, SettableValue value) { + public Parameter put(String key, Parameter value) { return put(SqlIdentifier.unquoted(key), value); } @@ -176,7 +177,7 @@ public class OutboundRow implements Map { * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ @Override - public SettableValue put(SqlIdentifier key, SettableValue value) { + public Parameter put(SqlIdentifier key, Parameter value) { return this.rowAsMap.put(key, value); } @@ -185,7 +186,7 @@ public class OutboundRow implements Map { * @see java.util.Map#remove(java.lang.Object) */ @Override - public SettableValue remove(Object key) { + public Parameter remove(Object key) { return this.rowAsMap.remove(key); } @@ -194,7 +195,7 @@ public class OutboundRow implements Map { * @see java.util.Map#putAll(java.util.Map) */ @Override - public void putAll(Map m) { + public void putAll(Map m) { this.rowAsMap.putAll(m); } @@ -221,7 +222,7 @@ public class OutboundRow implements Map { * @see java.util.Map#values() */ @Override - public Collection values() { + public Collection values() { return this.rowAsMap.values(); } @@ -230,7 +231,7 @@ public class OutboundRow implements Map { * @see java.util.Map#entrySet() */ @Override - public Set> entrySet() { + public Set> entrySet() { return this.rowAsMap.entrySet(); } @@ -273,7 +274,7 @@ public class OutboundRow implements Map { } @Override - public void forEach(BiConsumer action) { + public void forEach(BiConsumer action) { this.rowAsMap.forEach(action); } diff --git a/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java b/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java index e15cd22..71587ac 100644 --- a/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java +++ b/src/main/java/org/springframework/data/r2dbc/mapping/SettableValue.java @@ -128,4 +128,12 @@ public class SettableValue { public String toString() { return this.parameter.toString(); } + + /** + * @return the {@link Parameter} representing this settable value. + * @since 1.2 + */ + public Parameter toParameter() { + return isEmpty() ? Parameter.empty(getType()) : Parameter.fromOrEmpty(getValue(), getType()); + } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java index d6e2bf3..2bc3b2b 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/AbstractR2dbcQuery.java @@ -23,10 +23,8 @@ import reactor.core.publisher.Mono; import org.reactivestreams.Publisher; import org.springframework.core.KotlinDetector; import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.r2dbc.convert.EntityRowMapper; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.core.DatabaseClient.GenericExecuteSpec; -import org.springframework.data.r2dbc.core.FetchSpec; import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter; import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -36,6 +34,9 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.FetchSpec; +import org.springframework.r2dbc.core.RowsFetchSpec; import org.springframework.data.util.ReflectionUtils; import org.springframework.util.Assert; @@ -107,8 +108,15 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { BindableQuery query = createQuery(parameterAccessor); ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); - GenericExecuteSpec boundQuery = query.bind(databaseClient.execute(query)); - FetchSpec fetchSpec = boundQuery.as(resolveResultType(processor)).fetch(); + DatabaseClient.GenericExecuteSpec boundQuery = query.bind(databaseClient.sql(query)); + + FetchSpec fetchSpec; + if (requiresMapping()) { + EntityRowMapper rowMapper = new EntityRowMapper<>(resolveResultType(processor), converter); + fetchSpec = new FetchSpecAdapter<>(boundQuery.map(rowMapper)); + } else { + fetchSpec = boundQuery.fetch(); + } SqlIdentifier tableName = method.getEntityInformation().getTableName(); @@ -118,6 +126,10 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { return execution.execute(fetchSpec, processor.getReturnedType().getDomainType(), tableName); } + private boolean requiresMapping() { + return !isModifyingQuery(); + } + private Class resolveResultType(ResultProcessor resultProcessor) { ReturnedType returnedType = resultProcessor.getReturnedType(); @@ -169,4 +181,33 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery { * @return the {@link BindableQuery}. */ protected abstract BindableQuery createQuery(RelationalParameterAccessor accessor); + + private static class FetchSpecAdapter implements FetchSpec { + + private final RowsFetchSpec delegate; + + private FetchSpecAdapter(RowsFetchSpec delegate) { + this.delegate = delegate; + } + + @Override + public Mono one() { + return delegate.one(); + } + + @Override + public Mono first() { + return delegate.first(); + } + + @Override + public Flux all() { + return delegate.all(); + } + + @Override + public Mono rowsUpdated() { + throw new UnsupportedOperationException("Not supported after applying a row mapper"); + } + } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/BindableQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/BindableQuery.java index 3f16340..fefaa9f 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/BindableQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/BindableQuery.java @@ -17,20 +17,20 @@ package org.springframework.data.r2dbc.repository.query; import java.util.function.Supplier; -import org.springframework.data.r2dbc.core.DatabaseClient.BindSpec; +import org.springframework.r2dbc.core.DatabaseClient; /** - * Interface declaring a query that supplies SQL and can bind parameters to a {@link BindSpec}. + * Interface declaring a query that supplies SQL and can bind parameters to a {@link DatabaseClient.GenericExecuteSpec}. * * @author Mark Paluch */ public interface BindableQuery extends Supplier { /** - * Bind parameters to the {@link BindSpec query}. + * Bind parameters to the {@link DatabaseClient.GenericExecuteSpec query}. * * @param bindSpec must not be {@literal null}. * @return the bound query object. */ - > T bind(T bindSpec); + DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec); } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java index 2306d4c..9ac8c77 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java @@ -22,8 +22,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; @@ -31,6 +29,7 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; /** @@ -76,25 +75,27 @@ class ExpressionEvaluatingParameterBinder { * @param bindSpec must not be {@literal null}. * @param parameterAccessor must not be {@literal null}. */ - public > T bind(T bindSpec, RelationalParameterAccessor parameterAccessor) { + public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec, + RelationalParameterAccessor parameterAccessor) { Object[] values = parameterAccessor.getValues(); Parameters bindableParameters = parameterAccessor.getBindableParameters(); - T bindSpecToUse = bindExpressions(bindSpec, values, bindableParameters); + DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, values, bindableParameters); bindSpecToUse = bindParameters(bindSpecToUse, parameterAccessor.hasBindableNullValue(), values, bindableParameters); return bindSpecToUse; } - private > T bindExpressions(T bindSpec, Object[] values, + private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec, Object[] values, Parameters bindableParameters) { - T bindSpecToUse = bindSpec; + DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; for (ParameterBinding binding : expressionQuery.getBindings()) { - SettableValue valueForBinding = getParameterValueForBinding(bindableParameters, values, binding); + org.springframework.r2dbc.core.Parameter valueForBinding = getParameterValueForBinding(bindableParameters, values, + binding); if (valueForBinding.isEmpty()) { bindSpecToUse = bindSpecToUse.bindNull(binding.getParameterName(), valueForBinding.getType()); @@ -106,10 +107,11 @@ class ExpressionEvaluatingParameterBinder { return bindSpecToUse; } - private > T bindParameters(T bindSpec, boolean bindableNull, Object[] values, + private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec, + boolean bindableNull, Object[] values, Parameters bindableParameters) { - T bindSpecToUse = bindSpec; + DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec; int bindingIndex = 0; @@ -166,7 +168,8 @@ class ExpressionEvaluatingParameterBinder { * @param binding must not be {@literal null}. * @return the value used for the given {@link ParameterBinding}. */ - private SettableValue getParameterValueForBinding(Parameters parameters, Object[] values, + private org.springframework.r2dbc.core.Parameter getParameterValueForBinding(Parameters parameters, + Object[] values, ParameterBinding binding) { return evaluateExpression(binding.getExpression(), parameters, values); } @@ -179,7 +182,8 @@ class ExpressionEvaluatingParameterBinder { * @param parameterValues must not be {@literal null}. * @return the value of the {@code expressionString} evaluation. */ - private SettableValue evaluateExpression(String expressionString, Parameters parameters, + private org.springframework.r2dbc.core.Parameter evaluateExpression(String expressionString, + Parameters parameters, Object[] parameterValues) { EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(parameters, parameterValues); @@ -188,6 +192,6 @@ class ExpressionEvaluatingParameterBinder { Object value = expression.getValue(evaluationContext, Object.class); Class valueType = expression.getValueType(evaluationContext); - return SettableValue.fromOrEmpty(value, valueType != null ? valueType : Object.class); + return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class); } } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java index 7bb8102..3a6c6d1 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java @@ -21,7 +21,6 @@ import java.util.List; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.relational.repository.query.RelationalEntityMetadata; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; @@ -29,6 +28,7 @@ import org.springframework.data.relational.repository.query.RelationalParameters import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.PreparedOperation; /** diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java index 054da8f..0d5b2b9 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java @@ -15,15 +15,16 @@ */ package org.springframework.data.r2dbc.repository.query; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.dialect.BindTarget; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.binding.BindTarget; import org.springframework.util.Assert; /** * A {@link BindableQuery} implementation based on a {@link PreparedOperation}. * * @author Roman Chigvintsev + * @author Mark Paluch */ class PreparedOperationBindableQuery implements BindableQuery { @@ -41,12 +42,11 @@ class PreparedOperationBindableQuery implements BindableQuery { this.preparedQuery = preparedQuery; } - @SuppressWarnings("unchecked") @Override - public > T bind(T bindSpec) { - BindSpecBindTargetAdapter bindTargetAdapter = new BindSpecBindTargetAdapter<>(bindSpec); + public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec) { + BindSpecBindTargetAdapter bindTargetAdapter = new BindSpecBindTargetAdapter(bindSpec); preparedQuery.bindTo(bindTargetAdapter); - return (T) bindTargetAdapter.bindSpec; + return bindTargetAdapter.bindSpec; } @Override @@ -55,13 +55,14 @@ class PreparedOperationBindableQuery implements BindableQuery { } /** - * This class adapts {@link org.springframework.data.r2dbc.core.DatabaseClient.BindSpec} to {@link BindTarget} - * allowing easy binding of query parameters using {@link PreparedOperation}. + * This class adapts {@link DatabaseClient.GenericExecuteSpec} to {@link BindTarget} allowing easy binding of query + * parameters using {@link PreparedOperation}. */ - private static class BindSpecBindTargetAdapter> implements BindTarget { - DatabaseClient.BindSpec bindSpec; + private static class BindSpecBindTargetAdapter implements BindTarget { - private BindSpecBindTargetAdapter(DatabaseClient.BindSpec bindSpec) { + DatabaseClient.GenericExecuteSpec bindSpec; + + private BindSpecBindTargetAdapter(DatabaseClient.GenericExecuteSpec bindSpec) { this.bindSpec = bindSpec; } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java index b399a98..9877ee9 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java @@ -23,7 +23,6 @@ import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.EntityInstantiators; -import org.springframework.data.r2dbc.core.FetchSpec; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -31,6 +30,7 @@ import org.springframework.data.relational.repository.query.DtoInstantiatingConv import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.ReflectionUtils; +import org.springframework.r2dbc.core.FetchSpec; import org.springframework.util.ClassUtils; /** diff --git a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java index eb0d00f..ded9bfe 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java @@ -16,12 +16,11 @@ package org.springframework.data.r2dbc.repository.query; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.core.DatabaseClient.BindSpec; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; /** @@ -76,7 +75,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { this.binder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider, expressionQuery); } - /* + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#isModifyingQuery() */ @@ -85,7 +84,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { return getQueryMethod().isModifyingQuery(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor) */ @@ -95,7 +94,7 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery { return new BindableQuery() { @Override - public > T bind(T bindSpec) { + public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec) { return binder.bind(bindSpec, accessor); } diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java index fba6215..5fe5576 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java @@ -43,6 +43,7 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; /** diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java index 2c01b5a..0102e40 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java @@ -25,6 +25,7 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; /** diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java index df0b568..9ec39e3 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java @@ -22,16 +22,16 @@ import org.reactivestreams.Publisher; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; -import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.repository.reactive.ReactiveSortingRepository; import org.springframework.data.util.Lazy; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -76,6 +76,7 @@ public class SimpleR2dbcRepository implements ReactiveSortingRepository entity, DatabaseClient databaseClient, R2dbcConverter converter, ReactiveDataAccessStrategy accessStrategy) { @@ -88,6 +89,28 @@ public class SimpleR2dbcRepository implements ReactiveSortingRepository entity, + org.springframework.data.r2dbc.core.DatabaseClient databaseClient, R2dbcConverter converter, + ReactiveDataAccessStrategy accessStrategy) { + + this.entity = entity; + this.entityOperations = new R2dbcEntityTemplate(databaseClient, accessStrategy); + this.idProperty = Lazy.of(() -> converter // + .getMappingContext() // + .getRequiredPersistentEntity(this.entity.getJavaType()) // + .getRequiredIdProperty()); + } + /* (non-Javadoc) * @see org.springframework.data.repository.reactive.ReactiveCrudRepository#save(S) */ diff --git a/src/test/java/org/springframework/data/r2dbc/config/H2IntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/config/H2IntegrationTests.java index 18d611c..1ded23e 100644 --- a/src/test/java/org/springframework/data/r2dbc/config/H2IntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/config/H2IntegrationTests.java @@ -32,13 +32,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.dao.DataAccessException; import org.springframework.data.annotation.Id; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.data.r2dbc.testing.H2TestSupport; import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -70,9 +70,8 @@ public class H2IntegrationTests { jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)"); - databaseClient.execute("SELECT COUNT(*) FROM legoset") // - .as(Long.class) // - .fetch() // + databaseClient.sql("SELECT COUNT(*) FROM legoset") // + .map(it -> it.get(0, Long.class)) // .all() // .as(StepVerifier::create) // .expectNext(1L) // diff --git a/src/test/java/org/springframework/data/r2dbc/config/R2dbcConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/config/R2dbcConfigurationIntegrationTests.java index 528bb17..30b075e 100644 --- a/src/test/java/org/springframework/data/r2dbc/config/R2dbcConfigurationIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/config/R2dbcConfigurationIntegrationTests.java @@ -27,7 +27,7 @@ import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.DatabaseClient; /** * Tests for {@link AbstractR2dbcConfiguration}. diff --git a/src/test/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManagerUnitTests.java b/src/test/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManagerUnitTests.java deleted file mode 100644 index ff45ef4..0000000 --- a/src/test/java/org/springframework/data/r2dbc/connectionfactory/R2dbcTransactionManagerUnitTests.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 2019-2020 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.r2dbc.connectionfactory; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.*; - -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactory; -import io.r2dbc.spi.IsolationLevel; -import io.r2dbc.spi.R2dbcBadGrammarException; -import io.r2dbc.spi.Statement; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.data.r2dbc.BadSqlGrammarException; -import org.springframework.transaction.CannotCreateTransactionException; -import org.springframework.transaction.IllegalTransactionStateException; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.reactive.TransactionSynchronization; -import org.springframework.transaction.reactive.TransactionSynchronizationManager; -import org.springframework.transaction.reactive.TransactionalOperator; -import org.springframework.transaction.support.DefaultTransactionDefinition; - -/** - * Unit tests for {@link R2dbcTransactionManager}. - * - * @author Mark Paluch - */ -public class R2dbcTransactionManagerUnitTests { - - ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class); - Connection connectionMock = mock(Connection.class); - - private R2dbcTransactionManager tm; - - @Before - public void before() { - - when(connectionFactoryMock.create()).thenReturn((Mono) Mono.just(connectionMock)); - when(connectionMock.beginTransaction()).thenReturn(Mono.empty()); - when(connectionMock.close()).thenReturn(Mono.empty()); - tm = new R2dbcTransactionManager(connectionFactoryMock); - } - - @Test // gh-107 - public void testSimpleTransaction() { - - TestTransactionSynchronization sync = new TestTransactionSynchronization( - TransactionSynchronization.STATUS_COMMITTED); - AtomicInteger commits = new AtomicInteger(); - when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet)); - - TransactionalOperator operator = TransactionalOperator.create(tm); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).flatMap(it -> { - - return TransactionSynchronizationManager.forCurrentTransaction() - .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync)); - - }) // - .as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - assertThat(commits).hasValue(1); - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - - assertThat(sync.beforeCommitCalled).isTrue(); - assertThat(sync.afterCommitCalled).isTrue(); - assertThat(sync.beforeCompletionCalled).isTrue(); - assertThat(sync.afterCompletionCalled).isTrue(); - } - - @Test // gh-329 - public void testBeginFails() { - - reset(connectionFactoryMock); - when(connectionFactoryMock.create()).thenReturn(Mono.error(new R2dbcBadGrammarException("fail"))); - - when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectErrorSatisfies(actual -> { - - assertThat(actual).isInstanceOf(CannotCreateTransactionException.class) - .hasCauseInstanceOf(BadSqlGrammarException.class); - - }).verify(); - } - - @Test // gh-107 - public void appliesIsolationLevel() { - - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - when(connectionMock.getTransactionIsolationLevel()).thenReturn(IsolationLevel.READ_COMMITTED); - when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - verify(connectionMock).beginTransaction(); - verify(connectionMock).setTransactionIsolationLevel(IsolationLevel.READ_COMMITTED); - verify(connectionMock).setTransactionIsolationLevel(IsolationLevel.SERIALIZABLE); - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - } - - @Test // gh-184 - public void doesNotSetIsolationLevelIfMatch() { - - when(connectionMock.getTransactionIsolationLevel()).thenReturn(IsolationLevel.READ_COMMITTED); - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - verify(connectionMock).beginTransaction(); - verify(connectionMock, never()).setTransactionIsolationLevel(any()); - verify(connectionMock).commitTransaction(); - } - - @Test // gh-184 - public void doesNotSetAutoCommitDisabled() { - - when(connectionMock.isAutoCommit()).thenReturn(false); - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - verify(connectionMock).beginTransaction(); - verify(connectionMock, never()).setAutoCommit(anyBoolean()); - verify(connectionMock).commitTransaction(); - } - - @Test // gh-184 - public void restoresAutoCommit() { - - when(connectionMock.isAutoCommit()).thenReturn(true); - when(connectionMock.setAutoCommit(anyBoolean())).thenReturn(Mono.empty()); - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - verify(connectionMock).beginTransaction(); - verify(connectionMock).setAutoCommit(false); - verify(connectionMock).setAutoCommit(true); - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - } - - @Test // gh-107 - public void appliesReadOnly() { - - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty()); - Statement statement = mock(Statement.class); - when(connectionMock.createStatement(anyString())).thenReturn(statement); - when(statement.execute()).thenReturn(Mono.empty()); - tm.setEnforceReadOnly(true); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setReadOnly(true); - - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).createStatement("SET TRANSACTION READ ONLY"); - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - } - - @Test // gh-107 - public void testCommitFails() { - - when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() -> { - return Mono.error(new R2dbcBadGrammarException("Commit should fail")); - })); - - when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty()); - - TransactionalOperator operator = TransactionalOperator.create(tm); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock) // - .doOnNext(it -> { - it.createStatement("foo"); - }).then() // - .as(operator::transactional) // - .as(StepVerifier::create) // - .verifyError(IllegalTransactionStateException.class); - - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).createStatement("foo"); - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - } - - @Test // gh-107 - public void testRollback() { - - AtomicInteger commits = new AtomicInteger(); - when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet)); - - AtomicInteger rollbacks = new AtomicInteger(); - when(connectionMock.rollbackTransaction()).thenReturn(Mono.fromRunnable(rollbacks::incrementAndGet)); - - TransactionalOperator operator = TransactionalOperator.create(tm); - - ConnectionFactoryUtils.getConnection(connectionFactoryMock).doOnNext(it -> { - - throw new IllegalStateException(); - - }).as(operator::transactional) // - .as(StepVerifier::create) // - .verifyError(IllegalStateException.class); - - assertThat(commits).hasValue(0); - assertThat(rollbacks).hasValue(1); - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).rollbackTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - } - - @Test // gh-329 - public void testRollbackFails() { - - when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() -> { - return Mono.error(new R2dbcBadGrammarException("Commit should fail")); - }), Mono.empty()); - - TransactionalOperator operator = TransactionalOperator.create(tm); - - operator.execute(reactiveTransaction -> { - - reactiveTransaction.setRollbackOnly(); - - return ConnectionFactoryUtils.getConnection(connectionFactoryMock) // - .doOnNext(it -> { - it.createStatement("foo"); - }).then(); - }).as(StepVerifier::create) // - .verifyError(IllegalTransactionStateException.class); - - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).createStatement("foo"); - verify(connectionMock, never()).commitTransaction(); - verify(connectionMock).rollbackTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - } - - @Test // gh-107 - public void testTransactionSetRollbackOnly() { - - when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty()); - TestTransactionSynchronization sync = new TestTransactionSynchronization( - TransactionSynchronization.STATUS_ROLLED_BACK); - - TransactionalOperator operator = TransactionalOperator.create(tm); - - operator.execute(tx -> { - - tx.setRollbackOnly(); - assertThat(tx.isNewTransaction()).isTrue(); - - return TransactionSynchronizationManager.forCurrentTransaction().doOnNext(it -> { - - assertThat(it.hasResource(connectionFactoryMock)).isTrue(); - it.registerSynchronization(sync); - - }).then(); - }).as(StepVerifier::create) // - .verifyComplete(); - - verify(connectionMock).isAutoCommit(); - verify(connectionMock).beginTransaction(); - verify(connectionMock).rollbackTransaction(); - verify(connectionMock).close(); - verifyNoMoreInteractions(connectionMock); - - assertThat(sync.beforeCommitCalled).isFalse(); - assertThat(sync.afterCommitCalled).isFalse(); - assertThat(sync.beforeCompletionCalled).isTrue(); - assertThat(sync.afterCompletionCalled).isTrue(); - } - - @Test // gh-107 - public void testPropagationNeverWithExistingTransaction() { - - when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - operator.execute(tx1 -> { - - assertThat(tx1.isNewTransaction()).isTrue(); - - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER); - return operator.execute(tx2 -> { - - fail("Should have thrown IllegalTransactionStateException"); - return Mono.empty(); - }); - }).as(StepVerifier::create) // - .verifyError(IllegalTransactionStateException.class); - - verify(connectionMock).rollbackTransaction(); - verify(connectionMock).close(); - } - - @Test // gh-107 - public void testPropagationSupportsAndRequiresNew() { - - when(connectionMock.commitTransaction()).thenReturn(Mono.empty()); - - DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); - definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); - TransactionalOperator operator = TransactionalOperator.create(tm, definition); - - operator.execute(tx1 -> { - - assertThat(tx1.isNewTransaction()).isFalse(); - - DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition(); - innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - TransactionalOperator inner = TransactionalOperator.create(tm, innerDef); - - return inner.execute(tx2 -> { - - assertThat(tx2.isNewTransaction()).isTrue(); - return Mono.empty(); - }); - }).as(StepVerifier::create) // - .verifyComplete(); - - verify(connectionMock).commitTransaction(); - verify(connectionMock).close(); - } - - private static class TestTransactionSynchronization implements TransactionSynchronization { - - private int status; - - public boolean beforeCommitCalled; - - public boolean beforeCompletionCalled; - - public boolean afterCommitCalled; - - public boolean afterCompletionCalled; - - public Throwable afterCompletionException; - - public TestTransactionSynchronization(int status) { - this.status = status; - } - - @Override - public Mono suspend() { - return Mono.empty(); - } - - @Override - public Mono resume() { - return Mono.empty(); - } - - @Override - public Mono beforeCommit(boolean readOnly) { - if (this.status != TransactionSynchronization.STATUS_COMMITTED) { - fail("Should never be called"); - } - return Mono.fromRunnable(() -> { - assertFalse(this.beforeCommitCalled); - this.beforeCommitCalled = true; - }); - } - - @Override - public Mono beforeCompletion() { - return Mono.fromRunnable(() -> { - assertFalse(this.beforeCompletionCalled); - this.beforeCompletionCalled = true; - }); - } - - @Override - public Mono afterCommit() { - if (this.status != TransactionSynchronization.STATUS_COMMITTED) { - fail("Should never be called"); - } - return Mono.fromRunnable(() -> { - assertFalse(this.afterCommitCalled); - this.afterCommitCalled = true; - }); - } - - @Override - public Mono afterCompletion(int status) { - try { - return Mono.fromRunnable(() -> doAfterCompletion(status)); - } catch (Throwable ex) { - this.afterCompletionException = ex; - } - - return Mono.empty(); - } - - protected void doAfterCompletion(int status) { - assertFalse(this.afterCompletionCalled); - this.afterCompletionCalled = true; - assertTrue(status == this.status); - } - } -} diff --git a/src/test/java/org/springframework/data/r2dbc/connectionfactory/SingleConnectionConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/connectionfactory/SingleConnectionConnectionFactoryUnitTests.java index 09089aa..e3af59e 100644 --- a/src/test/java/org/springframework/data/r2dbc/connectionfactory/SingleConnectionConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/connectionfactory/SingleConnectionConnectionFactoryUnitTests.java @@ -100,23 +100,6 @@ public class SingleConnectionConnectionFactoryUnitTests { factory.destroy(); } - @Test // gh-204 - public void releaseConnectionShouldNotCloseConnection() { - - Connection connectionMock = mock(Connection.class); - ConnectionFactoryMetadata metadata = mock(ConnectionFactoryMetadata.class); - - SingleConnectionConnectionFactory factory = new SingleConnectionConnectionFactory(connectionMock, metadata, false); - - Connection connection = factory.create().block(); - - ConnectionFactoryUtils.releaseConnection(connection, factory) // - .as(StepVerifier::create) // - .verifyComplete(); - - verify(connectionMock, never()).close(); - } - @Test // gh-204 public void releaseConnectionShouldCloseUnrelatedConnection() { diff --git a/src/test/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverterUnitTests.java b/src/test/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverterUnitTests.java index 63496b5..61a9244 100644 --- a/src/test/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverterUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverterUnitTests.java @@ -37,9 +37,9 @@ import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.r2dbc.mapping.OutboundRow; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.r2dbc.core.Parameter; /** * Unit tests for {@link MappingR2dbcConverter}. @@ -72,12 +72,12 @@ public class MappingR2dbcConverterUnitTests { LocalDateTime localDateTime = LocalDateTime.now(); converter.write(new Person("id", "Walter", "White", instant, localDateTime), row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("id"), SettableValue.fromOrEmpty("id", String.class)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("id"), Parameter.fromOrEmpty("id", String.class)); assertThat(row).containsEntry(SqlIdentifier.unquoted("firstname"), - SettableValue.fromOrEmpty("Walter", String.class)); - assertThat(row).containsEntry(SqlIdentifier.unquoted("lastname"), SettableValue.fromOrEmpty("White", String.class)); - assertThat(row).containsEntry(SqlIdentifier.unquoted("instant"), SettableValue.from(instant)); - assertThat(row).containsEntry(SqlIdentifier.unquoted("local_date_time"), SettableValue.from(localDateTime)); + Parameter.fromOrEmpty("Walter", String.class)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("lastname"), Parameter.fromOrEmpty("White", String.class)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("instant"), Parameter.from(instant)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("local_date_time"), Parameter.from(localDateTime)); } @Test // gh-41 @@ -117,7 +117,7 @@ public class MappingR2dbcConverterUnitTests { OutboundRow row = new OutboundRow(); converter.write(withMap, row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("nested"), SettableValue.from("map")); + assertThat(row).containsEntry(SqlIdentifier.unquoted("nested"), Parameter.from("map")); } @Test // gh-59 @@ -138,7 +138,7 @@ public class MappingR2dbcConverterUnitTests { OutboundRow row = new OutboundRow(); converter.write(withMap, row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), SettableValue.from("Mint")); + assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), Parameter.from("Mint")); } @Test // gh-59 @@ -148,7 +148,7 @@ public class MappingR2dbcConverterUnitTests { OutboundRow row = new OutboundRow(); converter.write(withMap, row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), SettableValue.fromOrEmpty(null, String.class)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), Parameter.fromOrEmpty(null, String.class)); } @Test // gh-59 @@ -172,8 +172,8 @@ public class MappingR2dbcConverterUnitTests { OutboundRow row = new OutboundRow(); converter.write(person, row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("foo_column"), SettableValue.from("bar")) - .containsEntry(SqlIdentifier.unquoted("entity"), SettableValue.from("nested_entity")); + assertThat(row).containsEntry(SqlIdentifier.unquoted("foo_column"), Parameter.from("bar")) + .containsEntry(SqlIdentifier.unquoted("entity"), Parameter.from("nested_entity")); } @Test // gh-59 @@ -263,8 +263,8 @@ public class MappingR2dbcConverterUnitTests { public OutboundRow convert(CustomConversionPerson source) { OutboundRow row = new OutboundRow(); - row.put("foo_column", SettableValue.from(source.foo)); - row.put("entity", SettableValue.from("nested_entity")); + row.put("foo_column", Parameter.from(source.foo)); + row.put("entity", Parameter.from("nested_entity")); return row; } diff --git a/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java b/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java index 085c8e4..4860cd1 100644 --- a/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java @@ -32,9 +32,9 @@ import org.springframework.data.convert.CustomConversions; import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.OutboundRow; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.r2dbc.core.Parameter; /** * Postgres-specific unit tests for {@link MappingR2dbcConverter}. @@ -69,7 +69,7 @@ public class PostgresMappingR2dbcConverterUnitTests { OutboundRow row = new OutboundRow(); converter.write(person, row); - assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), SettableValue.from(person.jsonValue)); + assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), Parameter.from(person.jsonValue)); } @AllArgsConstructor diff --git a/src/test/java/org/springframework/data/r2dbc/core/AbstractTransactionalDatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/core/AbstractTransactionalDatabaseClientIntegrationTests.java index 4f819c5..8593581 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/AbstractTransactionalDatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/AbstractTransactionalDatabaseClientIntegrationTests.java @@ -36,9 +36,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.dao.DataAccessException; import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; -import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager; import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.r2dbc.connection.R2dbcTransactionManager; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; @@ -146,7 +147,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend public void executeInsertInManagedTransaction() { Flux integerFlux = databaseClient // - .execute(getInsertIntoLegosetStatement()) // + .sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // @@ -162,7 +163,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend @Test // gh-2 public void executeInsertInAutoCommitTransaction() { - Flux integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) // + Flux integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // @@ -178,7 +179,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend @Test // gh-2 public void shouldRollbackTransaction() { - Mono integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) // + Mono integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // @@ -196,7 +197,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend @Test // gh-2, gh-75, gh-107 public void emitTransactionIds() { - Flux txId = databaseClient.execute(getCurrentTransactionIdStatement()) // + Flux txId = databaseClient.sql(getCurrentTransactionIdStatement()) // .map((row, md) -> row.get(0)) // .all(); @@ -220,7 +221,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend TransactionalOperator transactionalOperator = TransactionalOperator .create(new R2dbcTransactionManager(connectionFactory), new DefaultTransactionDefinition()); - Flux integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) // + Flux integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // @@ -301,7 +302,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend @Transactional public Flux emitTransactionIds(Mono prepareTransaction, String idStatement) { - Flux txId = databaseClient.execute(idStatement) // + Flux txId = databaseClient.sql(idStatement) // .map((row, md) -> row.get(0)) // .all(); @@ -311,7 +312,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend @Transactional public Flux shouldRollbackTransactionUsingTransactionalOperator(String insertStatement) { - return databaseClient.execute(insertStatement) // + return databaseClient.sql(insertStatement) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // diff --git a/src/test/java/org/springframework/data/r2dbc/core/JasyncMySqlTransactionalDatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/core/JasyncMySqlTransactionalDatabaseClientIntegrationTests.java index ec6117a..3a4ddfd 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/JasyncMySqlTransactionalDatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/JasyncMySqlTransactionalDatabaseClientIntegrationTests.java @@ -26,6 +26,7 @@ import org.junit.ClassRule; import org.springframework.data.r2dbc.testing.ExternalDatabase; import org.springframework.data.r2dbc.testing.MySqlTestSupport; +import org.springframework.r2dbc.core.DatabaseClient; /** * Transactional integration tests for {@link DatabaseClient} against MySQL using Jasync MySQL. @@ -62,7 +63,7 @@ public class JasyncMySqlTransactionalDatabaseClientIntegrationTests * batches every now and then. * @see: https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-internal-data.html */ - return client.execute(getInsertIntoLegosetStatement()) // + return client.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // diff --git a/src/test/java/org/springframework/data/r2dbc/core/MariaDbTransactionalDatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/core/MariaDbTransactionalDatabaseClientIntegrationTests.java index 28dcbed..7fea1cc 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/MariaDbTransactionalDatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/MariaDbTransactionalDatabaseClientIntegrationTests.java @@ -26,6 +26,7 @@ import org.junit.ClassRule; import org.springframework.data.r2dbc.testing.ExternalDatabase; import org.springframework.data.r2dbc.testing.MariaDbTestSupport; +import org.springframework.r2dbc.core.DatabaseClient; /** * Transactional integration tests for {@link DatabaseClient} against MariaDb. @@ -61,7 +62,7 @@ public class MariaDbTransactionalDatabaseClientIntegrationTests * And we need to delay emitting the result so that Mariadb has time to write the transaction id, which is done in * batches every now and then. */ - return client.execute(getInsertIntoLegosetStatement()) // + return client.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // diff --git a/src/test/java/org/springframework/data/r2dbc/core/MySqlTransactionalDatabaseClientIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/core/MySqlTransactionalDatabaseClientIntegrationTests.java index baed70f..e18cc56 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/MySqlTransactionalDatabaseClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/MySqlTransactionalDatabaseClientIntegrationTests.java @@ -26,6 +26,7 @@ import org.junit.ClassRule; import org.springframework.data.r2dbc.testing.ExternalDatabase; import org.springframework.data.r2dbc.testing.MySqlTestSupport; +import org.springframework.r2dbc.core.DatabaseClient; /** * Transactional integration tests for {@link DatabaseClient} against MySQL. @@ -62,7 +63,7 @@ public class MySqlTransactionalDatabaseClientIntegrationTests * batches every now and then. * @see: https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-internal-data.html */ - return client.execute(getInsertIntoLegosetStatement()) // + return client.sql(getInsertIntoLegosetStatement()) // .bind(0, 42055) // .bind(1, "SCHAUFELRADBAGGER") // .bindNull(2, Integer.class) // diff --git a/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java b/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java index 6205866..5356051 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/PostgresReactiveDataAccessStrategyTests.java @@ -33,6 +33,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.OutboundRow; import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.r2dbc.core.Parameter; /** * {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}. @@ -88,8 +89,8 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS OutboundRow outboundRow = strategy.getOutboundRow(withArray); assertThat(outboundRow) // - .containsEntry(SqlIdentifier.unquoted("string_array"), SettableValue.from(new String[] { "hello", "world" })) - .containsEntry(SqlIdentifier.unquoted("string_list"), SettableValue.from(new String[] { "hello", "world" })); + .containsEntry(SqlIdentifier.unquoted("string_array"), Parameter.from(new String[] { "hello", "world" })) + .containsEntry(SqlIdentifier.unquoted("string_list"), Parameter.from(new String[] { "hello", "world" })); } @Test // gh-139 @@ -104,7 +105,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS OutboundRow outboundRow = strategy.getOutboundRow(withConversion); assertThat(outboundRow) // - .containsEntry(SqlIdentifier.unquoted("my_objects"), SettableValue.from("[one, two]")); + .containsEntry(SqlIdentifier.unquoted("my_objects"), Parameter.from("[one, two]")); } @Test // gh-139 @@ -121,7 +122,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS assertThat(outboundRow) // .containsKey(SqlIdentifier.unquoted("my_objects")); - SettableValue value = outboundRow.get("my_objects"); + Parameter value = outboundRow.get("my_objects"); assertThat(value.isEmpty()).isTrue(); assertThat(value.getType()).isEqualTo(String.class); } @@ -139,7 +140,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS assertThat(outboundRow).containsKey(SqlIdentifier.unquoted("enum_set")); - SettableValue value = outboundRow.get(SqlIdentifier.unquoted("enum_set")); + Parameter value = outboundRow.get(SqlIdentifier.unquoted("enum_set")); assertThat(value.getValue()).isEqualTo(new String[] { "ONE", "TWO" }); } @@ -156,7 +157,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS assertThat(outboundRow).containsKey(SqlIdentifier.unquoted("enum_array")); - SettableValue value = outboundRow.get(SqlIdentifier.unquoted("enum_array")); + Parameter value = outboundRow.get(SqlIdentifier.unquoted("enum_array")); assertThat(value.getValue()).isEqualTo(new String[] { "ONE", "TWO" }); } diff --git a/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java b/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java index 0dfc007..d749c3c 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java @@ -40,7 +40,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.OutboundRow; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback; import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback; import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback; @@ -52,6 +51,8 @@ import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Update; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.lang.Nullable; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.Parameter; import org.springframework.util.CollectionUtils; /** @@ -61,7 +62,7 @@ import org.springframework.util.CollectionUtils; */ public class R2dbcEntityTemplateUnitTests { - DatabaseClient client; + org.springframework.r2dbc.core.DatabaseClient client; R2dbcEntityTemplate entityTemplate; StatementRecorder recorder; @@ -70,8 +71,8 @@ public class R2dbcEntityTemplateUnitTests { recorder = StatementRecorder.newInstance(); client = DatabaseClient.builder().connectionFactory(recorder) - .dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build(); - entityTemplate = new R2dbcEntityTemplate(client); + .bindMarkers(PostgresDialect.INSTANCE.getBindMarkersFactory()).build(); + entityTemplate = new R2dbcEntityTemplate(client, PostgresDialect.INSTANCE); } @Test // gh-220 @@ -92,7 +93,7 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("SELECT")); assertThat(statement.getSql()).isEqualTo("SELECT COUNT(person.id) FROM person WHERE person.THE_NAME = $1"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 @@ -113,7 +114,7 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("SELECT")); assertThat(statement.getSql()).isEqualTo("SELECT person.id FROM person WHERE person.THE_NAME = $1 LIMIT 1"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 @@ -129,7 +130,7 @@ public class R2dbcEntityTemplateUnitTests { assertThat(statement.getSql()) .isEqualTo("SELECT person.* FROM person WHERE person.THE_NAME = $1 ORDER BY THE_NAME ASC"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-215 @@ -170,7 +171,7 @@ public class R2dbcEntityTemplateUnitTests { assertThat(statement.getSql()) .isEqualTo("SELECT person.* FROM person WHERE person.THE_NAME = $1 ORDER BY THE_NAME ASC LIMIT 2"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 @@ -191,8 +192,8 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE")); assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1 WHERE person.THE_NAME = $2"); - assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("Heisenberg")).containsEntry(1, - SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("Heisenberg")).containsEntry(1, + Parameter.from("Walter")); } @Test // gh-220 @@ -212,7 +213,7 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE")); assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.THE_NAME = $1"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 @@ -229,7 +230,7 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE")); assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.id = $1"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-365 @@ -249,8 +250,8 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT")); assertThat(statement.getSql()).isEqualTo("INSERT INTO versioned_person (id, version, name) VALUES ($1, $2, $3)"); - assertThat(statement.getBindings()).hasSize(3).containsEntry(0, SettableValue.from("id")).containsEntry(1, - SettableValue.from(1L)); + assertThat(statement.getBindings()).hasSize(3).containsEntry(0, Parameter.from("id")).containsEntry(1, + Parameter.from(1L)); } @Test // gh-215 @@ -277,8 +278,8 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT")); assertThat(statement.getSql()).isEqualTo("INSERT INTO person (THE_NAME, description) VALUES ($1, $2)"); - assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("before-convert")) - .containsEntry(1, SettableValue.from("before-save")); + assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("before-convert")).containsEntry(1, + Parameter.from("before-save")); } @Test // gh-365 @@ -299,8 +300,8 @@ public class R2dbcEntityTemplateUnitTests { assertThat(statement.getSql()).isEqualTo( "UPDATE versioned_person SET version = $1, name = $2 WHERE versioned_person.id = $3 AND (versioned_person.version = $4)"); - assertThat(statement.getBindings()).hasSize(4).containsEntry(0, SettableValue.from(2L)).containsEntry(3, - SettableValue.from(1L)); + assertThat(statement.getBindings()).hasSize(4).containsEntry(0, Parameter.from(2L)).containsEntry(3, + Parameter.from(1L)); } @Test // gh-215 @@ -332,8 +333,8 @@ public class R2dbcEntityTemplateUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE")); assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1, description = $2 WHERE person.id = $3"); - assertThat(statement.getBindings()).hasSize(3).containsEntry(0, SettableValue.from("before-convert")) - .containsEntry(1, SettableValue.from("before-save")); + assertThat(statement.getBindings()).hasSize(3).containsEntry(0, Parameter.from("before-convert")).containsEntry(1, + Parameter.from("before-save")); } @ToString @@ -402,7 +403,7 @@ public class R2dbcEntityTemplateUnitTests { public Mono onBeforeSave(Person entity, OutboundRow outboundRow, SqlIdentifier table) { capture(entity); - outboundRow.put(SqlIdentifier.unquoted("description"), SettableValue.from("before-save")); + outboundRow.put(SqlIdentifier.unquoted("description"), Parameter.from("before-save")); return Mono.just(entity); } } diff --git a/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTestSupport.java b/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTestSupport.java index a91ab91..db3dcb7 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTestSupport.java +++ b/src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTestSupport.java @@ -38,8 +38,8 @@ import org.junit.Test; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.r2dbc.dialect.R2dbcDialect; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.r2dbc.core.Parameter; /** * Abstract base class for {@link R2dbcDialect}-aware {@link DefaultReactiveDataAccessStrategy} tests. @@ -205,7 +205,7 @@ public abstract class ReactiveDataAccessStrategyTestSupport { setter.accept(toSave, testValue); assertThat(strategy.getOutboundRow(toSave)).containsEntry(SqlIdentifier.unquoted(fieldname), - SettableValue.from(testValue)); + Parameter.from(testValue)); when(rowMock.get(fieldname)).thenReturn(testValue); diff --git a/src/test/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationUnitTests.java b/src/test/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationUnitTests.java index 05d8701..0d444a9 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationUnitTests.java @@ -30,6 +30,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.testing.StatementRecorder; import org.springframework.data.relational.core.mapping.Column; +import org.springframework.r2dbc.core.Parameter; /** * Unit test for {@link ReactiveDeleteOperation}. @@ -104,7 +105,7 @@ public class ReactiveDeleteOperationUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE")); assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.THE_NAME = $1"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 diff --git a/src/test/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationUnitTests.java b/src/test/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationUnitTests.java index f5b98f8..1e05bb7 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationUnitTests.java @@ -31,6 +31,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.testing.StatementRecorder; import org.springframework.data.relational.core.mapping.Column; +import org.springframework.r2dbc.core.Parameter; /** * Unit test for {@link ReactiveInsertOperation}. @@ -77,7 +78,7 @@ public class ReactiveInsertOperationUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT")); assertThat(statement.getSql()).isEqualTo("INSERT INTO person (THE_NAME) VALUES ($1)"); - assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter")); } @Test // gh-220 diff --git a/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationUnitTests.java b/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationUnitTests.java index f29797b..02d18e1 100644 --- a/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationUnitTests.java @@ -27,10 +27,10 @@ import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.r2dbc.dialect.PostgresDialect; -import org.springframework.data.r2dbc.mapping.SettableValue; import org.springframework.data.r2dbc.testing.StatementRecorder; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.query.Update; +import org.springframework.r2dbc.core.Parameter; /** * Unit test for {@link ReactiveUpdateOperation}. @@ -107,8 +107,8 @@ public class ReactiveUpdateOperationUnitTests { StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE")); assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1 WHERE person.THE_NAME = $2"); - assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("Heisenberg")).containsEntry(1, - SettableValue.from("Walter")); + assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("Heisenberg")).containsEntry(1, + Parameter.from("Walter")); } @Test // gh-220 diff --git a/src/test/java/org/springframework/data/r2dbc/repository/ConvertingR2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/ConvertingR2dbcRepositoryIntegrationTests.java index a232fe9..c2b8a60 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/ConvertingR2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/ConvertingR2dbcRepositoryIntegrationTests.java @@ -49,6 +49,7 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.data.r2dbc.testing.H2TestSupport; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.r2dbc.core.Parameter; import org.springframework.test.context.junit4.SpringRunner; /** @@ -149,10 +150,10 @@ public class ConvertingR2dbcRepositoryIntegrationTests { OutboundRow outboundRow = new OutboundRow(); if (convertedEntity.getId() != null) { - outboundRow.put("id", SettableValue.from(convertedEntity.getId())); + outboundRow.put("id", Parameter.from(convertedEntity.getId())); } - outboundRow.put("name", SettableValue.from("prefixed: " + convertedEntity.getName())); + outboundRow.put("name", Parameter.from("prefixed: " + convertedEntity.getName())); return outboundRow; } diff --git a/src/test/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrarTests.java b/src/test/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrarTests.java index e7c21af..470e7da 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrarTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoriesRegistrarTests.java @@ -26,13 +26,13 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.MySqlDialect; import org.springframework.data.r2dbc.dialect.PostgresDialect; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.dialect.SqlServerDialect; import org.springframework.data.r2dbc.repository.config.mysql.MySqlPersonRepository; import org.springframework.data.r2dbc.repository.config.sqlserver.SqlServerPersonRepository; diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java index 3159d34..986fa4d 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQueryUnitTests.java @@ -38,7 +38,7 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.dialect.DialectResolver; @@ -88,8 +88,7 @@ public class PartTreeR2dbcQueryUnitTests { R2dbcDialect dialect = DialectResolver.getDialect(connectionFactory); dataAccessStrategy = new DefaultReactiveDataAccessStrategy(dialect, r2dbcConverter); - databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory) - .dataAccessStrategy(dataAccessStrategy).build(); + databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory).build(); } @Test // gh-282 @@ -166,7 +165,7 @@ public class PartTreeR2dbcQueryUnitTests { assertThat(bindableQuery.get()) .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2"); - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); when(bindSpecMock.bind(anyInt(), any())).thenReturn(bindSpecMock); bindableQuery.bind(bindSpecMock); @@ -325,7 +324,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); verify(bindSpecMock, times(1)).bind(0, "Jo%"); @@ -353,7 +352,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); verify(bindSpecMock, times(1)).bind(0, "%hn"); @@ -381,7 +380,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); verify(bindSpecMock, times(1)).bind(0, "%oh%"); @@ -409,7 +408,7 @@ public class PartTreeR2dbcQueryUnitTests { dataAccessStrategy); RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor); - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); bindableQuery.bind(bindSpecMock); verify(bindSpecMock, times(1)).bind(0, "%oh%"); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQueryUnitTests.java index 3ad79db..ad585e1 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQueryUnitTests.java @@ -23,11 +23,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.core.PreparedOperation; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.PreparedOperation; /** + * Unit tests for {@link PreparedOperationBindableQuery}. + * * @author Roman Chigvintsev + * @author Marl Paluch */ @RunWith(MockitoJUnitRunner.class) @Ignore @@ -35,11 +38,10 @@ public class PreparedOperationBindableQueryUnitTests { @Mock PreparedOperation preparedOperation; - @SuppressWarnings({ "rawtypes", "unchecked" }) @Test // gh-282 public void bindsQueryParameterValues() { - DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class); + DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class); PreparedOperationBindableQuery query = new PreparedOperationBindableQuery(preparedOperation); query.bind(bindSpecMock); diff --git a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java index 9fdc9b8..66b68f2 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQueryUnitTests.java @@ -32,8 +32,8 @@ import org.springframework.data.geo.Point; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; -import org.springframework.data.r2dbc.core.DatabaseClient.GenericExecuteSpec; +import org.springframework.r2dbc.core.DatabaseClient; +import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; import org.springframework.data.r2dbc.repository.Query; import org.springframework.data.relational.core.mapping.RelationalMappingContext; diff --git a/src/test/java/org/springframework/data/r2dbc/repository/support/AbstractSimpleR2dbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/r2dbc/repository/support/AbstractSimpleR2dbcRepositoryIntegrationTests.java index 0c4329b..156c333 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/support/AbstractSimpleR2dbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/support/AbstractSimpleR2dbcRepositoryIntegrationTests.java @@ -41,7 +41,6 @@ import org.springframework.data.annotation.Version; import org.springframework.data.domain.Persistable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -50,6 +49,7 @@ import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.r2dbc.core.DatabaseClient; /** * Abstract integration tests for {@link SimpleR2dbcRepository} to be ran against various databases. diff --git a/src/test/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryUnitTests.java index dea67ea..0860adf 100644 --- a/src/test/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryUnitTests.java @@ -27,12 +27,12 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; import org.springframework.data.r2dbc.convert.R2dbcConverter; -import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation; import org.springframework.data.repository.Repository; +import org.springframework.r2dbc.core.DatabaseClient; /** * Unit test for {@link R2dbcRepositoryFactory}. diff --git a/src/test/java/org/springframework/data/r2dbc/testing/StatementRecorder.java b/src/test/java/org/springframework/data/r2dbc/testing/StatementRecorder.java index d8dbd08..589ec48 100644 --- a/src/test/java/org/springframework/data/r2dbc/testing/StatementRecorder.java +++ b/src/test/java/org/springframework/data/r2dbc/testing/StatementRecorder.java @@ -40,7 +40,7 @@ import java.util.regex.Pattern; import org.reactivestreams.Publisher; -import org.springframework.data.r2dbc.mapping.SettableValue; +import org.springframework.r2dbc.core.Parameter; /** * Recorder utility for R2DBC {@link Statement}s. Allows stubbing and introspection. @@ -255,7 +255,7 @@ public class StatementRecorder implements ConnectionFactory { private final List results; - private final Map bindings = new LinkedHashMap<>(); + private final Map bindings = new LinkedHashMap<>(); public RecordedStatement(String sql, Result result) { this(sql, Collections.singletonList(result)); @@ -266,7 +266,7 @@ public class StatementRecorder implements ConnectionFactory { this.results = results; } - public Map getBindings() { + public Map getBindings() { return bindings; } @@ -281,25 +281,25 @@ public class StatementRecorder implements ConnectionFactory { @Override public Statement bind(int index, Object o) { - this.bindings.put(index, SettableValue.from(o)); + this.bindings.put(index, Parameter.from(o)); return this; } @Override public Statement bind(String identifier, Object o) { - this.bindings.put(identifier, SettableValue.from(o)); + this.bindings.put(identifier, Parameter.from(o)); return this; } @Override public Statement bindNull(int index, Class type) { - this.bindings.put(index, SettableValue.empty(type)); + this.bindings.put(index, Parameter.empty(type)); return this; } @Override public Statement bindNull(String identifier, Class type) { - this.bindings.put(identifier, SettableValue.empty(type)); + this.bindings.put(identifier, Parameter.empty(type)); return this; }