Fix deleteAllById(…) using composite keys.
We now correctly delete all elements when using composite keys (MapId, Primary Key classes). Previously, we did not support that case or removed only the first element. Closes #1298
This commit is contained in:
@@ -20,7 +20,10 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.MapId;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -85,9 +88,11 @@ class FindByIdQuery {
|
||||
* Check if the {@link Iterable} of {@code ID}s contains composite keys.
|
||||
*
|
||||
* @param ids
|
||||
* @param mappingContext
|
||||
* @return
|
||||
*/
|
||||
static boolean hasCompositeKeys(Iterable<?> ids) {
|
||||
static boolean hasCompositeKeys(Iterable<?> ids,
|
||||
MappingContext<BasicCassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
|
||||
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null");
|
||||
|
||||
@@ -100,6 +105,13 @@ class FindByIdQuery {
|
||||
if (mapId.size() > 1) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
||||
BasicCassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(id.getClass());
|
||||
|
||||
if (persistentEntity != null && persistentEntity.isCompositePrimaryKey()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,11 @@ public class SimpleCassandraRepository<T, ID> implements CassandraRepository<T,
|
||||
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null");
|
||||
|
||||
if (!ids.iterator().hasNext()) {
|
||||
if (FindByIdQuery.hasCompositeKeys(ids, this.mappingContext)) {
|
||||
|
||||
for (ID id : ids) {
|
||||
deleteById(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassand
|
||||
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null");
|
||||
|
||||
if (FindByIdQuery.hasCompositeKeys(ids)) {
|
||||
if (FindByIdQuery.hasCompositeKeys(ids, this.mappingContext)) {
|
||||
return findAllById(Flux.fromIterable(ids));
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ public class SimpleReactiveCassandraRepository<T, ID> implements ReactiveCassand
|
||||
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null");
|
||||
|
||||
if (FindByIdQuery.hasCompositeKeys(ids)) {
|
||||
return deleteById(Flux.fromIterable(ids));
|
||||
if (FindByIdQuery.hasCompositeKeys(ids, this.mappingContext)) {
|
||||
return Flux.fromIterable(ids).flatMap(this::deleteById).then();
|
||||
}
|
||||
|
||||
if (!ids.iterator().hasNext()) {
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -28,6 +30,8 @@ import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
|
||||
*/
|
||||
@PrimaryKeyClass
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CompositeKey implements Serializable {
|
||||
|
||||
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1, name = "first_name") private String firstname;
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -26,20 +27,24 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.cassandra.core.CassandraTemplate;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
import org.springframework.data.cassandra.core.mapping.MapId;
|
||||
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.domain.User;
|
||||
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link SimpleCassandraRepository} using MapId.
|
||||
* Integration tests for {@link SimpleCassandraRepository} using MapId and primary key classes.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class SimpleCassandraRepositoryMapIdIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
|
||||
class SimpleCassandraRepositoryCompositeIdIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
|
||||
|
||||
private SimpleCassandraRepository<User, MapId> simple;
|
||||
|
||||
private SimpleCassandraRepository<TypeWithMapId, MapId> composite;
|
||||
private SimpleCassandraRepository<TypeWithMapId, MapId> mapId;
|
||||
|
||||
private SimpleCassandraRepository<TypeWithKeyClass, CompositeKey> primaryKeyClass;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -48,17 +53,22 @@ class SimpleCassandraRepositoryMapIdIntegrationTests extends AbstractKeyspaceCre
|
||||
CassandraTemplate template = new CassandraTemplate(this.session);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(User.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TypeWithKeyClass.class, template);
|
||||
|
||||
SchemaTestUtils.truncate(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.truncate(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.truncate(TypeWithKeyClass.class, template);
|
||||
|
||||
simple = new SimpleCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(User.class), template.getConverter()),
|
||||
template);
|
||||
|
||||
composite = new SimpleCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
mapId = new SimpleCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(TypeWithMapId.class),
|
||||
template.getConverter()), template);
|
||||
|
||||
primaryKeyClass = new SimpleCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(TypeWithKeyClass.class),
|
||||
template.getConverter()), template);
|
||||
}
|
||||
|
||||
@Test // DATACASS-661
|
||||
@@ -81,9 +91,43 @@ class SimpleCassandraRepositoryMapIdIntegrationTests extends AbstractKeyspaceCre
|
||||
withMapId.setFirstname("Walter");
|
||||
withMapId.setLastname("White");
|
||||
|
||||
composite.save(withMapId);
|
||||
mapId.save(withMapId);
|
||||
|
||||
assertThatThrownBy(() -> composite.findAllById(Collections.singletonList(withMapId.getMapId())))
|
||||
assertThatThrownBy(() -> mapId.findAllById(Collections.singletonList(withMapId.getMapId())))
|
||||
.isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
@Test // GH-1298
|
||||
void shouldDeleteAllByMapId() {
|
||||
|
||||
TypeWithMapId withMapId1 = new TypeWithMapId();
|
||||
withMapId1.setFirstname("Walter");
|
||||
withMapId1.setLastname("White");
|
||||
|
||||
TypeWithMapId withMapId2 = new TypeWithMapId();
|
||||
withMapId2.setFirstname("Skyler");
|
||||
withMapId2.setLastname("White");
|
||||
|
||||
mapId.saveAll(Arrays.asList(withMapId1, withMapId2));
|
||||
|
||||
mapId.deleteAllById(Arrays.asList(withMapId1.getMapId(), withMapId2.getMapId()));
|
||||
|
||||
assertThat(mapId.findAll()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-1298
|
||||
void shouldDeleteAllByCompositeId() {
|
||||
|
||||
TypeWithKeyClass composite1 = new TypeWithKeyClass();
|
||||
composite1.setKey(new CompositeKey("Walter", "White"));
|
||||
|
||||
TypeWithKeyClass composite2 = new TypeWithKeyClass();
|
||||
composite2.setKey(new CompositeKey("Skyler", "White"));
|
||||
|
||||
primaryKeyClass.saveAll(Arrays.asList(composite1, composite2));
|
||||
|
||||
primaryKeyClass.deleteAllById(Arrays.asList(composite1.getKey(), composite2.getKey()));
|
||||
|
||||
assertThat(primaryKeyClass.findAll()).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -27,20 +28,24 @@ import org.springframework.data.cassandra.core.ReactiveCassandraTemplate;
|
||||
import org.springframework.data.cassandra.core.cql.session.DefaultBridgedReactiveSession;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
import org.springframework.data.cassandra.core.mapping.MapId;
|
||||
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.domain.User;
|
||||
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link SimpleReactiveCassandraRepository} using MapId.
|
||||
* Integration tests for {@link SimpleReactiveCassandraRepository} using MapId and primary key classes.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class SimpleReactiveCassandraRepositoryMapIdIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
|
||||
class SimpleReactiveCassandraRepositoryCompositeIdIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
|
||||
|
||||
private SimpleReactiveCassandraRepository<User, MapId> simple;
|
||||
|
||||
private SimpleReactiveCassandraRepository<TypeWithMapId, MapId> composite;
|
||||
private SimpleReactiveCassandraRepository<TypeWithMapId, MapId> mapId;
|
||||
|
||||
private SimpleReactiveCassandraRepository<TypeWithKeyClass, CompositeKey> primaryKeyClass;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -49,9 +54,10 @@ class SimpleReactiveCassandraRepositoryMapIdIntegrationTests extends AbstractKey
|
||||
CassandraTemplate template = new CassandraTemplate(this.session);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(User.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TypeWithKeyClass.class, template);
|
||||
|
||||
SchemaTestUtils.truncate(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.truncate(TypeWithMapId.class, template);
|
||||
SchemaTestUtils.truncate(TypeWithKeyClass.class, template);
|
||||
|
||||
ReactiveCassandraTemplate reactiveTemplate = new ReactiveCassandraTemplate(
|
||||
new DefaultBridgedReactiveSession(this.session));
|
||||
@@ -60,9 +66,13 @@ class SimpleReactiveCassandraRepositoryMapIdIntegrationTests extends AbstractKey
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(User.class), template.getConverter()),
|
||||
reactiveTemplate);
|
||||
|
||||
composite = new SimpleReactiveCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
mapId = new SimpleReactiveCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(TypeWithMapId.class),
|
||||
template.getConverter()), reactiveTemplate);
|
||||
|
||||
primaryKeyClass = new SimpleReactiveCassandraRepository<>(new MappingCassandraEntityInformation(
|
||||
template.getConverter().getMappingContext().getRequiredPersistentEntity(TypeWithKeyClass.class),
|
||||
template.getConverter()), reactiveTemplate);
|
||||
}
|
||||
|
||||
@Test // DATACASS-661
|
||||
@@ -91,14 +101,60 @@ class SimpleReactiveCassandraRepositoryMapIdIntegrationTests extends AbstractKey
|
||||
withMapId.setFirstname("Walter");
|
||||
withMapId.setLastname("White");
|
||||
|
||||
composite.save(withMapId) //
|
||||
mapId.save(withMapId) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
composite.findAllById(Collections.singletonList(withMapId.getMapId())) //
|
||||
mapId.findAllById(Collections.singletonList(withMapId.getMapId())) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-1298
|
||||
void shouldDeleteAllByMapId() {
|
||||
|
||||
TypeWithMapId withMapId1 = new TypeWithMapId();
|
||||
withMapId1.setFirstname("Walter");
|
||||
withMapId1.setLastname("White");
|
||||
|
||||
TypeWithMapId withMapId2 = new TypeWithMapId();
|
||||
withMapId2.setFirstname("Skyler");
|
||||
withMapId2.setLastname("White");
|
||||
|
||||
mapId.saveAll(Arrays.asList(withMapId1, withMapId2)).then() //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
mapId.deleteAllById(Arrays.asList(withMapId1.getMapId(), withMapId2.getMapId())) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
mapId.findAll() //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-1298
|
||||
void shouldDeleteAllByCompositeId() {
|
||||
|
||||
TypeWithKeyClass composite1 = new TypeWithKeyClass();
|
||||
composite1.setKey(new CompositeKey("Walter", "White"));
|
||||
|
||||
TypeWithKeyClass composite2 = new TypeWithKeyClass();
|
||||
composite2.setKey(new CompositeKey("Skyler", "White"));
|
||||
|
||||
primaryKeyClass.saveAll(Arrays.asList(composite1, composite2)).then() //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
primaryKeyClass.deleteAllById(Arrays.asList(composite1.getKey(), composite2.getKey())) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
primaryKeyClass.findAll() //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user