DATAMONGO-2195 - Throw OptimisticLockingFailureException on delete only in repositories.
OptimisticLockingFailureException is now thrown only when deleting an entity through a repository and no longer when using the Template API. Original pull request: #641.
This commit is contained in:
@@ -1730,36 +1730,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
DeleteResult result = multi ? collectionToUse.deleteMany(removeQuery, options)
|
||||
: collectionToUse.deleteOne(removeQuery, options);
|
||||
|
||||
checkForOptimisticLockingFailures(result, removeQuery, collectionToUse);
|
||||
|
||||
maybeEmitEvent(new AfterDeleteEvent<>(queryObject, entityClass, collectionName));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkForOptimisticLockingFailures(DeleteResult result, Document removeQuery,
|
||||
MongoCollection<Document> collectionToUse) {
|
||||
|
||||
if (multi || !ResultOperations.isUndecidedDeleteResult(result, removeQuery, entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String versionFieldName = entity.getVersionProperty().getFieldName();
|
||||
Document idQuery = new Document(removeQuery);
|
||||
idQuery.remove(versionFieldName);
|
||||
|
||||
Iterator<Document> it = collectionToUse.find(idQuery).projection(Projections.include("_id", versionFieldName))
|
||||
.limit(1).iterator();
|
||||
|
||||
if (it.hasNext()) {
|
||||
|
||||
Document source = it.next();
|
||||
throw ResultOperations.newDeleteVersionedOptimisticLockingException(source.get("_id"), collectionName,
|
||||
removeQuery.get(versionFieldName), source.get(versionFieldName));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1824,35 +1824,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return collectionToUse.deleteMany(removeQuery, deleteOptions);
|
||||
}
|
||||
|
||||
}).flatMap(deleteResult -> {
|
||||
|
||||
if (!ResultOperations.isUndecidedDeleteResult(deleteResult, removeQuery, entity)) {
|
||||
return Mono.just(deleteResult);
|
||||
}
|
||||
|
||||
return execute(collectionName, (coll -> {
|
||||
|
||||
String versionFieldName = entity.getVersionProperty().getFieldName();
|
||||
|
||||
Document queryWithoutVersion = new Document(removeQuery);
|
||||
queryWithoutVersion.remove(versionFieldName);
|
||||
|
||||
Publisher<Document> publisher = coll.find(queryWithoutVersion)
|
||||
.projection(Projections.include("_id", entity.getVersionProperty().getFieldName())).limit(1).first();
|
||||
|
||||
return Mono.from(publisher) //
|
||||
.defaultIfEmpty(new Document()) //
|
||||
.flatMap(it -> {
|
||||
|
||||
if (it.isEmpty()) {
|
||||
return Mono.just(deleteResult);
|
||||
}
|
||||
|
||||
return Mono.error(ResultOperations.newDeleteVersionedOptimisticLockingException(it.get("_id"),
|
||||
collectionName, removeQuery.get(versionFieldName), it.get(versionFieldName)));
|
||||
});
|
||||
})).next();
|
||||
|
||||
}).doOnNext(it -> maybeEmitEvent(new AfterDeleteEvent<>(queryObject, entityClass, collectionName))) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.mongodb.core;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ResultOperations} is intended to share logic depending on {@link DeleteResult} and
|
||||
* {@link com.mongodb.client.result.UpdateResult} between reactive and imperative implementations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
*/
|
||||
class ResultOperations {
|
||||
|
||||
/**
|
||||
* Decide if a given {@link DeleteResult} needs further inspection. Returns {@literal true} if the delete of a
|
||||
* {@link org.springframework.data.annotation.Version versioned} entity did not remove any documents although the
|
||||
* operation has been {@link DeleteResult#wasAcknowledged() acknowledged}.
|
||||
*
|
||||
* @param deleteResult must not be {@literal null}.
|
||||
* @param query the actual query used for the delete operation.
|
||||
* @param entity can be {@literal null}.
|
||||
* @return {@literal true} if it cannot be decided if nothing got deleted because of a potential
|
||||
* {@link org.springframework.data.annotation.Version version} mismatch, or if the document did not exist in
|
||||
* first place.
|
||||
*/
|
||||
static boolean isUndecidedDeleteResult(DeleteResult deleteResult, org.bson.Document query,
|
||||
@Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (!deleteResult.wasAcknowledged() || deleteResult.getDeletedCount() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.hasVersionProperty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return containsVersionProperty(query, entity);
|
||||
}
|
||||
|
||||
static OptimisticLockingFailureException newDeleteVersionedOptimisticLockingException(Object id,
|
||||
String collectionName, Object expectedVersion, Object actualVersion) {
|
||||
|
||||
throw new OptimisticLockingFailureException(
|
||||
String.format("The entity with id %s in %s has changed and cannot be deleted! " + System.lineSeparator() + //
|
||||
"Expected version %s but was %s.", id, collectionName, expectedVersion, actualVersion));
|
||||
}
|
||||
|
||||
private static boolean containsVersionProperty(Document document, MongoPersistentEntity<?> entity) {
|
||||
return document.containsKey(entity.getRequiredVersionProperty().getFieldName());
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Mongo specific {@link EntityInformation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface MongoEntityInformation<T, ID> extends EntityInformation<T, ID> {
|
||||
|
||||
@@ -37,4 +39,25 @@ public interface MongoEntityInformation<T, ID> extends EntityInformation<T, ID>
|
||||
* @return
|
||||
*/
|
||||
String getIdAttribute();
|
||||
|
||||
/**
|
||||
* Returns whether the entity uses optimistic locking.
|
||||
*
|
||||
* @return
|
||||
* @since 2.2
|
||||
*/
|
||||
default boolean isVersioned() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version value for the entity or {@literal null} if the entity is not {@link #isVersioned() versioned}.
|
||||
*
|
||||
* @param entity must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
default Object getVersion(T entity) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.repository.support;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
@@ -87,14 +88,16 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
|
||||
this.fallbackIdType = idType != null ? idType : (Class<ID>) ObjectId.class;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollectionName()
|
||||
*/
|
||||
public String getCollectionName() {
|
||||
return customCollectionName == null ? entityMetadata.getCollection() : customCollectionName;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getIdAttribute()
|
||||
*/
|
||||
public String getIdAttribute() {
|
||||
@@ -106,7 +109,6 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
|
||||
* @see org.springframework.data.repository.core.support.PersistentEntityInformation#getIdType()
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<ID> getIdType() {
|
||||
|
||||
if (this.entityMetadata.hasIdProperty()) {
|
||||
@@ -115,4 +117,30 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
|
||||
|
||||
return fallbackIdType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#isVersioned()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVersioned() {
|
||||
return this.entityMetadata.hasVersionProperty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getVersion(Object)
|
||||
*/
|
||||
@Override
|
||||
public Object getVersion(T entity) {
|
||||
|
||||
if (!isVersioned()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor<T> accessor = this.entityMetadata.getPropertyAccessor(entity);
|
||||
|
||||
return accessor.getProperty(this.entityMetadata.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
@@ -40,6 +41,8 @@ import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
|
||||
/**
|
||||
* Repository base implementation for Mongo.
|
||||
*
|
||||
@@ -161,7 +164,14 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
|
||||
Assert.notNull(entity, "The given entity must not be null!");
|
||||
|
||||
mongoOperations.remove(entity, entityInformation.getCollectionName());
|
||||
DeleteResult deleteResult = mongoOperations.remove(entity, entityInformation.getCollectionName());
|
||||
|
||||
if (entityInformation.isVersioned() && deleteResult.wasAcknowledged() && deleteResult.getDeletedCount() == 0) {
|
||||
throw new OptimisticLockingFailureException(String.format(
|
||||
"The entity with id %s with version %s in %s cannot be deleted! Was it modified or deleted in the meantime?",
|
||||
entityInformation.getId(entity), entityInformation.getVersion(entity),
|
||||
entityInformation.getCollectionName()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
@@ -39,6 +40,8 @@ import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
|
||||
/**
|
||||
* Reactive repository base implementation for Mongo.
|
||||
*
|
||||
@@ -355,7 +358,24 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(entity, "The given entity must not be null!");
|
||||
|
||||
return mongoOperations.remove(entity, entityInformation.getCollectionName()).then();
|
||||
Mono<DeleteResult> remove = mongoOperations.remove(entity, entityInformation.getCollectionName());
|
||||
|
||||
if (entityInformation.isVersioned()) {
|
||||
|
||||
remove = remove.handle((deleteResult, sink) -> {
|
||||
|
||||
if (deleteResult.wasAcknowledged() && deleteResult.getDeletedCount() == 0) {
|
||||
sink.error(new OptimisticLockingFailureException(String.format(
|
||||
"The entity with id %s with version %s in %s cannot be deleted! Was it modified or deleted in the meantime?",
|
||||
entityInformation.getId(entity), entityInformation.getVersion(entity),
|
||||
entityInformation.getCollectionName())));
|
||||
} else {
|
||||
sink.next(deleteResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return remove.then();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -367,7 +387,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(entities, "The given Iterable of entities must not be null!");
|
||||
|
||||
return Flux.fromIterable(entities).flatMap(entity -> deleteById(entityInformation.getRequiredId(entity))).then();
|
||||
return Flux.fromIterable(entities).flatMap(this::delete).then();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1648,20 +1648,7 @@ public class MongoTemplateTests {
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeEntityWithMatchingVersion() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
|
||||
template.insert(person);
|
||||
assertThat(person.version, is(0));
|
||||
|
||||
template.remove(person);
|
||||
assertThat(template.count(new Query(), Person.class)).isZero();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeEntityWithoutMatchingVersionThrowsOptimisticLockingFailureException() {
|
||||
public void removeVersionedEntityConsidersVersion() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
@@ -1671,38 +1658,13 @@ public class MongoTemplateTests {
|
||||
template.update(PersonWithVersionPropertyOfTypeInteger.class).matching(query(where("id").is(person.id)))
|
||||
.apply(new Update().set("firstName", "Walter")).first();
|
||||
|
||||
Assertions.assertThatThrownBy(() -> template.remove(person)).isInstanceOf(OptimisticLockingFailureException.class)
|
||||
.hasMessageContaining("Expected version 0 but was 1");
|
||||
DeleteResult deleteResult = template.remove(person);
|
||||
|
||||
assertThat(deleteResult.wasAcknowledged()).isTrue();
|
||||
assertThat(deleteResult.getDeletedCount()).isZero();
|
||||
assertThat(template.count(new Query(), PersonWithVersionPropertyOfTypeInteger.class)).isOne();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeNonExistingVersionedEntityJustPasses() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.id = "id-1";
|
||||
person.firstName = "Dave";
|
||||
person.version = 10;
|
||||
|
||||
assertThat(template.remove(person).getDeletedCount()).isZero();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
@DirtiesContext
|
||||
public void removeEntityWithoutMatchingVersionWhenUsingUnaknowledgedWriteDoesNotThrowsOptimisticLockingFailureException() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
|
||||
template.insert(person);
|
||||
assertThat(person.version, is(0));
|
||||
template.update(PersonWithVersionPropertyOfTypeInteger.class).matching(query(where("id").is(person.id)))
|
||||
.apply(new Update().set("firstName", "Walter")).first();
|
||||
|
||||
template.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
|
||||
assertThat(template.remove(person).wasAcknowledged()).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-588
|
||||
public void initializesVersionOnBatchInsert() {
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -705,23 +705,7 @@ public class ReactiveMongoTemplateTests {
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeEntityWithMatchingVersion() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
|
||||
template.insert(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
assertThat(person.version).isZero();
|
||||
|
||||
template.remove(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
template.count(new Query(), PersonWithVersionPropertyOfTypeInteger.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(0L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeEntityWithoutMatchingVersionThrowsOptimisticLockingFailureException() {
|
||||
public void removeVersionedEntityConsidersVersion() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
@@ -734,42 +718,12 @@ public class ReactiveMongoTemplateTests {
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
template.remove(person).as(StepVerifier::create).expectError(OptimisticLockingFailureException.class).verify();
|
||||
template.count(new Query(), PersonWithVersionPropertyOfTypeInteger.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
template.remove(person).as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void removeNonExistingVersionedEntityJustPasses() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.id = "id-1";
|
||||
person.firstName = "Dave";
|
||||
person.version = 10;
|
||||
|
||||
template.remove(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
@DirtiesContext
|
||||
public void removeEntityWithoutMatchingVersionWhenUsingUnaknowledgedWriteDoesNotThrowsOptimisticLockingFailureException() {
|
||||
|
||||
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||
person.firstName = "Dave";
|
||||
|
||||
template.insert(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
assertThat(person.version).isZero();
|
||||
template.update(PersonWithVersionPropertyOfTypeInteger.class).matching(query(where("id").is(person.id)))
|
||||
.apply(new Update().set("firstName", "Walter")).first() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
template.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
|
||||
|
||||
template.remove(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
assertThat(actual.wasAcknowledged()).isTrue();
|
||||
assertThat(actual.getDeletedCount()).isZero();
|
||||
}).verifyComplete();
|
||||
template.count(new Query(), PersonWithVersionPropertyOfTypeInteger.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1L) //
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.MongoTestUtils;
|
||||
@@ -303,8 +302,11 @@ public class ReactiveMongoTemplateTransactionTests {
|
||||
|
||||
template.inTransaction().execute(action -> action.remove(saved)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectError(OptimisticLockingFailureException.class) //
|
||||
.verify();
|
||||
.consumeNextWith(actual -> {
|
||||
|
||||
assertThat(actual.wasAcknowledged()).isTrue();
|
||||
assertThat(actual.getDeletedCount()).isZero();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package org.springframework.data.mongodb.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.mongodb.client.MongoClients;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@@ -64,9 +65,6 @@ public class SimpleMongoRepositoryVersionedEntityTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static final MongoPersistentEntity ENTITY = new BasicMongoPersistentEntity(
|
||||
ClassTypeInformation.from(VersionedPerson.class));
|
||||
|
||||
@Autowired //
|
||||
private MongoTemplate template;
|
||||
|
||||
@@ -78,7 +76,10 @@ public class SimpleMongoRepositoryVersionedEntityTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
personEntityInformation = new MappingMongoEntityInformation(ENTITY);
|
||||
MongoPersistentEntity entity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(VersionedPerson.class);
|
||||
|
||||
personEntityInformation = new MappingMongoEntityInformation(entity);
|
||||
repository = new SimpleMongoRepository<>(personEntityInformation, template);
|
||||
repository.deleteAll();
|
||||
|
||||
@@ -117,8 +118,7 @@ public class SimpleMongoRepositoryVersionedEntityTests {
|
||||
|
||||
sarah.setVersion(5L);
|
||||
|
||||
assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> repository.delete(sarah)) //
|
||||
.withMessageContaining("Expected version 5 but was 0");
|
||||
assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> repository.delete(sarah));
|
||||
|
||||
assertThat(template.count(query(where("id").is(sarah.getId())), VersionedPerson.class)).isOne();
|
||||
}
|
||||
@@ -146,7 +146,8 @@ public class SimpleMongoRepositoryVersionedEntityTests {
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void deleteNonExisting() {
|
||||
repository.delete(new VersionedPerson("T-800"));
|
||||
assertThatThrownBy(() -> repository.delete(new VersionedPerson("T-800")))
|
||||
.isInstanceOf(OptimisticLockingFailureException.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
@@ -157,7 +158,8 @@ public class SimpleMongoRepositoryVersionedEntityTests {
|
||||
|
||||
initTxTemplate().execute(status -> {
|
||||
|
||||
repository.delete(new VersionedPerson("T-800"));
|
||||
assertThatThrownBy(() -> repository.delete(new VersionedPerson("T-800")))
|
||||
.isInstanceOf(OptimisticLockingFailureException.class);
|
||||
|
||||
return Void.TYPE;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2019 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.mongodb.repository.support;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.repository.VersionedPerson;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoClients;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class SimpleReactiveMongoRepositoryVersionedEntityTests {
|
||||
|
||||
@Configuration
|
||||
static class Config extends AbstractReactiveMongoConfiguration {
|
||||
|
||||
@Override
|
||||
public MongoClient reactiveMongoClient() {
|
||||
return MongoClients.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return "database";
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired //
|
||||
private ReactiveMongoTemplate template;
|
||||
|
||||
private MongoEntityInformation<VersionedPerson, String> personEntityInformation;
|
||||
private SimpleReactiveMongoRepository<VersionedPerson, String> repository;
|
||||
|
||||
private VersionedPerson sarah;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
MongoPersistentEntity entity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(VersionedPerson.class);
|
||||
|
||||
personEntityInformation = new MappingMongoEntityInformation(entity);
|
||||
repository = new SimpleReactiveMongoRepository<>(personEntityInformation, template);
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
sarah = repository.save(new VersionedPerson("Sarah", "Connor")).block();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void deleteWithMatchingVersion() {
|
||||
|
||||
repository.delete(sarah).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
template.count(query(where("id").is(sarah.getId())), VersionedPerson.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(0L).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void deleteWithVersionMismatch() {
|
||||
|
||||
sarah.setVersion(5L);
|
||||
|
||||
repository.delete(sarah).as(StepVerifier::create).verifyError(OptimisticLockingFailureException.class);
|
||||
|
||||
template.count(query(where("id").is(sarah.getId())), VersionedPerson.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2195
|
||||
public void deleteNonExisting() {
|
||||
|
||||
repository.delete(new VersionedPerson("T-800")).as(StepVerifier::create)
|
||||
.verifyError(OptimisticLockingFailureException.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
* <<mongodb.reactive.repositories.queries.type-safe,Querydsl support for reactive repositories>> via `ReactiveQuerydslPredicateExecutor`.
|
||||
* <<reactive.gridfs,Reactive GridFS support>>.
|
||||
* Extended SpEL aggregation support for MongoDB 3.4 and MongoDB 4.0 operators (see <<mongo.aggregation.projection.expressions>>).
|
||||
* Template API delete by entity considers the version property in delete queries.
|
||||
* Repository deletes now throw `OptimisticLockingFailureException` when a versioned entity cannot be deleted.
|
||||
|
||||
[[new-features.2-1-0]]
|
||||
== What's New in Spring Data MongoDB 2.1
|
||||
|
||||
@@ -1080,11 +1080,12 @@ template.save(tmp); // throws OptimisticLockingFailureException
|
||||
|
||||
IMPORTANT: Optimistic Locking requires to set the `WriteConcern` to `ACKNOWLEDGED`. Otherwise `OptimisticLockingFailureException` can be silently swallowed.
|
||||
|
||||
NOTE: As of Version 2.2 `MongoOperations` also includes the `@Version` property when removing an entity from the database, raising an
|
||||
`OptimisticLockingFailureException` if a document with matching `_id` but a `version` mismatch shall be deleted. In cases
|
||||
where no Document with matching `_id` can be found the operation passes without error.
|
||||
NOTE: As of Version 2.2 `MongoOperations` also includes the `@Version` property when removing an entity from the database.
|
||||
To remove a `Document` without version check use `MongoOperations#remove(Query,...)` instead of `MongoOperations#remove(Object)`.
|
||||
|
||||
NOTE: As of Version 2.2 repositories check for the outcome of acknowledged deletes when removing versioned entities.
|
||||
An `OptimisticLockingFailureException` is raised if a versioned entity cannot be deleted through `CrudRepository.delete(Object)`. In such case, the version was changed or the object was deleted in the meantime. Use `CrudRepository.deleteById(ID)` to bypass optimistic locking functionality and delete objects regardless of their version.
|
||||
|
||||
[[mongo.query]]
|
||||
== Querying Documents
|
||||
|
||||
|
||||
Reference in New Issue
Block a user