Introduce customization hook for PreparedStatementHandler.

CassandraTemplate and its asynchronous and reactive variants now define a createPreparedStatementHandler() template method to customize CQL preparation and binding.

Closes #1237
This commit is contained in:
Mark Paluch
2022-03-04 13:59:19 +01:00
parent d0a7f21fcf
commit e8d3da2d6f
3 changed files with 93 additions and 16 deletions

View File

@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.core;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -33,6 +34,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.DataAccessUtils; import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.SessionFactory; import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.EntityOperations.AdaptibleEntity; import org.springframework.data.cassandra.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.cassandra.core.convert.CassandraConverter; import org.springframework.data.cassandra.core.convert.CassandraConverter;
@@ -729,6 +731,19 @@ public class AsyncCassandraTemplate
// Implementation hooks and utility methods // Implementation hooks and utility methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/**
* Create a new statement-based {@link AsyncPreparedStatementHandler} using the statement passed in.
* <p>
* This method allows for the creation to be overridden by subclasses.
*
* @param statement the statement to be prepared.
* @return the new {@link PreparedStatementHandler} to use.
* @since 3.3.3
*/
protected AsyncPreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
return new PreparedStatementHandler(statement, exceptionTranslator);
}
private <T> ListenableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName, private <T> ListenableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
SimpleStatement statement) { SimpleStatement statement) {
@@ -781,7 +796,7 @@ public class AsyncCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getAsyncCqlOperations().query(statementHandler, statementHandler, rowMapper); return getAsyncCqlOperations().query(statementHandler, statementHandler, rowMapper);
} }
@@ -792,7 +807,7 @@ public class AsyncCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getAsyncCqlOperations().query(statementHandler, statementHandler, callbackHandler); return getAsyncCqlOperations().query(statementHandler, statementHandler, callbackHandler);
} }
@@ -807,7 +822,7 @@ public class AsyncCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getAsyncCqlOperations().query(statementHandler, statementHandler, return getAsyncCqlOperations().query(statementHandler, statementHandler,
(AsyncResultSetExtractor<T>) resultSet -> new AsyncResult<>(mappingFunction.apply(resultSet))); (AsyncResultSetExtractor<T>) resultSet -> new AsyncResult<>(mappingFunction.apply(resultSet)));
} }
@@ -925,24 +940,48 @@ public class AsyncCassandraTemplate
} }
} }
/**
* General callback interface used to create and bind prepared CQL statements.
* <p>
* This interface prepares the CQL statement and sets values on a {@link PreparedStatement} as union-type comprised
* from {@link AsyncPreparedStatementCreator}, {@link PreparedStatementBinder}, and {@link CqlProvider}.
*
* @since 3.3.3
*/
public interface AsyncPreparedStatementHandler
extends AsyncPreparedStatementCreator, PreparedStatementBinder, CqlProvider {
}
/** /**
* Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a * Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a
* {@link BoundStatement}. * {@link BoundStatement}.
* *
* @since 3.2 * @since 3.2
*/ */
private class PreparedStatementHandler public static class PreparedStatementHandler implements AsyncPreparedStatementHandler {
implements AsyncPreparedStatementCreator, PreparedStatementBinder, CqlProvider {
private final SimpleStatement statement; private final SimpleStatement statement;
private final PersistenceExceptionTranslator exceptionTranslator;
public PreparedStatementHandler(Statement<?> statement) { public PreparedStatementHandler(Statement<?> statement, PersistenceExceptionTranslator exceptionTranslator) {
this.statement = PreparedStatementDelegate.getStatementForPrepare(statement); this.statement = PreparedStatementDelegate.getStatementForPrepare(statement);
this.exceptionTranslator = exceptionTranslator;
} }
@Override @Override
public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException { public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return new CassandraFutureAdapter<>(session.prepareAsync(statement), exceptionTranslator); return new CassandraFutureAdapter<>(doPrepare(session), exceptionTranslator);
}
/**
* Invokes the statement preparation.
*
* @param session
* @return
*/
protected CompletionStage<PreparedStatement> doPrepare(CqlSession session) {
return session.prepareAsync(statement);
} }
@Override @Override

View File

@@ -775,6 +775,19 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
// Implementation hooks and utility methods // Implementation hooks and utility methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/**
* Create a new statement-based {@link PreparedStatementHandler} using the statement passed in.
* <p>
* This method allows for the creation to be overridden by subclasses.
*
* @param statement the statement to be prepared.
* @return the new {@link PreparedStatementHandler} to use.
* @since 3.3.3
*/
protected PreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
return new PreparedStatementHandler(statement);
}
private <T> EntityWriteResult<T> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) { private <T> EntityWriteResult<T> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) {
return executeSave(entity, tableName, statement, ignore -> {}); return executeSave(entity, tableName, statement, ignore -> {});
} }
@@ -811,7 +824,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getCqlOperations().query(statementHandler, statementHandler, rowMapper); return getCqlOperations().query(statementHandler, statementHandler, rowMapper);
} }
@@ -826,7 +839,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getCqlOperations().queryForStream(statementHandler, statementHandler, rowMapper); return getCqlOperations().queryForStream(statementHandler, statementHandler, rowMapper);
} }
@@ -845,7 +858,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply); return getCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply);
} }

View File

@@ -15,7 +15,6 @@
*/ */
package org.springframework.data.cassandra.core; package org.springframework.data.cassandra.core;
import org.springframework.data.projection.EntityProjection;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink; import reactor.core.publisher.SynchronousSink;
@@ -62,6 +61,7 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl; import org.springframework.data.domain.SliceImpl;
import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -753,6 +753,19 @@ public class ReactiveCassandraTemplate
// Implementation hooks and utility methods // Implementation hooks and utility methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/**
* Create a new statement-based {@link ReactivePreparedStatementHandler} using the statement passed in.
* <p>
* This method allows for the creation to be overridden by subclasses.
*
* @param statement the statement to be prepared.
* @return the new {@link PreparedStatementHandler} to use.
* @since 3.3.3
*/
protected ReactivePreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
return new PreparedStatementHandler(statement);
}
private <T> Mono<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) { private <T> Mono<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) {
return executeSave(entity, tableName, statement, (writeResult, sink) -> sink.next(writeResult)); return executeSave(entity, tableName, statement, (writeResult, sink) -> sink.next(writeResult));
} }
@@ -789,7 +802,7 @@ public class ReactiveCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getReactiveCqlOperations().query(statementHandler, statementHandler, rowMapper); return getReactiveCqlOperations().query(statementHandler, statementHandler, rowMapper);
} }
@@ -800,7 +813,7 @@ public class ReactiveCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getReactiveCqlOperations() return getReactiveCqlOperations()
.query(statementHandler, statementHandler, rs -> Mono.just(mappingFunction.apply(rs))).next(); .query(statementHandler, statementHandler, rs -> Mono.just(mappingFunction.apply(rs))).next();
} }
@@ -813,7 +826,7 @@ public class ReactiveCassandraTemplate
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) { if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement); ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getReactiveCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply).next(); return getReactiveCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply).next();
} }
@@ -915,14 +928,26 @@ public class ReactiveCassandraTemplate
return Mono.just(object); return Mono.just(object);
} }
/**
* General callback interface used to create and bind prepared CQL statements.
* <p>
* This interface prepares the CQL statement and sets values on a {@link PreparedStatement} as union-type comprised
* from {@link ReactivePreparedStatementCreator}, {@link PreparedStatementBinder}, and {@link CqlProvider}.
*
* @since 3.3.3
*/
public interface ReactivePreparedStatementHandler
extends ReactivePreparedStatementCreator, PreparedStatementBinder, CqlProvider {
}
/** /**
* Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a * Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a
* {@link BoundStatement}. * {@link BoundStatement}.
* *
* @since 3.2 * @since 3.2
*/ */
private static class PreparedStatementHandler public static class PreparedStatementHandler implements ReactivePreparedStatementHandler {
implements ReactivePreparedStatementCreator, PreparedStatementBinder, CqlProvider {
private final SimpleStatement statement; private final SimpleStatement statement;