DATAMONGO-2285 - Polishing.

Preserve existing logic translating com.mongodb.MongoBulkWriteException that might be thrown by calling MongoOperations.insertAll(Collection), and move bulk operation translation to DefaultBulkOperations.
Along the lines remove the no longer used PersistenceExceptionTranslator from DefaultBulkOperations.

Original Pull Request: #862
This commit is contained in:
Christoph Strobl
2020-06-23 08:37:37 +02:00
parent f907dbc559
commit cee1d976de
6 changed files with 70 additions and 56 deletions

View File

@@ -24,8 +24,9 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
@@ -46,6 +47,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.mongodb.MongoBulkWriteException;
import com.mongodb.WriteConcern;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.MongoCollection;
@@ -62,6 +64,7 @@ import com.mongodb.client.model.*;
* @author Jens Schauder
* @author Michail Nikolaev
* @author Roman Puchkovskiy
* @author Jacob Botuck
* @since 1.9
*/
class DefaultBulkOperations implements BulkOperations {
@@ -71,7 +74,6 @@ class DefaultBulkOperations implements BulkOperations {
private final BulkOperationContext bulkOperationContext;
private final List<SourceAwareWriteModelHolder> models = new ArrayList<>();
private PersistenceExceptionTranslator exceptionTranslator;
private @Nullable WriteConcern defaultWriteConcern;
private BulkWriteOptions bulkOptions;
@@ -95,19 +97,9 @@ class DefaultBulkOperations implements BulkOperations {
this.mongoOperations = mongoOperations;
this.collectionName = collectionName;
this.bulkOperationContext = bulkOperationContext;
this.exceptionTranslator = new MongoExceptionTranslator();
this.bulkOptions = getBulkWriteOptions(bulkOperationContext.getBulkMode());
}
/**
* Configures the {@link PersistenceExceptionTranslator} to be used. Defaults to {@link MongoExceptionTranslator}.
*
* @param exceptionTranslator can be {@literal null}.
*/
public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator == null ? new MongoExceptionTranslator() : exceptionTranslator;
}
/**
* Configures the default {@link WriteConcern} to be used. Defaults to {@literal null}.
*
@@ -314,11 +306,26 @@ class DefaultBulkOperations implements BulkOperations {
collection = collection.withWriteConcern(defaultWriteConcern);
}
return collection.bulkWrite( //
models.stream() //
.map(this::extractAndMapWriteModel) //
.collect(Collectors.toList()), //
bulkOptions);
try {
return collection.bulkWrite( //
models.stream() //
.map(this::extractAndMapWriteModel) //
.collect(Collectors.toList()), //
bulkOptions);
} catch (RuntimeException ex) {
if (ex instanceof MongoBulkWriteException) {
MongoBulkWriteException mongoBulkWriteException = (MongoBulkWriteException) ex;
if (mongoBulkWriteException.getWriteConcernError() != null) {
throw new DataIntegrityViolationException(ex.getMessage(), ex);
}
throw new BulkOperationException(ex.getMessage(), mongoBulkWriteException);
}
throw ex;
}
}
private WriteModel<Document> extractAndMapWriteModel(SourceAwareWriteModelHolder it) {

View File

@@ -29,7 +29,6 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.MongoTransactionException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
@@ -40,6 +39,7 @@ import org.springframework.util.ClassUtils;
import com.mongodb.MongoBulkWriteException;
import com.mongodb.MongoException;
import com.mongodb.MongoServerException;
import com.mongodb.bulk.BulkWriteError;
/**
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
@@ -49,7 +49,6 @@ import com.mongodb.MongoServerException;
* @author Oliver Gierke
* @author Michal Vich
* @author Christoph Strobl
* @author Jacob Botuck
*/
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
@@ -64,7 +63,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
Collections.singletonList("MongoInternalException"));
private static final Set<String> DATA_INTEGRITY_EXCEPTIONS = new HashSet<>(
Arrays.asList("WriteConcernException", "MongoWriteException"));
Arrays.asList("WriteConcernException", "MongoWriteException", "MongoBulkWriteException"));
/*
* (non-Javadoc)
@@ -99,17 +98,18 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
if (((MongoServerException) ex).getCode() == 11000) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof MongoBulkWriteException) {
for (BulkWriteError x : ((MongoBulkWriteException) ex).getWriteErrors()) {
if (x.getCode() == 11000) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
}
}
}
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
if (ex instanceof MongoBulkWriteException) {
MongoBulkWriteException mongoBulkWriteException = (MongoBulkWriteException) ex;
if (mongoBulkWriteException.getWriteConcernError() != null) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
return new BulkOperationException(ex.getMessage(), mongoBulkWriteException);
}
// All other MongoExceptions
if (ex instanceof MongoException) {

View File

@@ -757,7 +757,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
new BulkOperationContext(mode, Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper,
eventPublisher, entityCallbacks));
operations.setExceptionTranslator(exceptionTranslator);
operations.setDefaultWriteConcern(writeConcern);
return operations;

View File

@@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.convert.QueryMapper;
@@ -91,13 +92,13 @@ public class DefaultBulkOperationsIntegrationTests {
assertThat(createBulkOps(BulkMode.ORDERED).insert(documents).execute().getInsertedCount()).isEqualTo(2);
}
@Test // DATAMONGO-934
@Test // DATAMONGO-934, DATAMONGO-2285
public void insertOrderedFails() {
List<BaseDoc> documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2"));
assertThatThrownBy(() -> createBulkOps(BulkMode.ORDERED).insert(documents).execute()) //
.isInstanceOf(DuplicateKeyException.class) //
.isInstanceOf(BulkOperationException.class) //
.hasCauseInstanceOf(MongoBulkWriteException.class) //
.extracting(Throwable::getCause) //
.satisfies(it -> {
@@ -117,13 +118,13 @@ public class DefaultBulkOperationsIntegrationTests {
assertThat(createBulkOps(BulkMode.UNORDERED).insert(documents).execute().getInsertedCount()).isEqualTo(2);
}
@Test // DATAMONGO-934
@Test // DATAMONGO-934, DATAMONGO-2285
public void insertUnOrderedContinuesOnError() {
List<BaseDoc> documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2"));
assertThatThrownBy(() -> createBulkOps(BulkMode.UNORDERED).insert(documents).execute()) //
.isInstanceOf(DuplicateKeyException.class) //
.isInstanceOf(BulkOperationException.class) //
.hasCauseInstanceOf(MongoBulkWriteException.class) //
.extracting(Throwable::getCause) //
.satisfies(it -> {

View File

@@ -24,6 +24,7 @@ import static org.mockito.Mockito.eq;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -40,9 +41,11 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
@@ -64,9 +67,13 @@ import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.MongoBulkWriteException;
import com.mongodb.MongoWriteException;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.WriteError;
import com.mongodb.bulk.BulkWriteError;
import com.mongodb.bulk.WriteConcernError;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.BulkWriteOptions;
@@ -85,6 +92,7 @@ import com.mongodb.client.model.WriteModel;
* @author Minsu Kim
* @author Jens Schauder
* @author Roman Puchkovskiy
* @author Jacob Botuck
*/
@ExtendWith(MockitoExtension.class)
class DefaultBulkOperationsUnitTests {
@@ -367,6 +375,29 @@ class DefaultBulkOperationsUnitTests {
.isEqualTo(new Document("$set", new Document("items.$.documents.0.the_file_id", "file-id")));
}
@Test // DATAMONGO-2285
public void translateMongoBulkOperationExceptionWithWriteConcernError() {
when(collection.bulkWrite(anyList(), any(BulkWriteOptions.class))).thenThrow(new MongoBulkWriteException(null,
Collections.emptyList(),
new WriteConcernError(42, "codename", "writeconcern error happened", new BsonDocument()), new ServerAddress()));
assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> ops.insert(new SomeDomainType()).execute());
}
@Test // DATAMONGO-2285
public void translateMongoBulkOperationExceptionWithoutWriteConcernError() {
when(collection.bulkWrite(anyList(), any(BulkWriteOptions.class))).thenThrow(new MongoBulkWriteException(null,
Collections.singletonList(new BulkWriteError(42, "a write error happened", new BsonDocument(), 49)), null,
new ServerAddress()));
assertThatExceptionOfType(BulkOperationException.class)
.isThrownBy(() -> ops.insert(new SomeDomainType()).execute());
}
static class OrderTest {
String id;

View File

@@ -18,12 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import com.mongodb.MongoBulkWriteException;
import com.mongodb.bulk.BulkWriteError;
import com.mongodb.bulk.WriteConcernError;
import org.bson.BsonDocument;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -31,11 +26,9 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.NestedRuntimeException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.MongoTransactionException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
@@ -52,7 +45,6 @@ import com.mongodb.ServerAddress;
* @author Michal Vich
* @author Oliver Gierke
* @author Christoph Strobl
* @author Jacob Botuck
*/
public class MongoExceptionTranslatorUnitTests {
@@ -160,22 +152,6 @@ public class MongoExceptionTranslatorUnitTests {
checkTranslatedMongoException(MongoTransactionException.class, 267);
}
@Test
public void translateMongoBulkOperationExceptionWithWriteConcernError() {
expectExceptionWithCauseMessage(translator.translateExceptionIfPossible(new MongoBulkWriteException(null,
new ArrayList<>(), new WriteConcernError(42, "codename", "writeconcern error happened", new BsonDocument()),
new ServerAddress())), DataIntegrityViolationException.class, null);
}
@Test
public void translateMongoBulkOperationExceptionWithoutWriteConcernError() {
expectExceptionWithCauseMessage(translator.translateExceptionIfPossible(new MongoBulkWriteException(null,
Arrays.asList(new BulkWriteError(42, "a write error happened", new BsonDocument(), 49)), null,
new ServerAddress())), BulkOperationException.class, null);
}
private void checkTranslatedMongoException(Class<? extends Exception> clazz, int code) {
DataAccessException translated = translator.translateExceptionIfPossible(new MongoException(code, ""));