diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java
index 804fd7623..9c6eb3009 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java
@@ -358,6 +358,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return;
}
+ if (value == null) {
+ return;
+ }
+
if (log.isDebugEnabled()) {
log.debug("Adding insert.value [{}] - [{}]", property.getColumnName().toCql(), value);
}
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 9d4b99a80..0112dbc1c 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
@@ -42,7 +42,7 @@ import com.datastax.driver.core.querybuilder.Select;
*
* {@link CassandraOperations} mixes synchronous and asynchronous methods so asynchronous methods are subject to be
* moved into an asynchronous Cassandra template.
- *
+ *
* @author Alex Shvid
* @author David Webb
* @author Matthew Adams
@@ -197,7 +197,7 @@ public interface CassandraOperations extends CqlOperations {
long count(Class> entityClass);
/**
- * Insert the given entity.
+ * Insert the given entity without inserting {@literal null} values.
*
* @param entity The entity to insert
* @return The entity given
@@ -205,7 +205,7 @@ public interface CassandraOperations extends CqlOperations {
T insert(T entity);
/**
- * Insert the given entity.
+ * Insert the given entity without inserting {@literal null} values.
*
* @param entity The entity to insert
* @param options The {@link WriteOptions} to use.
@@ -525,7 +525,7 @@ public interface CassandraOperations extends CqlOperations {
/**
* Deletes all entities of a given class.
- *
+ *
* @param entityClass The entity type must not be {@literal null}.
*/
void deleteAll(Class entityClass);
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java
index 9cc0f3069..0b8e0a66d 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java
@@ -1,12 +1,12 @@
/*
- * Copyright 2013-2014 the original author or authors
- *
+ * Copyright 2013-2017 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.
@@ -21,10 +21,20 @@ import org.springframework.data.repository.core.EntityInformation;
/**
* Cassandra specific {@link EntityInformation}.
- *
+ *
* @author Alex Shvid
+ * @author Mark Paluch
*/
-public interface CassandraEntityInformation extends EntityInformation,
- CassandraEntityMetadata {
+public interface CassandraEntityInformation
+ extends EntityInformation, CassandraEntityMetadata {
+ /**
+ * Return {@literal true} if the persistent entity consists entirely of primary key properties (a single Id property,
+ * composite primary key).
+ *
+ * @return {@literal true} if the persistent entity consists entirely of primary key properties (a single Id property,
+ * composite primary key).
+ * @since 1.5.2
+ */
+ boolean isPrimaryKeyEntity();
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java
index 066903dcb..c78f17deb 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java
@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.repository.support;
import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicReference;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.CassandraConverter;
@@ -23,6 +24,7 @@ import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.repository.MapId;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
+import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.repository.core.support.AbstractEntityInformation;
import org.springframework.util.Assert;
@@ -33,12 +35,14 @@ import org.springframework.util.Assert;
*
* @author Alex Shvid
* @author Matthew T. Adams
+ * @author Mark Paluch
*/
public class MappingCassandraEntityInformation extends AbstractEntityInformation
implements CassandraEntityInformation {
private final CassandraPersistentEntity entityMetadata;
- private CassandraConverter converter;
+ private final CassandraConverter converter;
+ private final boolean isPrimaryKeyEntity;
/**
* Creates a new {@link MappingCassandraEntityInformation} for the given {@link CassandraPersistentEntity}.
@@ -51,6 +55,7 @@ public class MappingCassandraEntityInformation exten
this.entityMetadata = entity;
this.converter = converter;
+ this.isPrimaryKeyEntity = hasNonIdProperties(entity);
}
@SuppressWarnings("unchecked")
@@ -79,4 +84,28 @@ public class MappingCassandraEntityInformation exten
public CqlIdentifier getTableName() {
return entityMetadata.getTableName();
}
+
+ @Override
+ public boolean isPrimaryKeyEntity() {
+ return isPrimaryKeyEntity;
+ }
+
+ private static boolean hasNonIdProperties(CassandraPersistentEntity> entity) {
+
+ final AtomicReference hasPrimaryKeyOnlyProperties = new AtomicReference(true);
+
+ entity.doWithProperties(new PropertyHandler() {
+ @Override
+ public void doWithPersistentProperty(CassandraPersistentProperty property) {
+
+ if (property.isCompositePrimaryKey() || property.isPrimaryKeyColumn() || property.isIdProperty()) {
+ return;
+ }
+
+ hasPrimaryKeyOnlyProperties.set(false);
+ }
+ });
+
+ return hasPrimaryKeyOnlyProperties.get();
+ }
}
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 8030cc9c3..3c2e3b1b5 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
@@ -39,6 +39,8 @@ public class SimpleCassandraRepository implements Ty
protected CassandraOperations operations;
protected CassandraEntityInformation entityInformation;
+ private final boolean isPrimaryKeyEntity;
+
/**
* Creates a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and
* {@link CassandraTemplate}.
@@ -53,6 +55,7 @@ public class SimpleCassandraRepository implements Ty
this.entityInformation = metadata;
this.operations = operations;
+ this.isPrimaryKeyEntity = metadata.isPrimaryKeyEntity();
}
/* (non-Javadoc)
@@ -63,7 +66,7 @@ public class SimpleCassandraRepository implements Ty
Assert.notNull(entity, "Entity must not be null");
- if (entityInformation.isNew(entity)) {
+ if (entityInformation.isNew(entity) || isPrimaryKeyEntity) {
return operations.insert(entity);
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUDTIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUDTIntegrationTests.java
index ca353f554..a66cb04ce 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUDTIntegrationTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUDTIntegrationTests.java
@@ -156,8 +156,8 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
- assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
- + "VALUES (null,{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1',null);");
+ assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (currentaddress,id) "
+ + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1');");
}
@Test // DATACASS-172
@@ -193,8 +193,8 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
- assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
- + "VALUES (null,null,'1',[{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']}]);");
+ assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (id,previousaddresses) "
+ + "VALUES ('1',[{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']}]);");
}
@Test // DATACASS-172
@@ -228,8 +228,8 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
Insert insert = QueryBuilder.insertInto("addressbook");
converter.write(addressBook, insert);
- assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,currentaddress,id,previousaddresses) "
- + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},null,'1',null);");
+ assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,id) "
+ + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1');");
}
@Test // DATACASS-172
@@ -302,7 +302,7 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
converter.write(bank, insert);
assertThat(insert.toString())
- .isEqualTo("INSERT INTO bank (currency,id,othercurrencies) VALUES ({currency:'EUR'},null,null);");
+ .isEqualTo("INSERT INTO bank (currency) VALUES ({currency:'EUR'});");
}
@Test // DATACASS-172
@@ -361,7 +361,7 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
converter.write(bank, insert);
assertThat(insert.toString())
- .isEqualTo("INSERT INTO bank (currency,id,othercurrencies) VALUES (null,null,[{currency:'EUR'}]);");
+ .isEqualTo("INSERT INTO bank (othercurrencies) VALUES ([{currency:'EUR'}]);");
}
@Test // DATACASS-172
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformationUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformationUnitTests.java
new file mode 100644
index 000000000..1cda1138e
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformationUnitTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017 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.repository.support;
+
+import static org.assertj.core.api.Assertions.*;
+
+import lombok.Data;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.convert.MappingCassandraConverter;
+import org.springframework.data.cassandra.domain.AllPossibleTypes;
+import org.springframework.data.cassandra.domain.CompositeKey;
+import org.springframework.data.cassandra.domain.TypeWithKeyClass;
+import org.springframework.data.cassandra.domain.TypeWithMapId;
+import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
+import org.springframework.data.cassandra.mapping.UserTypeResolver;
+
+/**
+ * Unit tests for {@link MappingCassandraEntityInformation}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings("unchecked")
+public class MappingCassandraEntityInformationUnitTests {
+
+ BasicCassandraMappingContext context = new BasicCassandraMappingContext();
+
+ CassandraConverter converter = new MappingCassandraConverter(context);
+
+ @Mock UserTypeResolver userTypeResolver;
+
+ @Before
+ public void before() {
+ context.setUserTypeResolver(userTypeResolver);
+ }
+
+ @Test // DATACASS-420
+ public void shouldConsiderSimpleIdEntityAsPrimaryKeyOnly() {
+
+ MappingCassandraEntityInformation information = new MappingCassandraEntityInformation(
+ context.getPersistentEntity(PrimaryKeyOnly.class), converter);
+
+ assertThat(information.isPrimaryKeyEntity()).isTrue();
+ }
+
+ @Test // DATACASS-420
+ public void shouldConsiderCompositeIdEntityAsPrimaryKeyOnly() {
+
+ MappingCassandraEntityInformation information = new MappingCassandraEntityInformation(
+ context.getPersistentEntity(CompositeKey.class), converter);
+
+ assertThat(information.isPrimaryKeyEntity()).isTrue();
+ }
+
+ @Test // DATACASS-420
+ public void shouldConsiderCompositeKeyClassEntityAsPrimaryKeyOnly() {
+
+ MappingCassandraEntityInformation information = new MappingCassandraEntityInformation(
+ context.getPersistentEntity(TypeWithKeyClass.class), converter);
+
+ assertThat(information.isPrimaryKeyEntity()).isTrue();
+ }
+
+ @Test // DATACASS-420
+ public void shouldConsiderMapIdClassEntityAsPrimaryKeyOnly() {
+
+ MappingCassandraEntityInformation information = new MappingCassandraEntityInformation(
+ context.getPersistentEntity(TypeWithMapId.class), converter);
+
+ assertThat(information.isPrimaryKeyEntity()).isTrue();
+ }
+
+ @Test // DATACASS-420
+ public void shouldComplexEntityNotAsPrimaryKeyOnly() {
+
+ MappingCassandraEntityInformation information = new MappingCassandraEntityInformation(
+ context.getPersistentEntity(AllPossibleTypes.class), converter);
+
+ assertThat(information.isPrimaryKeyEntity()).isFalse();
+ }
+
+ @Data
+ static class PrimaryKeyOnly {
+ @Id String id;
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
index 38e0ae81e..36cdf9c2f 100755
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
@@ -627,27 +627,8 @@ public class CassandraOperationsIntegrationTests extends AbstractKeyspaceCreatin
assertThat(template.count(Book.class)).isEqualTo(count);
}
- @Test // DATACASS-182
- public void updateShouldRemoveFields() {
-
- Book book = new Book();
- book.setIsbn("isbn");
- book.setTitle("title");
- book.setAuthor("author");
-
- template.insert(book);
-
- book.setTitle(null);
- template.update(book);
-
- Book loaded = template.selectOneById(Book.class, book.getIsbn());
-
- assertThat(loaded.getTitle()).isNull();
- assertThat(loaded.getAuthor()).isEqualTo("author");
- }
-
- @Test // DATACASS-182
- public void insertShouldRemoveFields() {
+ @Test // DATACASS-182, DATACASS-420
+ public void insertShouldNotRemoveFields() {
Book book = new Book();
book.setIsbn("isbn");
@@ -662,7 +643,7 @@ public class CassandraOperationsIntegrationTests extends AbstractKeyspaceCreatin
Book loaded = template.selectOneById(Book.class, book.getIsbn());
- assertThat(loaded.getTitle()).isNull();
+ assertThat(loaded.getTitle()).isEqualTo("title");
assertThat(loaded.getAuthor()).isEqualTo("author");
}
@@ -683,6 +664,26 @@ public class CassandraOperationsIntegrationTests extends AbstractKeyspaceCreatin
assertThat(loaded.getTitle()).isEqualTo("title");
}
+ @Test // DATACASS-182, DATACASS-420
+ public void updateShouldRemoveFields() {
+
+ Book book = new Book();
+ book.setIsbn("isbn");
+ book.setTitle("title");
+ book.setAuthor("author");
+
+ template.insert(book);
+
+ book.setTitle(null);
+
+ template.update(book);
+
+ Book loaded = template.selectOneById(Book.class, book.getIsbn());
+
+ assertThat(loaded.getTitle()).isNull();
+ assertThat(loaded.getAuthor()).isEqualTo("author");
+ }
+
@Test // DATACASS-182
public void insertAndUpdateToEmptyCollection() {