DATACASS-560 - Allow INSERT with null values through InsertOptions.insertNulls.
We now allow the configuration whether null values of entity objects should be inserted as tombstones using INSERT statements. By default, this option is disable. Enabling tombstones allows upsert usage through INSERT statements as null values get inserted in Cassandra. This option is useful if an object should be persisted in its whole state. Inserting nulls causes tombstones to be created and can have a negative impact on performance. This change allows SimpleRepository and SimpleCassandraRepository to use CassandraTemplate directly allowing emission of lifecycle events.
This commit is contained in:
@@ -502,8 +502,9 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
Assert.notNull(options, "InsertOptions must not be null");
|
||||
|
||||
CqlIdentifier tableName = getTableName(entity);
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter());
|
||||
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
CqlIdentifier tableName = persistentEntity.getTableName();
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert));
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.springframework.data.cassandra.core.cql.QueryOptions;
|
||||
import org.springframework.data.cassandra.core.cql.WriteOptions;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.datastax.driver.core.querybuilder.Batch;
|
||||
@@ -109,10 +111,16 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
|
||||
Assert.notNull(entities, "Entities must not be null");
|
||||
Assert.notNull(options, "WriteOptions must not be null");
|
||||
|
||||
CassandraMappingContext mappingContext = operations.getConverter().getMappingContext();
|
||||
|
||||
for (Object entity : entities) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
batch.add(QueryUtils.createInsertQuery(getTableName(entity), entity, options, operations.getConverter()));
|
||||
|
||||
BasicCassandraPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getRequiredPersistentEntity(entity.getClass());
|
||||
batch.add(QueryUtils.createInsertQuery(persistentEntity.getTableName().toCql(), entity, options,
|
||||
operations.getConverter(), persistentEntity));
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
@@ -549,7 +549,9 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
|
||||
|
||||
WriteResult doInsert(Object entity, WriteOptions options, CqlIdentifier tableName) {
|
||||
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter());
|
||||
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert));
|
||||
|
||||
|
||||
@@ -41,13 +41,16 @@ public class InsertOptions extends WriteOptions {
|
||||
|
||||
private boolean ifNotExists;
|
||||
|
||||
private boolean insertNulls;
|
||||
|
||||
private InsertOptions(@Nullable ConsistencyLevel consistencyLevel, @Nullable RetryPolicy retryPolicy,
|
||||
@Nullable Boolean tracing, @Nullable Integer fetchSize, Duration readTimeout, Duration ttl,
|
||||
@Nullable Long timestamp, boolean ifNotExists) {
|
||||
@Nullable Long timestamp, boolean ifNotExists, boolean insertNulls) {
|
||||
|
||||
super(consistencyLevel, retryPolicy, tracing, fetchSize, readTimeout, ttl, timestamp);
|
||||
|
||||
this.ifNotExists = ifNotExists;
|
||||
this.insertNulls = insertNulls;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +89,14 @@ public class InsertOptions extends WriteOptions {
|
||||
return this.ifNotExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} to insert {@literal null} values from an entity.
|
||||
* @since 2.1
|
||||
*/
|
||||
public boolean isInsertNulls() {
|
||||
return this.insertNulls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link InsertOptions}.
|
||||
*
|
||||
@@ -97,6 +108,8 @@ public class InsertOptions extends WriteOptions {
|
||||
|
||||
private boolean ifNotExists;
|
||||
|
||||
private boolean insertNulls;
|
||||
|
||||
private InsertOptionsBuilder() {}
|
||||
|
||||
private InsertOptionsBuilder(InsertOptions insertOptions) {
|
||||
@@ -104,6 +117,7 @@ public class InsertOptions extends WriteOptions {
|
||||
super(insertOptions);
|
||||
|
||||
this.ifNotExists = insertOptions.ifNotExists;
|
||||
this.insertNulls = insertOptions.insertNulls;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -247,6 +261,34 @@ public class InsertOptions extends WriteOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert {@literal null} values from an entity. This allows the usage of {@code INSERT} statements as upsert by
|
||||
* ensuring * that the whole entity state is persisted. Inserting {@literal null}s in Cassandra creates tombstones
|
||||
* so this * option should be used with caution.
|
||||
*
|
||||
* @return {@code this} {@link InsertOptionsBuilder}
|
||||
* @since 2.1
|
||||
*/
|
||||
public InsertOptionsBuilder withInsertNulls() {
|
||||
return withInsertNulls(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert {@literal null} values from an entity. This allows the usage of {@code INSERT} statements as upsert by
|
||||
* ensuring that the whole entity state is persisted. Inserting {@literal null}s in Cassandra creates tombstones so
|
||||
* this option should be used with caution.
|
||||
*
|
||||
* @param insertNulls {@literal true} to enable insertion of {@literal null} values.
|
||||
* @return {@code this} {@link InsertOptionsBuilder}
|
||||
* @since 2.1
|
||||
*/
|
||||
public InsertOptionsBuilder withInsertNulls(boolean insertNulls) {
|
||||
|
||||
this.insertNulls = insertNulls;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link InsertOptions} with the configured values.
|
||||
*
|
||||
@@ -254,7 +296,7 @@ public class InsertOptions extends WriteOptions {
|
||||
*/
|
||||
public InsertOptions build() {
|
||||
return new InsertOptions(this.consistencyLevel, this.retryPolicy, this.tracing, this.fetchSize, this.readTimeout,
|
||||
this.ttl, this.timestamp, this.ifNotExists);
|
||||
this.ttl, this.timestamp, this.ifNotExists, this.insertNulls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,21 @@
|
||||
package org.springframework.data.cassandra.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraConverter;
|
||||
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
|
||||
import org.springframework.data.cassandra.core.cql.QueryOptions;
|
||||
import org.springframework.data.cassandra.core.cql.QueryOptionsUtil;
|
||||
import org.springframework.data.cassandra.core.cql.RowMapper;
|
||||
import org.springframework.data.cassandra.core.cql.WriteOptions;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -64,17 +69,20 @@ class QueryUtils {
|
||||
* @param objectToUpdate the object to save, must not be {@literal null}.
|
||||
* @param options optional {@link WriteOptions} to apply to the {@link Insert} statement, may be {@literal null}.
|
||||
* @param entityWriter the {@link EntityWriter} to write insert values.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return The Query object to run with session.execute();
|
||||
*/
|
||||
static Insert createInsertQuery(String tableName, Object objectToUpdate, WriteOptions options,
|
||||
EntityWriter<Object, Object> entityWriter) {
|
||||
CassandraConverter entityWriter, CassandraPersistentEntity<?> entity) {
|
||||
|
||||
Assert.hasText(tableName, "TableName must not be empty");
|
||||
Assert.notNull(objectToUpdate, "Object to insert must not be null");
|
||||
Assert.notNull(entityWriter, "EntityWriter must not be null");
|
||||
Assert.notNull(entityWriter, "CassandraConverter must not be null");
|
||||
Assert.notNull(entity, "CassandraPersistentEntity must not be null");
|
||||
|
||||
Insert insert = QueryOptionsUtil.addWriteOptions(QueryBuilder.insertInto(tableName), options);
|
||||
|
||||
boolean insertNulls = false;
|
||||
if (options instanceof InsertOptions) {
|
||||
|
||||
InsertOptions insertOptions = (InsertOptions) options;
|
||||
@@ -82,9 +90,22 @@ class QueryUtils {
|
||||
if (insertOptions.isIfNotExists()) {
|
||||
insert = insert.ifNotExists();
|
||||
}
|
||||
|
||||
insertNulls = insertOptions.isInsertNulls();
|
||||
}
|
||||
|
||||
entityWriter.write(objectToUpdate, insert);
|
||||
if (insertNulls) {
|
||||
|
||||
Map<String, Object> toInsert = new LinkedHashMap<>();
|
||||
|
||||
entityWriter.write(objectToUpdate, toInsert, entity);
|
||||
|
||||
for (Entry<String, Object> entry : toInsert.entrySet()) {
|
||||
insert.value(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
entityWriter.write(objectToUpdate, insert);
|
||||
}
|
||||
|
||||
return insert;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@@ -433,7 +432,9 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
|
||||
|
||||
Mono<WriteResult> doInsert(Object entity, WriteOptions options, CqlIdentifier tableName) {
|
||||
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter());
|
||||
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
|
||||
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
|
||||
|
||||
// noinspection ConstantConditions
|
||||
Mono<WriteResult> result = getReactiveCqlOperations().execute(new StatementCallback(insert))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import static org.springframework.data.cassandra.core.query.Criteria.where;
|
||||
import static org.springframework.data.cassandra.core.query.Criteria.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,6 +23,7 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.CassandraTemplate;
|
||||
import org.springframework.data.cassandra.core.InsertOptions;
|
||||
import org.springframework.data.cassandra.core.query.Query;
|
||||
import org.springframework.data.cassandra.repository.CassandraRepository;
|
||||
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
|
||||
@@ -46,13 +47,15 @@ import com.datastax.driver.core.querybuilder.Select;
|
||||
*/
|
||||
public class SimpleCassandraRepository<T, ID> implements CassandraRepository<T, ID> {
|
||||
|
||||
private static final InsertOptions INSERT_NULLS = InsertOptions.builder().withInsertNulls().build();
|
||||
|
||||
private final CassandraEntityInformation<T, ID> entityInformation;
|
||||
|
||||
private final CassandraOperations operations;
|
||||
|
||||
/**
|
||||
* Create a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation}
|
||||
* and {@link CassandraTemplate}.
|
||||
* Create a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and
|
||||
* {@link CassandraTemplate}.
|
||||
*
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
@@ -74,9 +77,7 @@ public class SimpleCassandraRepository<T, ID> implements CassandraRepository<T,
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
Insert insert = createInsert(entity);
|
||||
|
||||
operations.getCqlOperations().execute(insert);
|
||||
operations.insert(entity, INSERT_NULLS);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ public class SimpleCassandraRepository<T, ID> implements CassandraRepository<T,
|
||||
for (S entity : entities) {
|
||||
|
||||
result.add(entity);
|
||||
operations.getCqlOperations().execute(createInsert(entity));
|
||||
operations.insert(entity, INSERT_NULLS);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -105,6 +106,8 @@ public class SimpleCassandraRepository<T, ID> implements CassandraRepository<T,
|
||||
*
|
||||
* @param entity the entity, must not be {@literal null}.
|
||||
* @return the constructed {@link Insert} statement.
|
||||
* @deprecated since 2.1, use {@link InsertOptions#isInsertNulls()} with
|
||||
* {@link CassandraOperations#insert(Object, InsertOptions)}.
|
||||
*/
|
||||
protected <S extends T> Insert createInsert(S entity) {
|
||||
return InsertUtil.createInsert(operations.getConverter(), entity);
|
||||
|
||||
@@ -18,13 +18,13 @@ package org.springframework.data.cassandra.repository.support;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.cassandra.core.InsertOptions;
|
||||
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
|
||||
import org.springframework.data.cassandra.repository.ReactiveCassandraRepository;
|
||||
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import com.datastax.driver.core.querybuilder.Insert;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.datastax.driver.core.querybuilder.Select;
|
||||
@@ -38,6 +38,8 @@ import com.datastax.driver.core.querybuilder.Select;
|
||||
*/
|
||||
public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassandraRepository<T, ID> {
|
||||
|
||||
private static final InsertOptions INSERT_NULLS = InsertOptions.builder().withInsertNulls().build();
|
||||
|
||||
private final CassandraEntityInformation<T, ID> entityInformation;
|
||||
|
||||
private final ReactiveCassandraOperations operations;
|
||||
@@ -67,7 +69,7 @@ public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassand
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
return operations.getReactiveCqlOperations().execute(createInsert(entity)).map(it -> entity);
|
||||
return operations.insert(entity, INSERT_NULLS).thenReturn(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,6 +77,8 @@ public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassand
|
||||
*
|
||||
* @param entity the entity, must not be {@literal null}.
|
||||
* @return the constructed {@link Insert} statement.
|
||||
* @deprecated since 2.1, use {@link InsertOptions#isInsertNulls()} with
|
||||
* {@link ReactiveCassandraOperations#insert(Object, InsertOptions)}.
|
||||
*/
|
||||
private <S extends T> Insert createInsert(S entity) {
|
||||
return InsertUtil.createInsert(operations.getConverter(), entity);
|
||||
@@ -100,7 +104,7 @@ public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassand
|
||||
Assert.notNull(entityStream, "The given Publisher of entities must not be null");
|
||||
|
||||
return Flux.from(entityStream)
|
||||
.flatMap(entity -> operations.getReactiveCqlOperations().execute(createInsert(entity)).map(it -> entity));
|
||||
.flatMap(entity -> operations.insert(entity, INSERT_NULLS).thenReturn(entity));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -15,14 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -36,7 +33,6 @@ import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.cassandra.CassandraConnectionFailureException;
|
||||
import org.springframework.data.cassandra.core.query.Query;
|
||||
import org.springframework.data.cassandra.domain.User;
|
||||
@@ -250,8 +246,24 @@ public class CassandraTemplateUnitTests {
|
||||
.isEqualTo("INSERT INTO users (firstname,id,lastname) VALUES ('Walter','heisenberg','White') IF NOT EXISTS;");
|
||||
}
|
||||
|
||||
@Test // DATACASS-560
|
||||
public void insertShouldInsertWithNulls() {
|
||||
|
||||
InsertOptions insertOptions = InsertOptions.builder().withInsertNulls().build();
|
||||
|
||||
when(resultSet.wasApplied()).thenReturn(true);
|
||||
|
||||
User user = new User("heisenberg", null, null);
|
||||
|
||||
template.insert(user, insertOptions);
|
||||
|
||||
verify(session).execute(statementCaptor.capture());
|
||||
assertThat(statementCaptor.getValue().toString())
|
||||
.isEqualTo("INSERT INTO users (firstname,id,lastname) VALUES (null,'heisenberg',null);");
|
||||
}
|
||||
|
||||
@Test // DATACASS-292
|
||||
public void insertShouldTranslateException() throws Exception {
|
||||
public void insertShouldTranslateException() {
|
||||
|
||||
reset(session);
|
||||
when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
|
||||
@@ -351,7 +363,7 @@ public class CassandraTemplateUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATACASS-292
|
||||
public void deleteShouldTranslateException() throws Exception {
|
||||
public void deleteShouldTranslateException() {
|
||||
|
||||
reset(session);
|
||||
when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
|
||||
|
||||
@@ -15,23 +15,29 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener;
|
||||
import org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.cassandra.core.mapping.event.CassandraMappingEvent;
|
||||
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
|
||||
import org.springframework.data.cassandra.domain.User;
|
||||
import org.springframework.data.cassandra.repository.CassandraRepository;
|
||||
@@ -59,9 +65,15 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC
|
||||
public String[] getEntityBasePackages() {
|
||||
return new String[] { User.class.getPackage().getName() };
|
||||
}
|
||||
|
||||
@Bean
|
||||
CaptureEventListener eventListener() {
|
||||
return new CaptureEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired private CassandraOperations operations;
|
||||
@Autowired private CaptureEventListener eventListener;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
private CassandraRepositoryFactory factory;
|
||||
@@ -99,6 +111,8 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC
|
||||
boyd = new User("45", "Boyd", "Tinsley");
|
||||
|
||||
repository.saveAll(Arrays.asList(oliver, dave, carter, boyd));
|
||||
|
||||
eventListener.clear();
|
||||
}
|
||||
|
||||
@Test // DATACASS-396
|
||||
@@ -217,6 +231,18 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATACASS-560
|
||||
public void saveShouldEmitEvents() {
|
||||
|
||||
dave.setFirstname("Hello, Dave");
|
||||
dave.setLastname("Bowman");
|
||||
|
||||
repository.save(dave);
|
||||
|
||||
assertThat(eventListener.getBeforeSave()).hasSize(1);
|
||||
assertThat(eventListener.getAfterSave()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test // DATACASS-396
|
||||
public void saveEntityShouldInsertNewEntity() {
|
||||
|
||||
@@ -304,4 +330,35 @@ public class SimpleCassandraRepositoryIntegrationTests extends AbstractKeyspaceC
|
||||
|
||||
interface UserRepostitory extends CassandraRepository<User, String> { }
|
||||
|
||||
static class CaptureEventListener extends AbstractCassandraEventListener<User> {
|
||||
|
||||
private final List<CassandraMappingEvent<?>> events = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onBeforeSave(BeforeSaveEvent<User> event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterSave(AfterSaveEvent<User> event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
List<BeforeSaveEvent<User>> getBeforeSave() {
|
||||
return filter(BeforeSaveEvent.class);
|
||||
}
|
||||
|
||||
List<AfterSaveEvent<User>> getAfterSave() {
|
||||
return filter(AfterSaveEvent.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> filter(Class<? super T> targetType) {
|
||||
return (List) events.stream().filter(targetType::isInstance).map(targetType::cast).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.Serializable;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -31,11 +28,10 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
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.InsertOptions;
|
||||
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
|
||||
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
|
||||
import org.springframework.data.cassandra.core.cql.CqlOperations;
|
||||
import org.springframework.data.cassandra.core.cql.QueryOptions;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
@@ -72,15 +68,10 @@ public class SimpleCassandraRepositoryUnitTests {
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
mappingContext.setUserTypeResolver(userTypeResolver);
|
||||
|
||||
when(cassandraOperations.getConverter()).thenReturn(converter);
|
||||
when(cassandraOperations.getCqlOperations()).thenReturn(cqlOperations);
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.of("address"))).thenReturn(userType);
|
||||
}
|
||||
|
||||
@Test // DATACASS-428
|
||||
@Test // DATACASS-428, DATACASS-560
|
||||
public void saveShouldInsertNewPrimaryKeyOnlyEntity() {
|
||||
|
||||
CassandraPersistentEntity<?> entity = converter.getMappingContext().getRequiredPersistentEntity(SimplePerson.class);
|
||||
@@ -92,11 +83,10 @@ public class SimpleCassandraRepositoryUnitTests {
|
||||
|
||||
repository.save(person);
|
||||
|
||||
verify(cqlOperations).execute(insertCaptor.capture());
|
||||
assertThat(insertCaptor.getValue().toString()).isEqualTo("INSERT INTO simpleperson (id) VALUES (null);");
|
||||
verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build());
|
||||
}
|
||||
|
||||
@Test // DATACASS-428
|
||||
@Test // DATACASS-428, DATACASS-560
|
||||
public void saveShouldUpdateNewEntity() {
|
||||
|
||||
CassandraPersistentEntity<?> entity = converter.getMappingContext().getRequiredPersistentEntity(Person.class);
|
||||
@@ -108,10 +98,10 @@ public class SimpleCassandraRepositoryUnitTests {
|
||||
|
||||
repository.save(person);
|
||||
|
||||
verify(cqlOperations).execute(any(Insert.class));
|
||||
verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build());
|
||||
}
|
||||
|
||||
@Test // DATACASS-428
|
||||
@Test // DATACASS-428, DATACASS-560
|
||||
public void saveShouldUpdateExistingEntity() {
|
||||
|
||||
CassandraPersistentEntity<?> entity = converter.getMappingContext().getRequiredPersistentEntity(Person.class);
|
||||
@@ -125,10 +115,7 @@ public class SimpleCassandraRepositoryUnitTests {
|
||||
|
||||
repository.save(person);
|
||||
|
||||
verify(cqlOperations).execute(insertCaptor.capture());
|
||||
assertThat(insertCaptor.getValue().toString())
|
||||
.contains("INSERT INTO person (lastname,firstname,alternativeaddresses,");
|
||||
assertThat(insertCaptor.getValue().toString()).contains("VALUES ('bar','foo',null");
|
||||
verify(cassandraOperations).insert(person, InsertOptions.builder().withInsertNulls().build());
|
||||
}
|
||||
|
||||
@Test // DATACASS-428
|
||||
|
||||
Reference in New Issue
Block a user