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:
Mark Paluch
2022-08-23 15:21:45 +02:00
parent 7975e90b3e
commit c33addd934
6 changed files with 139 additions and 19 deletions

View File

@@ -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;
}
}
}

View File

@@ -200,7 +200,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;
}

View File

@@ -183,7 +183,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));
}
@@ -257,8 +257,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()) {

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();
}
}