From fbcf50e27ae3645797d173bf1a869cf193e08cd0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 Jul 2018 11:28:10 +0200 Subject: [PATCH] DATACASS-573 - Add mutation support for immutable types. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now return new instances that are potentially created by wither/Kotlin copy(…) methods when reading immutable properties. --- .../core/AsyncCassandraOperations.java | 8 +- .../core/AsyncCassandraTemplate.java | 10 +-- .../cassandra/core/CassandraOperations.java | 14 ++-- .../cassandra/core/CassandraTemplate.java | 18 ++--- .../cassandra/core/EntityWriteResult.java | 73 +++++++++++++++++++ .../core/ExecutableInsertOperation.java | 5 +- .../ExecutableInsertOperationSupport.java | 2 +- .../core/ReactiveCassandraOperations.java | 8 +- .../core/ReactiveCassandraTemplate.java | 28 ++++--- .../core/ReactiveInsertOperation.java | 5 +- .../core/ReactiveInsertOperationSupport.java | 3 +- .../data/cassandra/core/WriteResult.java | 4 +- .../convert/MappingCassandraConverter.java | 19 ++--- .../support/SimpleCassandraRepository.java | 15 +--- ...syncCassandraTemplateIntegrationTests.java | 17 +++-- .../CassandraTemplateIntegrationTests.java | 19 +++-- ...tiveCassandraTemplateIntegrationTests.java | 10 ++- ...nsertOperationSupportIntegrationTests.java | 23 +++--- ...leCassandraRepositoryIntegrationTests.java | 8 +- .../SimpleCassandraRepositoryUnitTests.java | 17 ++++- 20 files changed, 204 insertions(+), 102 deletions(-) create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/EntityWriteResult.java diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java index 37b2d86a1..28c87b149 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java @@ -292,11 +292,11 @@ public interface AsyncCassandraOperations { * * @param entity The entity to insert, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem executing the query. * @see InsertOptions#empty() */ - ListenableFuture insert(Object entity, InsertOptions options) throws DataAccessException; + ListenableFuture> insert(T entity, InsertOptions options) throws DataAccessException; /** * Update the given entity and return the entity if the update was applied. @@ -312,11 +312,11 @@ public interface AsyncCassandraOperations { * * @param entity The entity to update, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem executing the query. * @see UpdateOptions#empty() */ - ListenableFuture update(Object entity, UpdateOptions options) throws DataAccessException; + ListenableFuture> update(T entity, UpdateOptions options) throws DataAccessException; /** * Delete the given entity and return the entity if the delete was applied. diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java index 9be84554b..cdb70d6ce 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java @@ -497,7 +497,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions) */ @Override - public ListenableFuture insert(Object entity, InsertOptions options) { + public ListenableFuture> insert(T entity, InsertOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "InsertOptions must not be null"); @@ -511,7 +511,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica return new MappingListenableFutureAdapter<>(getAsyncCqlOperations().execute(new AsyncStatementCallback(insert)), resultSet -> { maybeEmitEvent(new AfterSaveEvent<>(entity, tableName)); - return WriteResult.of(resultSet); + return EntityWriteResult.of(resultSet, entity); }); } @@ -520,14 +520,14 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica */ @Override public ListenableFuture update(T entity) { - return new MappingListenableFutureAdapter<>(update(entity, UpdateOptions.empty()), writeResult -> entity); + return new MappingListenableFutureAdapter<>(update(entity, UpdateOptions.empty()), EntityWriteResult::getEntity); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions) */ @Override - public ListenableFuture update(Object entity, UpdateOptions options) { + public ListenableFuture> update(T entity, UpdateOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "UpdateOptions must not be null"); @@ -540,7 +540,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica return new MappingListenableFutureAdapter<>(getAsyncCqlOperations().execute(new AsyncStatementCallback(update)), resultSet -> { maybeEmitEvent(new AfterSaveEvent<>(entity, tableName)); - return WriteResult.of(resultSet); + return EntityWriteResult.of(resultSet, entity); }); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java index e1f9ad3b5..86827b4d0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java @@ -310,39 +310,41 @@ public interface CassandraOperations extends FluentCassandraOperations { * Insert the given entity and return the entity if the insert was applied. * * @param entity The entity to insert, must not be {@literal null}. + * @return the inserted entity. * @throws DataAccessException if there is any problem executing the query. */ - void insert(Object entity) throws DataAccessException; + T insert(T entity) throws DataAccessException; /** * Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied. * * @param entity The entity to insert, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem executing the query. * @see InsertOptions#empty() */ - WriteResult insert(Object entity, InsertOptions options) throws DataAccessException; + EntityWriteResult insert(T entity, InsertOptions options) throws DataAccessException; /** * Update the given entity and return the entity if the update was applied. * * @param entity The entity to update, must not be {@literal null}. + * @return the updated entity. * @throws DataAccessException if there is any problem executing the query. */ - void update(Object entity) throws DataAccessException; + T update(T entity) throws DataAccessException; /** * Update the given entity applying {@link WriteOptions} and return the entity if the update was applied. * * @param entity The entity to update, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem executing the query. * @see UpdateOptions#empty() */ - WriteResult update(Object entity, UpdateOptions options) throws DataAccessException; + EntityWriteResult update(T entity, UpdateOptions options) throws DataAccessException; /** * Delete the given entity and return the entity if the delete was applied. diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java index 214c6daf9..ef24444cd 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -531,15 +531,15 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object) */ @Override - public void insert(Object entity) { - insert(entity, InsertOptions.empty()); + public T insert(T entity) { + return insert(entity, InsertOptions.empty()).getEntity(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions) */ @Override - public WriteResult insert(Object entity, InsertOptions options) { + public EntityWriteResult insert(T entity, InsertOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "InsertOptions must not be null"); @@ -547,7 +547,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP return doInsert(entity, options, getTableName(entity)); } - WriteResult doInsert(Object entity, WriteOptions options, CqlIdentifier tableName) { + EntityWriteResult doInsert(T entity, WriteOptions options, CqlIdentifier tableName) { CassandraPersistentEntity persistentEntity = getRequiredPersistentEntity(entity.getClass()); @@ -560,22 +560,22 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP maybeEmitEvent(new AfterSaveEvent<>(entity, tableName)); - return result; + return EntityWriteResult.of(result, entity); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object) */ @Override - public void update(Object entity) { - update(entity, UpdateOptions.empty()); + public T update(T entity) { + return update(entity, UpdateOptions.empty()).getEntity(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions) */ @Override - public WriteResult update(Object entity, UpdateOptions options) { + public EntityWriteResult update(T entity, UpdateOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "UpdateOptions must not be null"); @@ -590,7 +590,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP maybeEmitEvent(new AfterSaveEvent<>(entity, tableName)); - return result; + return EntityWriteResult.of(result, entity); } /* (non-Javadoc) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/EntityWriteResult.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/EntityWriteResult.java new file mode 100644 index 000000000..9f5f93308 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/EntityWriteResult.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 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 + * + * http://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.cassandra.core; + +import java.util.List; + +import com.datastax.driver.core.ExecutionInfo; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; + +/** + * The result of a write operation for an entity. + * + * @author Mark Paluch + * @since 2.1 + * @see WriteResult + */ +public class EntityWriteResult extends WriteResult { + + private final T entity; + + EntityWriteResult(List executionInfo, boolean wasApplied, List rows, T entity) { + super(executionInfo, wasApplied, rows); + this.entity = entity; + } + + EntityWriteResult(ResultSet resultSet, T entity) { + super(resultSet); + this.entity = entity; + } + + /** + * Create a {@link EntityWriteResult} from {@link WriteResult} and an entity. + * + * @param result must not be {@literal null}. + * @param entity must not be {@literal null}. + * @return the {@link EntityWriteResult} for {@link WriteResult} and an entity. + */ + static EntityWriteResult of(WriteResult result, T entity) { + return new EntityWriteResult<>(result.getExecutionInfo(), result.wasApplied(), result.getRows(), entity); + } + + /** + * Create a {@link EntityWriteResult} from {@link ResultSet} and an entity. + * + * @param resultSet must not be {@literal null}. + * @param entity must not be {@literal null}. + * @return the {@link EntityWriteResult} for {@link ResultSet} and an entity. + */ + static EntityWriteResult of(ResultSet resultSet, T entity) { + return new EntityWriteResult<>(resultSet, entity); + } + + /** + * @return the entity associated with this write operation result. + */ + public T getEntity() { + return entity; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperation.java index c55755671..ca6d39c84 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperation.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperation.java @@ -114,10 +114,11 @@ public interface ExecutableInsertOperation { * Insert exactly one {@link Object}. * * @param object {@link Object} to insert; must not be {@literal null}. + * @return the {@link EntityWriteResult} for this operation. * @throws IllegalArgumentException if {@link Object} is {@literal null}. - * @see org.springframework.data.cassandra.core.WriteResult + * @see org.springframework.data.cassandra.core.EntityWriteResult */ - WriteResult one(T object); + EntityWriteResult one(T object); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperationSupport.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperationSupport.java index 32de1fb9a..c1904010c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperationSupport.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ExecutableInsertOperationSupport.java @@ -91,7 +91,7 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation { * @see org.springframework.data.cassandra.core.ExecutableInsertOperation.TerminatingInsert#one(java.lang.Object) */ @Override - public WriteResult one(T object) { + public EntityWriteResult one(T object) { Assert.notNull(object, "Object must not be null"); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraOperations.java index 59e7f6114..d6b45ca60 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraOperations.java @@ -254,11 +254,11 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper * * @param entity The entity to insert, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem issuing the execution. * @see InsertOptions#empty() */ - Mono insert(Object entity, InsertOptions options) throws DataAccessException; + Mono> insert(T entity, InsertOptions options) throws DataAccessException; /** * Update the given entity and emit the entity if the update was applied. @@ -274,11 +274,11 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper * * @param entity The entity to update, must not be {@literal null}. * @param options must not be {@literal null}. - * @return the {@link WriteResult} for this operation. + * @return the {@link EntityWriteResult} for this operation. * @throws DataAccessException if there is any problem issuing the execution. * @see UpdateOptions#empty() */ - Mono update(Object entity, UpdateOptions options) throws DataAccessException; + Mono> update(T entity, UpdateOptions options) throws DataAccessException; /** * Delete the given entity and emit the entity if the delete was applied. diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java index b82b8674c..ce7956a25 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java @@ -461,14 +461,14 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A */ @Override public Mono insert(T entity) { - return insert(entity, InsertOptions.empty()).map(writeResult -> entity); + return insert(entity, InsertOptions.empty()).map(EntityWriteResult::getEntity); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions) */ @Override - public Mono insert(Object entity, InsertOptions options) { + public Mono> insert(T entity, InsertOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "InsertOptions must not be null"); @@ -476,15 +476,18 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A return doInsert(entity, options, getTableName(entity)); } - Mono doInsert(Object entity, WriteOptions options, CqlIdentifier tableName) { + Mono> doInsert(T entity, WriteOptions options, CqlIdentifier tableName) { CassandraPersistentEntity persistentEntity = getRequiredPersistentEntity(entity.getClass()); Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity); // noinspection ConstantConditions - Mono result = getReactiveCqlOperations().execute(new StatementCallback(insert)) - .doOnSubscribe(it -> maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert))).next(); + Mono> result = getReactiveCqlOperations() // + .execute(new StatementCallback(insert)) // + .doOnSubscribe(it -> maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert))) // + .map(it -> EntityWriteResult.of(it, entity)) // + .next(); return result.doOnNext(it -> maybeEmitEvent(new AfterSaveEvent<>(entity, tableName))); } @@ -501,7 +504,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A * @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions) */ @Override - public Mono update(Object entity, UpdateOptions options) { + public Mono> update(T entity, UpdateOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "UpdateOptions must not be null"); @@ -509,8 +512,11 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A CqlIdentifier tableName = getTableName(entity); Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter()); - Mono result = getReactiveCqlOperations().execute(new StatementCallback(update)) - .doOnSubscribe(it -> maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, update))).next(); + Mono> result = getReactiveCqlOperations() // + .execute(new StatementCallback(update)) // + .doOnSubscribe(it -> maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, update))) // + .map(it -> EntityWriteResult.of(it, entity)) // + .next(); return result.doOnNext(it -> maybeEmitEvent(new AfterSaveEvent<>(entity, tableName))); } @@ -535,8 +541,10 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A CqlIdentifier tableName = getTableName(entity); Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter()); - Mono result = getReactiveCqlOperations().execute(new StatementCallback(delete)) - .doOnSubscribe(it -> maybeEmitEvent(new BeforeDeleteEvent<>(delete, entity.getClass(), tableName))).next(); + Mono result = getReactiveCqlOperations() // + .execute(new StatementCallback(delete)) // + .doOnSubscribe(it -> maybeEmitEvent(new BeforeDeleteEvent<>(delete, entity.getClass(), tableName))) // + .next(); return result.doOnNext(it -> maybeEmitEvent(new AfterDeleteEvent<>(delete, entity.getClass(), tableName))); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperation.java index 67c07257e..fbedd34c6 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperation.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperation.java @@ -118,11 +118,12 @@ public interface ReactiveInsertOperation { * Insert exactly one {@link Object}. * * @param object {@link Object} to insert; must not be {@literal null}. + * @return the {@link EntityWriteResult} for this operation. * @throws IllegalArgumentException if {@link Object} is {@literal null}. - * @see org.springframework.data.cassandra.core.WriteResult + * @see org.springframework.data.cassandra.core.EntityWriteResult * @see reactor.core.publisher.Mono */ - Mono one(T object); + Mono> one(T object); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupport.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupport.java index cbd3bd723..ae5d22829 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupport.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupport.java @@ -19,7 +19,6 @@ import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; - import reactor.core.publisher.Mono; import org.springframework.data.cassandra.core.cql.CqlIdentifier; @@ -86,7 +85,7 @@ class ReactiveInsertOperationSupport implements ReactiveInsertOperation { * @see org.springframework.data.cassandra.core.ReactiveInsertOperation.TerminatingInsert#one(java.lang.Object) */ @Override - public Mono one(T object) { + public Mono> one(T object) { Assert.notNull(object, "Object must not be null"); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/WriteResult.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/WriteResult.java index 5238988c1..9d224a2da 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/WriteResult.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/WriteResult.java @@ -35,7 +35,9 @@ import com.datastax.driver.core.Row; public class WriteResult { private final boolean wasApplied; + private final List executionInfo; + private final List rows; WriteResult(List executionInfo, boolean wasApplied, List rows) { @@ -45,7 +47,7 @@ public class WriteResult { this.rows = rows; } - private WriteResult(ResultSet resultSet) { + WriteResult(ResultSet resultSet) { this.executionInfo = resultSet.getAllExecutionInfo(); this.wasApplied = resultSet.wasApplied(); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java index 2e6706fcf..984b58776 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java @@ -15,6 +15,8 @@ */ package org.springframework.data.cassandra.core.convert; +import lombok.AllArgsConstructor; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,8 +25,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.function.Function; -import lombok.AllArgsConstructor; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; @@ -57,9 +59,6 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.datastax.driver.core.CodecRegistry; import com.datastax.driver.core.DataType; import com.datastax.driver.core.Row; @@ -277,9 +276,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter S instance = instantiator.createInstance(entity, parameterValueProvider); - readProperties(entity, valueProvider, newConvertingPropertyAccessor(instance, entity)); + ConvertingPropertyAccessor propertyAccessor = newConvertingPropertyAccessor(instance, entity); + readProperties(entity, valueProvider, propertyAccessor); - return instance; + return (S) propertyAccessor.getBean(); } private void readProperties(CassandraPersistentEntity entity, CassandraValueProvider valueProvider, @@ -309,10 +309,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } // now recurse on using the key this time - readProperties(keyEntity, valueProvider, newConvertingPropertyAccessor(key, keyEntity)); + ConvertingPropertyAccessor pkPropertyAccessor = newConvertingPropertyAccessor(key, keyEntity); + readProperties(keyEntity, valueProvider, pkPropertyAccessor); // now that the key's properties have been populated, set the key property on the entity - propertyAccessor.setProperty(property, key); + propertyAccessor.setProperty(property, pkPropertyAccessor.getBean()); return; } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java index fda5baff5..d199c51bc 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java @@ -77,9 +77,7 @@ public class SimpleCassandraRepository implements CassandraRepository implements CassandraRepository result = new ArrayList<>(); for (S entity : entities) { - - result.add(entity); - operations.insert(entity, INSERT_NULLS); + result.add(operations.insert(entity, INSERT_NULLS).getEntity()); } return result; @@ -121,9 +117,7 @@ public class SimpleCassandraRepository implements CassandraRepository implements CassandraRepository result = new ArrayList<>(); for (S entity : entities) { - operations.insert(entity); - result.add(entity); + result.add(operations.insert(entity)); } return result; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java index 68e93332b..20b3205e8 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java @@ -15,8 +15,8 @@ */ package org.springframework.data.cassandra.core; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.cassandra.core.query.Criteria.where; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.cassandra.core.query.Criteria.*; import java.util.HashSet; import java.util.LinkedHashSet; @@ -25,7 +25,6 @@ import java.util.concurrent.Future; import org.junit.Before; import org.junit.Test; - import org.springframework.data.cassandra.core.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate; import org.springframework.data.cassandra.core.query.CassandraPageRequest; @@ -120,7 +119,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea User user = new User("heisenberg", "Walter", "White"); - ListenableFuture inserted = template.insert(user, lwtOptions); + ListenableFuture> inserted = template.insert(user, lwtOptions); assertThat(getUninterruptibly(inserted).wasApplied()).isTrue(); } @@ -136,7 +135,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea user.setFirstname("Walter Hartwell"); - ListenableFuture lwt = template.insert(user, lwtOptions); + ListenableFuture> lwt = template.insert(user, lwtOptions); assertThat(getUninterruptibly(lwt).wasApplied()).isFalse(); assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter"); @@ -147,9 +146,10 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea User user = new User("heisenberg", "Walter", "White"); - getUninterruptibly(template.insert(user)); + User result = getUninterruptibly(template.insert(user)); ListenableFuture count = template.count(User.class); + assertThat(result).isSameAs(user); assertThat(getUninterruptibly(count)).isEqualTo(1L); } @@ -196,7 +196,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea User user = new User("heisenberg", "Walter", "White"); - ListenableFuture lwt = template.update(user, lwtOptions); + ListenableFuture> lwt = template.update(user, lwtOptions); assertThat(getUninterruptibly(lwt).wasApplied()).isFalse(); assertThat(getUser(user.getId())).isNull(); @@ -212,9 +212,10 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea user.setFirstname("Walter Hartwell"); - ListenableFuture updated = template.update(user, lwtOptions); + ListenableFuture> updated = template.update(user, lwtOptions); assertThat(getUninterruptibly(updated).wasApplied()).isTrue(); + assertThat(getUninterruptibly(updated).getEntity()).isSameAs(user); assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter Hartwell"); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java index 4a16bbf55..6576900fd 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java @@ -146,31 +146,33 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI assertThat(loaded).isEqualTo(token1); } - @Test // DATACASS-292 + @Test // DATACASS-292, DATACASS-573 public void insertShouldInsertEntity() { User user = new User("heisenberg", "Walter", "White"); assertThat(template.selectOneById(user.getId(), User.class)).isNull(); - template.insert(user); + User result = template.insert(user); + assertThat(result).isSameAs(user); assertThat(template.selectOneById(user.getId(), User.class)).isEqualTo(user); } - @Test // DATACASS-250 + @Test // DATACASS-250, DATACASS-573 public void insertShouldCreateEntityWithLwt() { InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build(); User user = new User("heisenberg", "Walter", "White"); - template.insert(user, lwtOptions); + EntityWriteResult result = template.insert(user, lwtOptions); + assertThat(result.getEntity()).isSameAs(user); assertThat(template.selectOneById(user.getId(), User.class)).isNotNull(); } - @Test // DATACASS-250 + @Test // DATACASS-250, DATACASS-573 public void insertShouldNotUpdateEntityWithLwt() { InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build(); @@ -181,7 +183,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI user.setFirstname("Walter Hartwell"); - WriteResult lwt = template.insert(user, lwtOptions); + EntityWriteResult lwt = template.insert(user, lwtOptions); assertThat(lwt.wasApplied()).isFalse(); assertThat(template.selectOneById(user.getId(), User.class).getFirstname()).isEqualTo("Walter"); @@ -239,7 +241,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI assertThat(template.exists(Query.query(where("id").is("foo")), User.class)).isFalse(); } - @Test // DATACASS-292 + @Test // DATACASS-292, DATACASS-573 public void updateShouldUpdateEntity() { User user = new User("heisenberg", "Walter", "White"); @@ -247,8 +249,9 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI user.setFirstname("Walter Hartwell"); - template.update(user); + User result = template.update(user); + assertThat(result).isSameAs(user); assertThat(template.selectOneById(user.getId(), User.class)).isEqualTo(user); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplateIntegrationTests.java index 6d8355b24..fe4817344 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplateIntegrationTests.java @@ -92,16 +92,20 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC verifyUser(user.getId()).expectNext(user).verifyComplete(); } - @Test // DATACASS-250 + @Test // DATACASS-250, DATACASS-573 public void insertShouldCreateEntityWithLwt() { InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build(); User user = new User("heisenberg", "Walter", "White"); - Mono inserted = template.insert(user, lwtOptions); + Mono> inserted = template.insert(user, lwtOptions); - StepVerifier.create(inserted.map(WriteResult::wasApplied)).expectNext(true).verifyComplete(); + StepVerifier.create(inserted).consumeNextWith(actual -> { + + assertThat(actual.wasApplied()).isTrue(); + assertThat(actual.getEntity()).isSameAs(user); + }).verifyComplete(); } @Test // DATACASS-250 diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupportIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupportIntegrationTests.java index df4b41d90..45eed4227 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupportIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/ReactiveInsertOperationSupportIntegrationTests.java @@ -15,18 +15,16 @@ */ package org.springframework.data.cassandra.core; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Collections; +import static org.assertj.core.api.Assertions.*; import lombok.Data; - import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.util.Collections; + import org.junit.Before; import org.junit.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.cql.CqlIdentifier; @@ -90,21 +88,26 @@ public class ReactiveInsertOperationSupportIntegrationTests extends AbstractKeys this.template.insert(Person.class).inTable((String) null); } - @Test // DATACASS-485 + @Test // DATACASS-485, DATACASS-573 public void insertOne() { - Mono writeResult = this.template.insert(Person.class).inTable("person").one(han); + Mono> writeResult = this.template.insert(Person.class).inTable("person").one(han); + + StepVerifier.create(writeResult).consumeNextWith(actual -> { + + assertThat(actual.wasApplied()).isTrue(); + assertThat(actual.getEntity()).isSameAs(han); + }).verifyComplete(); - StepVerifier.create(writeResult.map(WriteResult::wasApplied)).expectNext(true).verifyComplete(); StepVerifier.create(template.selectOneById(han.id, Person.class)).expectNext(han).verifyComplete(); } - @Test // DATACASS-485 + @Test // DATACASS-485, DATACASS-573 public void insertOneWithOptions() { this.template.insert(Person.class).inTable("person").one(han); - Mono writeResult = this.template + Mono> writeResult = this.template .insert(Person.class).inTable("person") .withOptions(InsertOptions.builder().withIfNotExists().build()) .one(han); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryIntegrationTests.java index d6ba02b97..36241673c 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryIntegrationTests.java @@ -211,7 +211,7 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC assertThat(repository.count()).isEqualTo(3); } - @Test // DATACASS-396 + @Test // DATACASS-396, DATACASS-573 public void saveEntityShouldUpdateExistingEntity() { dave.setFirstname("Hello, Dave"); @@ -219,7 +219,7 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC User saved = repository.save(dave); - assertThat(saved).isEqualTo(saved); + assertThat(saved).isSameAs(saved); Optional loaded = repository.findById(dave.getId()); @@ -257,14 +257,14 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC assertThat(loaded).contains(User); } - @Test // DATACASS-396, DATACASS-416 + @Test // DATACASS-396, DATACASS-416, DATACASS-573 public void saveIterableOfNewEntitiesShouldInsertEntity() { repository.deleteAll(); List saved = repository.saveAll(Arrays.asList(dave, oliver, boyd)); - assertThat(saved).hasSize(3); + assertThat(saved).hasSize(3).contains(dave, oliver, boyd); assertThat(repository.count()).isEqualTo(3); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryUnitTests.java index d47cf856e..3db711c84 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepositoryUnitTests.java @@ -30,6 +30,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.core.EntityWriteResult; import org.springframework.data.cassandra.core.InsertOptions; import org.springframework.data.cassandra.core.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.cql.CqlOperations; @@ -63,6 +64,7 @@ public class SimpleCassandraRepositoryUnitTests { @Mock CqlOperations cqlOperations; @Mock UserTypeResolver userTypeResolver; @Mock UserType userType; + @Mock EntityWriteResult writeResult; @Captor ArgumentCaptor insertCaptor; @@ -71,7 +73,7 @@ public class SimpleCassandraRepositoryUnitTests { mappingContext.setUserTypeResolver(userTypeResolver); } - @Test // DATACASS-428, DATACASS-560 + @Test // DATACASS-428, DATACASS-560, DATACASS-573 public void saveShouldInsertNewPrimaryKeyOnlyEntity() { CassandraPersistentEntity entity = converter.getMappingContext().getRequiredPersistentEntity(SimplePerson.class); @@ -81,12 +83,15 @@ public class SimpleCassandraRepositoryUnitTests { SimplePerson person = new SimplePerson(); + when(cassandraOperations.insert(eq(person), any())).thenReturn(writeResult); + when(writeResult.getEntity()).thenReturn(person); + repository.save(person); verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build()); } - @Test // DATACASS-428, DATACASS-560 + @Test // DATACASS-428, DATACASS-560, DATACASS-573 public void saveShouldUpdateNewEntity() { CassandraPersistentEntity entity = converter.getMappingContext().getRequiredPersistentEntity(Person.class); @@ -96,12 +101,15 @@ public class SimpleCassandraRepositoryUnitTests { Person person = new Person(); + when(cassandraOperations.insert(eq(person), any())).thenReturn(writeResult); + when(writeResult.getEntity()).thenReturn(person); + repository.save(person); verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build()); } - @Test // DATACASS-428, DATACASS-560 + @Test // DATACASS-428, DATACASS-560, DATACASS-573 public void saveShouldUpdateExistingEntity() { CassandraPersistentEntity entity = converter.getMappingContext().getRequiredPersistentEntity(Person.class); @@ -113,6 +121,9 @@ public class SimpleCassandraRepositoryUnitTests { person.setFirstname("foo"); person.setLastname("bar"); + when(cassandraOperations.insert(eq(person), any())).thenReturn(writeResult); + when(writeResult.getEntity()).thenReturn(person); + repository.save(person); verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build());