Skip to content

Commit e8d3da2

Browse files
committed
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
1 parent d0a7f21 commit e8d3da2

File tree

3 files changed

+93
-16
lines changed

3 files changed

+93
-16
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java

+46-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.concurrent.CompletionStage;
2021
import java.util.function.Consumer;
2122
import java.util.function.Function;
2223
import java.util.stream.Collectors;
@@ -33,6 +34,7 @@
3334
import org.springframework.dao.DataAccessException;
3435
import org.springframework.dao.OptimisticLockingFailureException;
3536
import org.springframework.dao.support.DataAccessUtils;
37+
import org.springframework.dao.support.PersistenceExceptionTranslator;
3638
import org.springframework.data.cassandra.SessionFactory;
3739
import org.springframework.data.cassandra.core.EntityOperations.AdaptibleEntity;
3840
import org.springframework.data.cassandra.core.convert.CassandraConverter;
@@ -729,6 +731,19 @@ public ListenableFuture<Void> truncate(Class<?> entityClass) {
729731
// Implementation hooks and utility methods
730732
// -------------------------------------------------------------------------
731733

734+
/**
735+
* Create a new statement-based {@link AsyncPreparedStatementHandler} using the statement passed in.
736+
* <p>
737+
* This method allows for the creation to be overridden by subclasses.
738+
*
739+
* @param statement the statement to be prepared.
740+
* @return the new {@link PreparedStatementHandler} to use.
741+
* @since 3.3.3
742+
*/
743+
protected AsyncPreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
744+
return new PreparedStatementHandler(statement, exceptionTranslator);
745+
}
746+
732747
private <T> ListenableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
733748
SimpleStatement statement) {
734749

@@ -781,7 +796,7 @@ private <T> ListenableFuture<List<T>> doQuery(Statement<?> statement, RowMapper<
781796

782797
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
783798

784-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
799+
AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
785800
return getAsyncCqlOperations().query(statementHandler, statementHandler, rowMapper);
786801
}
787802

@@ -792,7 +807,7 @@ private ListenableFuture<Void> doQuery(Statement<?> statement, RowCallbackHandle
792807

793808
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
794809

795-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
810+
AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
796811
return getAsyncCqlOperations().query(statementHandler, statementHandler, callbackHandler);
797812
}
798813

@@ -807,7 +822,7 @@ private <T> ListenableFuture<T> doExecute(Statement<?> statement, Function<Async
807822

808823
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
809824

810-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
825+
AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
811826
return getAsyncCqlOperations().query(statementHandler, statementHandler,
812827
(AsyncResultSetExtractor<T>) resultSet -> new AsyncResult<>(mappingFunction.apply(resultSet)));
813828
}
@@ -925,24 +940,48 @@ protected T adapt(@Nullable S adapteeResult) {
925940
}
926941
}
927942

943+
/**
944+
* General callback interface used to create and bind prepared CQL statements.
945+
* <p>
946+
* This interface prepares the CQL statement and sets values on a {@link PreparedStatement} as union-type comprised
947+
* from {@link AsyncPreparedStatementCreator}, {@link PreparedStatementBinder}, and {@link CqlProvider}.
948+
*
949+
* @since 3.3.3
950+
*/
951+
public interface AsyncPreparedStatementHandler
952+
extends AsyncPreparedStatementCreator, PreparedStatementBinder, CqlProvider {
953+
954+
}
955+
928956
/**
929957
* Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a
930958
* {@link BoundStatement}.
931959
*
932960
* @since 3.2
933961
*/
934-
private class PreparedStatementHandler
935-
implements AsyncPreparedStatementCreator, PreparedStatementBinder, CqlProvider {
962+
public static class PreparedStatementHandler implements AsyncPreparedStatementHandler {
936963

937964
private final SimpleStatement statement;
965+
private final PersistenceExceptionTranslator exceptionTranslator;
938966

939-
public PreparedStatementHandler(Statement<?> statement) {
967+
public PreparedStatementHandler(Statement<?> statement, PersistenceExceptionTranslator exceptionTranslator) {
940968
this.statement = PreparedStatementDelegate.getStatementForPrepare(statement);
969+
this.exceptionTranslator = exceptionTranslator;
941970
}
942971

943972
@Override
944973
public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
945-
return new CassandraFutureAdapter<>(session.prepareAsync(statement), exceptionTranslator);
974+
return new CassandraFutureAdapter<>(doPrepare(session), exceptionTranslator);
975+
}
976+
977+
/**
978+
* Invokes the statement preparation.
979+
*
980+
* @param session
981+
* @return
982+
*/
983+
protected CompletionStage<PreparedStatement> doPrepare(CqlSession session) {
984+
return session.prepareAsync(statement);
946985
}
947986

948987
@Override

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,19 @@ public ExecutableDelete delete(Class<?> domainType) {
775775
// Implementation hooks and utility methods
776776
// -------------------------------------------------------------------------
777777

778+
/**
779+
* Create a new statement-based {@link PreparedStatementHandler} using the statement passed in.
780+
* <p>
781+
* This method allows for the creation to be overridden by subclasses.
782+
*
783+
* @param statement the statement to be prepared.
784+
* @return the new {@link PreparedStatementHandler} to use.
785+
* @since 3.3.3
786+
*/
787+
protected PreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
788+
return new PreparedStatementHandler(statement);
789+
}
790+
778791
private <T> EntityWriteResult<T> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) {
779792
return executeSave(entity, tableName, statement, ignore -> {});
780793
}
@@ -811,7 +824,7 @@ private <T> List<T> doQuery(Statement<?> statement, RowMapper<T> rowMapper) {
811824

812825
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
813826

814-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
827+
PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
815828
return getCqlOperations().query(statementHandler, statementHandler, rowMapper);
816829
}
817830

@@ -826,7 +839,7 @@ private <T> Stream<T> doQueryForStream(Statement<?> statement, RowMapper<T> rowM
826839

827840
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
828841

829-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
842+
PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
830843
return getCqlOperations().queryForStream(statementHandler, statementHandler, rowMapper);
831844
}
832845

@@ -845,7 +858,7 @@ private <T> T doExecute(Statement<?> statement, Function<ResultSet, T> mappingFu
845858

846859
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
847860

848-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
861+
PreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
849862
return getCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply);
850863
}
851864

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java

+31-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.data.cassandra.core;
1717

18-
import org.springframework.data.projection.EntityProjection;
1918
import reactor.core.publisher.Flux;
2019
import reactor.core.publisher.Mono;
2120
import reactor.core.publisher.SynchronousSink;
@@ -62,6 +61,7 @@
6261
import org.springframework.data.domain.SliceImpl;
6362
import org.springframework.data.mapping.callback.EntityCallbacks;
6463
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
64+
import org.springframework.data.projection.EntityProjection;
6565
import org.springframework.data.projection.ProjectionFactory;
6666
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
6767
import org.springframework.lang.Nullable;
@@ -753,6 +753,19 @@ public ReactiveUpdate update(Class<?> domainType) {
753753
// Implementation hooks and utility methods
754754
// -------------------------------------------------------------------------
755755

756+
/**
757+
* Create a new statement-based {@link ReactivePreparedStatementHandler} using the statement passed in.
758+
* <p>
759+
* This method allows for the creation to be overridden by subclasses.
760+
*
761+
* @param statement the statement to be prepared.
762+
* @return the new {@link PreparedStatementHandler} to use.
763+
* @since 3.3.3
764+
*/
765+
protected ReactivePreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
766+
return new PreparedStatementHandler(statement);
767+
}
768+
756769
private <T> Mono<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName, SimpleStatement statement) {
757770
return executeSave(entity, tableName, statement, (writeResult, sink) -> sink.next(writeResult));
758771
}
@@ -789,7 +802,7 @@ private <T> Flux<T> doQuery(Statement<?> statement, RowMapper<T> rowMapper) {
789802

790803
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
791804

792-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
805+
ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
793806
return getReactiveCqlOperations().query(statementHandler, statementHandler, rowMapper);
794807
}
795808

@@ -800,7 +813,7 @@ private <T> Mono<T> doExecute(Statement<?> statement, Function<ReactiveResultSet
800813

801814
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
802815

803-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
816+
ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
804817
return getReactiveCqlOperations()
805818
.query(statementHandler, statementHandler, rs -> Mono.just(mappingFunction.apply(rs))).next();
806819
}
@@ -813,7 +826,7 @@ private <T> Mono<T> doExecuteAndFlatMap(Statement<?> statement,
813826

814827
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
815828

816-
PreparedStatementHandler statementHandler = new PreparedStatementHandler(statement);
829+
ReactivePreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
817830
return getReactiveCqlOperations().query(statementHandler, statementHandler, mappingFunction::apply).next();
818831
}
819832

@@ -915,14 +928,26 @@ protected <T> Mono<T> maybeCallBeforeSave(T object, CqlIdentifier tableName, Sta
915928
return Mono.just(object);
916929
}
917930

931+
/**
932+
* General callback interface used to create and bind prepared CQL statements.
933+
* <p>
934+
* This interface prepares the CQL statement and sets values on a {@link PreparedStatement} as union-type comprised
935+
* from {@link ReactivePreparedStatementCreator}, {@link PreparedStatementBinder}, and {@link CqlProvider}.
936+
*
937+
* @since 3.3.3
938+
*/
939+
public interface ReactivePreparedStatementHandler
940+
extends ReactivePreparedStatementCreator, PreparedStatementBinder, CqlProvider {
941+
942+
}
943+
918944
/**
919945
* Utility class to prepare a {@link SimpleStatement} and bind values associated with the statement to a
920946
* {@link BoundStatement}.
921947
*
922948
* @since 3.2
923949
*/
924-
private static class PreparedStatementHandler
925-
implements ReactivePreparedStatementCreator, PreparedStatementBinder, CqlProvider {
950+
public static class PreparedStatementHandler implements ReactivePreparedStatementHandler {
926951

927952
private final SimpleStatement statement;
928953

0 commit comments

Comments
 (0)