Browse Source

Polishing.

Original pull request: #605
See #3148
See #2939
pull/4778/merge
Mark Paluch 1 year ago
parent
commit
7fbd4966d7
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientClientSessionException.java
  2. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbException.java
  3. 33
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java
  4. 28
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java
  5. 36
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java
  6. 16
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java
  7. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java
  8. 26
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java
  9. 61
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java
  10. 27
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java
  11. 31
      src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientClientSessionException.java

@ -16,24 +16,23 @@
package org.springframework.data.mongodb; package org.springframework.data.mongodb;
import org.springframework.dao.TransientDataAccessException; import org.springframework.dao.TransientDataAccessException;
import org.springframework.lang.Nullable;
/** /**
* {@link TransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data * {@link TransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
* access failures such as reading data using an already closed session. * access failures such as reading data using an already closed session.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.3 * @since 4.4
*/ */
public class TransientClientSessionException extends TransientMongoDbException { public class TransientClientSessionException extends TransientMongoDbException {
/** /**
* Constructor for {@link TransientClientSessionException}. * Constructor for {@link TransientClientSessionException}.
* *
* @param msg the detail message. Can be {@literal null}. * @param msg the detail message.
* @param cause the root cause. Can be {@literal null}. * @param cause the root cause.
*/ */
public TransientClientSessionException(@Nullable String msg, @Nullable Throwable cause) { public TransientClientSessionException(String msg, Throwable cause) {
super(msg, cause); super(msg, cause);
} }
} }

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbException.java

@ -16,7 +16,6 @@
package org.springframework.data.mongodb; package org.springframework.data.mongodb;
import org.springframework.dao.TransientDataAccessException; import org.springframework.dao.TransientDataAccessException;
import org.springframework.lang.Nullable;
/** /**
* Root of the hierarchy of MongoDB specific data access exceptions that are considered transient such as * Root of the hierarchy of MongoDB specific data access exceptions that are considered transient such as
@ -24,17 +23,17 @@ import org.springframework.lang.Nullable;
* specific labels}. * specific labels}.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 3.3 * @since 4.4
*/ */
public class TransientMongoDbException extends TransientDataAccessException { public class TransientMongoDbException extends TransientDataAccessException {
/** /**
* Constructor for {@link TransientMongoDbException}. * Constructor for {@link TransientMongoDbException}.
* *
* @param msg the detail message. Can be {@literal null}. * @param msg the detail message.
* @param cause the root cause. Can be {@literal null}. * @param cause the root cause.
*/ */
public TransientMongoDbException(String msg, @Nullable Throwable cause) { public TransientMongoDbException(String msg, Throwable cause) {
super(msg, cause); super(msg, cause);
} }
} }

33
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java

@ -55,8 +55,6 @@ import com.mongodb.event.ClusterListener;
*/ */
public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> implements PersistenceExceptionTranslator { public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> implements PersistenceExceptionTranslator {
private static final PersistenceExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();
private @Nullable MongoClientSettings mongoClientSettings; private @Nullable MongoClientSettings mongoClientSettings;
private @Nullable String host; private @Nullable String host;
private @Nullable Integer port; private @Nullable Integer port;
@ -64,7 +62,7 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> imp
private @Nullable ConnectionString connectionString; private @Nullable ConnectionString connectionString;
private @Nullable String replicaSet = null; private @Nullable String replicaSet = null;
private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_EXCEPTION_TRANSLATOR; private PersistenceExceptionTranslator exceptionTranslator = MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR;
/** /**
* Set the {@link MongoClientSettings} to be used when creating {@link MongoClient}. * Set the {@link MongoClientSettings} to be used when creating {@link MongoClient}.
@ -116,23 +114,34 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> imp
* @param exceptionTranslator * @param exceptionTranslator
*/ */
public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) { public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator == null ? DEFAULT_EXCEPTION_TRANSLATOR : exceptionTranslator; this.exceptionTranslator = exceptionTranslator == null ? MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR
} : exceptionTranslator;
public Class<? extends MongoClient> getObjectType() {
return MongoClient.class;
} }
@Override
@Nullable @Nullable
public DataAccessException translateExceptionIfPossible(RuntimeException ex) { public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex); return exceptionTranslator.translateExceptionIfPossible(ex);
} }
@Override
public Class<? extends MongoClient> getObjectType() {
return MongoClient.class;
}
@Override @Override
protected MongoClient createInstance() throws Exception { protected MongoClient createInstance() throws Exception {
return createMongoClient(computeClientSetting()); return createMongoClient(computeClientSetting());
} }
@Override
protected void destroyInstance(@Nullable MongoClient instance) throws Exception {
if (instance != null) {
instance.close();
}
}
/** /**
* Create {@link MongoClientSettings} based on configuration and priority (lower is better). * Create {@link MongoClientSettings} based on configuration and priority (lower is better).
* <ol> * <ol>
@ -324,14 +333,6 @@ public class MongoClientFactoryBean extends AbstractFactoryBean<MongoClient> imp
return !fromConnectionStringIsDefault ? fromConnectionString : defaultValue; return !fromConnectionStringIsDefault ? fromConnectionString : defaultValue;
} }
@Override
protected void destroyInstance(@Nullable MongoClient instance) throws Exception {
if (instance != null) {
instance.close();
}
}
private MongoClient createMongoClient(MongoClientSettings settings) throws UnknownHostException { private MongoClient createMongoClient(MongoClientSettings settings) throws UnknownHostException {
return MongoClients.create(settings, SpringDataMongoDB.driverInformation()); return MongoClients.create(settings, SpringDataMongoDB.driverInformation());
} }

28
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java

@ -32,8 +32,7 @@ import com.mongodb.client.MongoDatabase;
/** /**
* Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as * Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as
* database name and exception translator. * database name and exception translator. <br />
* <br />
* Not intended to be used directly. * Not intended to be used directly.
* *
* @author Christoph Strobl * @author Christoph Strobl
@ -47,8 +46,8 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
private final C mongoClient; private final C mongoClient;
private final String databaseName; private final String databaseName;
private final boolean mongoInstanceCreated; private final boolean mongoInstanceCreated;
private final PersistenceExceptionTranslator exceptionTranslator;
private PersistenceExceptionTranslator exceptionTranslator;
private @Nullable WriteConcern writeConcern; private @Nullable WriteConcern writeConcern;
/** /**
@ -75,15 +74,31 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
this.exceptionTranslator = exceptionTranslator; this.exceptionTranslator = exceptionTranslator;
} }
/**
* Configures the {@link PersistenceExceptionTranslator} to be used.
*
* @param exceptionTranslator the exception translator to set.
* @since 4.4
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
/** /**
* Configures the {@link WriteConcern} to be used on the {@link MongoDatabase} instance being created. * Configures the {@link WriteConcern} to be used on the {@link MongoDatabase} instance being created.
* *
* @param writeConcern the writeConcern to set * @param writeConcern the writeConcern to set.
*/ */
public void setWriteConcern(WriteConcern writeConcern) { public void setWriteConcern(WriteConcern writeConcern) {
this.writeConcern = writeConcern; this.writeConcern = writeConcern;
} }
@Override
public MongoDatabase getMongoDatabase() throws DataAccessException { public MongoDatabase getMongoDatabase() throws DataAccessException {
return getMongoDatabase(getDefaultDatabaseName()); return getMongoDatabase(getDefaultDatabaseName());
} }
@ -116,10 +131,7 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
} }
} }
public PersistenceExceptionTranslator getExceptionTranslator() { @Override
return this.exceptionTranslator;
}
public MongoDatabaseFactory withSession(ClientSession session) { public MongoDatabaseFactory withSession(ClientSession session) {
return new MongoDatabaseFactorySupport.ClientSessionBoundMongoDbFactory(session, this); return new MongoDatabaseFactorySupport.ClientSessionBoundMongoDbFactory(session, this);
} }

36
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import java.util.Set; import java.util.Set;
import org.bson.BsonInvalidOperationException; import org.bson.BsonInvalidOperationException;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
@ -25,11 +26,9 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.ClientSessionException; import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.TransientClientSessionException; import org.springframework.data.mongodb.TransientClientSessionException;
import org.springframework.data.mongodb.TransientMongoDbException;
import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.util.MongoDbErrorCodes; import org.springframework.data.mongodb.util.MongoDbErrorCodes;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -53,6 +52,8 @@ import com.mongodb.bulk.BulkWriteError;
*/ */
public class MongoExceptionTranslator implements PersistenceExceptionTranslator { public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
public static final MongoExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();
private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = Set.of("MongoException.DuplicateKey", private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = Set.of("MongoException.DuplicateKey",
"DuplicateKeyException"); "DuplicateKeyException");
@ -70,18 +71,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
@Override @Override
@Nullable @Nullable
public DataAccessException translateExceptionIfPossible(RuntimeException ex) { public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return doTranslateException(ex);
DataAccessException translatedException = doTranslateException(ex);
if (translatedException == null) {
return null;
}
// Translated exceptions that per se are not be recoverable (eg. WriteConflicts), might still be transient inside a
// transaction. Let's wrap those.
return (isTransientFailure(ex) && !(translatedException instanceof TransientDataAccessException))
? new TransientMongoDbException(ex.getMessage(), translatedException)
: translatedException;
} }
@Nullable @Nullable
@ -180,21 +170,23 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
/** /**
* Check if a given exception holds an error label indicating a transient failure. * Check if a given exception holds an error label indicating a transient failure.
* *
* @param e * @param e the exception to inspect.
* @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient * @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient
* exception error labels. * exception error labels.
* @see MongoException#hasErrorLabel(String) * @see MongoException#hasErrorLabel(String)
* @since 3.3 * @since 4.4
*/ */
public static boolean isTransientFailure(Exception e) { public boolean isTransientFailure(Exception e) {
if (!(e instanceof MongoException)) { if (e instanceof MongoException mongoException) {
return false; return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
} }
MongoException mongoException = (MongoException) e; if (e.getCause() != e && e.getCause() instanceof Exception ex) {
return isTransientFailure(ex);
}
return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL) return false;
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
} }
} }

16
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java

@ -36,13 +36,11 @@ import com.mongodb.reactivestreams.client.MongoClients;
public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoClient> public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoClient>
implements PersistenceExceptionTranslator { implements PersistenceExceptionTranslator {
private static final PersistenceExceptionTranslator DEFAULT_EXCEPTION_TRANSLATOR = new MongoExceptionTranslator();
private @Nullable String connectionString; private @Nullable String connectionString;
private @Nullable String host; private @Nullable String host;
private @Nullable Integer port; private @Nullable Integer port;
private @Nullable MongoClientSettings mongoClientSettings; private @Nullable MongoClientSettings mongoClientSettings;
private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_EXCEPTION_TRANSLATOR; private PersistenceExceptionTranslator exceptionTranslator = MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR;
/** /**
* Configures the host to connect to. * Configures the host to connect to.
@ -86,7 +84,13 @@ public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoCli
* @param exceptionTranslator * @param exceptionTranslator
*/ */
public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) { public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator == null ? DEFAULT_EXCEPTION_TRANSLATOR : exceptionTranslator; this.exceptionTranslator = exceptionTranslator == null ? MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR
: exceptionTranslator;
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
} }
@Override @Override
@ -123,8 +127,4 @@ public class ReactiveMongoClientFactoryBean extends AbstractFactoryBean<MongoCli
instance.close(); instance.close();
} }
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
} }

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java

@ -72,7 +72,7 @@ public class SimpleMongoClientDatabaseFactory extends MongoDatabaseFactorySuppor
* @param mongoInstanceCreated * @param mongoInstanceCreated
*/ */
SimpleMongoClientDatabaseFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) { SimpleMongoClientDatabaseFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) {
super(mongoClient, databaseName, mongoInstanceCreated, new MongoExceptionTranslator()); super(mongoClient, databaseName, mongoInstanceCreated, MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR);
} }
@Override @Override

26
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java

@ -51,8 +51,7 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
private final String databaseName; private final String databaseName;
private final boolean mongoInstanceCreated; private final boolean mongoInstanceCreated;
private final PersistenceExceptionTranslator exceptionTranslator; private PersistenceExceptionTranslator exceptionTranslator = MongoExceptionTranslator.DEFAULT_EXCEPTION_TRANSLATOR;
private @Nullable WriteConcern writeConcern; private @Nullable WriteConcern writeConcern;
/** /**
@ -85,7 +84,21 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
this.mongo = client; this.mongo = client;
this.databaseName = databaseName; this.databaseName = databaseName;
this.mongoInstanceCreated = mongoInstanceCreated; this.mongoInstanceCreated = mongoInstanceCreated;
this.exceptionTranslator = new MongoExceptionTranslator(); }
/**
* Configures the {@link PersistenceExceptionTranslator} to be used.
*
* @param exceptionTranslator the exception translator to set.
* @since 4.4
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
} }
/** /**
@ -97,10 +110,12 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
this.writeConcern = writeConcern; this.writeConcern = writeConcern;
} }
@Override
public Mono<MongoDatabase> getMongoDatabase() throws DataAccessException { public Mono<MongoDatabase> getMongoDatabase() throws DataAccessException {
return getMongoDatabase(databaseName); return getMongoDatabase(databaseName);
} }
@Override
public Mono<MongoDatabase> getMongoDatabase(String dbName) throws DataAccessException { public Mono<MongoDatabase> getMongoDatabase(String dbName) throws DataAccessException {
Assert.hasText(dbName, "Database name must not be empty"); Assert.hasText(dbName, "Database name must not be empty");
@ -118,6 +133,7 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
* *
* @see DisposableBean#destroy() * @see DisposableBean#destroy()
*/ */
@Override
public void destroy() throws Exception { public void destroy() throws Exception {
if (mongoInstanceCreated) { if (mongoInstanceCreated) {
@ -125,10 +141,6 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
} }
} }
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
@Override @Override
public CodecRegistry getCodecRegistry() { public CodecRegistry getCodecRegistry() {
return this.mongo.getDatabase(databaseName).getCodecRegistry(); return this.mongo.getDatabase(databaseName).getCodecRegistry();

61
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java

@ -128,7 +128,10 @@ public final class MongoDbErrorCodes {
clientSessionCodes.put(263, "OperationNotSupportedInTransaction"); clientSessionCodes.put(263, "OperationNotSupportedInTransaction");
clientSessionCodes.put(264, "TooManyLogicalSessions"); clientSessionCodes.put(264, "TooManyLogicalSessions");
errorCodes = new HashMap<>(); errorCodes = new HashMap<>(
dataAccessResourceFailureCodes.size() + dataIntegrityViolationCodes.size() + duplicateKeyCodes.size()
+ invalidDataAccessApiUsageException.size() + permissionDeniedCodes.size() + clientSessionCodes.size(),
1f);
errorCodes.putAll(dataAccessResourceFailureCodes); errorCodes.putAll(dataAccessResourceFailureCodes);
errorCodes.putAll(dataIntegrityViolationCodes); errorCodes.putAll(dataIntegrityViolationCodes);
errorCodes.putAll(duplicateKeyCodes); errorCodes.putAll(duplicateKeyCodes);
@ -149,12 +152,12 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isDataIntegrityViolationError(@Nullable Exception exception) { public static boolean isDataIntegrityViolationError(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException me) {
return isDataIntegrityViolationCode(((MongoException) exception).getCode()); return isDataIntegrityViolationCode(me.getCode());
} }
return false; return false;
} }
@ -166,12 +169,12 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isDataAccessResourceError(@Nullable Exception exception) { public static boolean isDataAccessResourceError(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException me) {
return isDataAccessResourceFailureCode(((MongoException) exception).getCode()); return isDataAccessResourceFailureCode(me.getCode());
} }
return false; return false;
} }
@ -183,12 +186,12 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isDuplicateKeyError(@Nullable Exception exception) { public static boolean isDuplicateKeyError(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException me) {
return isDuplicateKeyCode(((MongoException) exception).getCode()); return isDuplicateKeyCode(me.getCode());
} }
return false; return false;
} }
@ -196,14 +199,10 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isDataDuplicateKeyError(@Nullable Exception exception) { public static boolean isDataDuplicateKeyError(Exception exception) {
return isDuplicateKeyError(exception);
if(exception instanceof MongoException) {
return isDuplicateKeyCode(((MongoException) exception).getCode());
}
return false;
} }
public static boolean isPermissionDeniedCode(@Nullable Integer errorCode) { public static boolean isPermissionDeniedCode(@Nullable Integer errorCode) {
@ -213,11 +212,11 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isPermissionDeniedError(@Nullable Exception exception) { public static boolean isPermissionDeniedError(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException) {
return isPermissionDeniedCode(((MongoException) exception).getCode()); return isPermissionDeniedCode(((MongoException) exception).getCode());
} }
return false; return false;
@ -230,12 +229,12 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isInvalidDataAccessApiUsageError(@Nullable Exception exception) { public static boolean isInvalidDataAccessApiUsageError(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException me) {
return isInvalidDataAccessApiUsageCode(((MongoException) exception).getCode()); return isInvalidDataAccessApiUsageCode(me.getCode());
} }
return false; return false;
} }
@ -265,12 +264,12 @@ public final class MongoDbErrorCodes {
/** /**
* @param exception can be {@literal null}. * @param exception can be {@literal null}.
* @return * @return
* @since 3.3 * @since 4.4
*/ */
public static boolean isClientSessionFailure(@Nullable Exception exception) { public static boolean isClientSessionFailure(Exception exception) {
if(exception instanceof MongoException) { if (exception instanceof MongoException me) {
return isClientSessionFailureCode(((MongoException) exception).getCode()); return isClientSessionFailureCode(me.getCode());
} }
return false; return false;
} }

27
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java

@ -20,18 +20,16 @@ import static org.assertj.core.api.Assertions.*;
import org.bson.BsonDocument; import org.bson.BsonDocument;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.mongodb.ClientSessionException; import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.MongoTransactionException; import org.springframework.data.mongodb.MongoTransactionException;
import org.springframework.data.mongodb.TransientMongoDbException;
import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -41,9 +39,7 @@ import com.mongodb.MongoInternalException;
import com.mongodb.MongoSocketException; import com.mongodb.MongoSocketException;
import com.mongodb.MongoSocketReadTimeoutException; import com.mongodb.MongoSocketReadTimeoutException;
import com.mongodb.MongoSocketWriteException; import com.mongodb.MongoSocketWriteException;
import com.mongodb.MongoWriteException;
import com.mongodb.ServerAddress; import com.mongodb.ServerAddress;
import com.mongodb.WriteError;
/** /**
* Unit tests for {@link MongoExceptionTranslator}. * Unit tests for {@link MongoExceptionTranslator}.
@ -180,30 +176,21 @@ class MongoExceptionTranslatorUnitTests {
MongoException source = new MongoException(267, "PreparedTransactionInProgress"); MongoException source = new MongoException(267, "PreparedTransactionInProgress");
source.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); source.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL);
expectExceptionWithCauseMessage(translator.translateExceptionIfPossible(source), TransientMongoDbException.class, expectExceptionWithCauseMessage(translator.translateExceptionIfPossible(source),
UncategorizedMongoDbException.class,
"PreparedTransactionInProgress"); "PreparedTransactionInProgress");
assertThat(translator.isTransientFailure(source)).isTrue();
assertThat(translator.isTransientFailure(translator.translateExceptionIfPossible(source))).isTrue();
} }
@Test // DATAMONGO-2073 @Test // DATAMONGO-2073
public void translateMongoExceptionWithTransientLabelToTransientMongoDbException() { public void translateMongoExceptionWithTransientLabel() {
MongoException exception = new MongoException(0, ""); MongoException exception = new MongoException(0, "");
exception.addLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL); exception.addLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
DataAccessException translatedException = translator.translateExceptionIfPossible(exception); DataAccessException translatedException = translator.translateExceptionIfPossible(exception);
expectExceptionWithCauseMessage(translatedException, TransientMongoDbException.class); expectExceptionWithCauseMessage(translatedException, UncategorizedMongoDbException.class);
}
@Test // DATAMONGO-2073
public void wrapsTranslatedExceptionsWhenTransientLabelPresent() {
MongoException exception = new MongoWriteException(new WriteError(112, "WriteConflict", new BsonDocument()), null);
exception.addLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
DataAccessException translatedException = translator.translateExceptionIfPossible(exception);
assertThat(translatedException).isInstanceOf(TransientMongoDbException.class);
assertThat(translatedException.getCause()).isInstanceOf(DataIntegrityViolationException.class);
} }
private void checkTranslatedMongoException(Class<? extends Exception> clazz, int code) { private void checkTranslatedMongoException(Class<? extends Exception> clazz, int code) {

31
src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc

@ -31,7 +31,8 @@ The `execute` callbacks gives you a reference to either a `MongoCollection` or a
* `<T> T` *execute* `(String collectionName, CollectionCallback<T> action)`: Runs the given `CollectionCallback` on the collection of the given name. * `<T> T` *execute* `(String collectionName, CollectionCallback<T> action)`: Runs the given `CollectionCallback` on the collection of the given name.
* `<T> T` *execute* `(DbCallback<T> action)`: Runs a DbCallback, translating any exceptions as necessary. Spring Data MongoDB provides support for the Aggregation Framework introduced to MongoDB in version 2.2. * `<T> T` *execute* `(DbCallback<T> action)`: Runs a DbCallback, translating any exceptions as necessary.
Spring Data MongoDB provides support for the Aggregation Framework introduced to MongoDB in version 2.2.
* `<T> T` *execute* `(String collectionName, DbCallback<T> action)`: Runs a `DbCallback` on the collection of the given name translating any exceptions as necessary. * `<T> T` *execute* `(String collectionName, DbCallback<T> action)`: Runs a `DbCallback` on the collection of the given name translating any exceptions as necessary.
@ -90,6 +91,7 @@ List<Jedi> all = template.query(SWCharacter.class) <1>
.matching(query(where("jedi").is(true))) <4> .matching(query(where("jedi").is(true))) <4>
.all(); .all();
---- ----
<1> The type used to map fields used in the query to. <1> The type used to map fields used in the query to.
<2> The collection name to use if not defined on the domain type. <2> The collection name to use if not defined on the domain type.
<3> Result type if not using the original domain type. <3> Result type if not using the original domain type.
@ -107,9 +109,8 @@ Flux<Jedi> all = template.query(SWCharacter.class)
---- ----
====== ======
NOTE: Using projections allows `MongoTemplate` to optimize result mapping by limiting the actual response to fields required NOTE: Using projections allows `MongoTemplate` to optimize result mapping by limiting the actual response to fields required by the projection target type.
by the projection target type. This applies as long as the javadoc:org.springframework.data.mongodb.core.query.Query[] itself does not contain any field restriction and the This applies as long as the javadoc:org.springframework.data.mongodb.core.query.Query[] itself does not contain any field restriction and the target type is a closed interface or DTO projection.
target type is a closed interface or DTO projection.
WARNING: Projections must not be applied to xref:mongodb/mapping/document-references.adoc[DBRefs]. WARNING: Projections must not be applied to xref:mongodb/mapping/document-references.adoc[DBRefs].
@ -143,8 +144,8 @@ Flux<GeoResult<Jedi>> results = template.query(SWCharacter.class)
[[mongo-template.exception-translation]] [[mongo-template.exception-translation]]
== Exception Translation == Exception Translation
The Spring framework provides exception translation for a wide variety of database and mapping technologies. T The Spring framework provides exception translation for a wide variety of database and mapping technologies.
his has traditionally been for JDBC and JPA. This has traditionally been for JDBC and JPA.
The Spring support for MongoDB extends this feature to the MongoDB Database by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface. The Spring support for MongoDB extends this feature to the MongoDB Database by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface.
The motivation behind mapping to Spring's link:{springDocsUrl}/data-access.html#dao-exceptions[consistent data access exception hierarchy] is that you are then able to write portable and descriptive exception handling code without resorting to coding against MongoDB error codes. The motivation behind mapping to Spring's link:{springDocsUrl}/data-access.html#dao-exceptions[consistent data access exception hierarchy] is that you are then able to write portable and descriptive exception handling code without resorting to coding against MongoDB error codes.
@ -152,9 +153,25 @@ All of Spring's data access exceptions are inherited from the root `DataAccessEx
Note that not all exceptions thrown by the MongoDB driver inherit from the `MongoException` class. Note that not all exceptions thrown by the MongoDB driver inherit from the `MongoException` class.
The inner exception and message are preserved so that no information is lost. The inner exception and message are preserved so that no information is lost.
Some of the mappings performed by the `MongoExceptionTranslator` are `com.mongodb.Network to DataAccessResourceFailureException` and `MongoException` error codes 1003, 12001, 12010, 12011, and 12012 to `InvalidDataAccessApiUsageException`. Some of the mappings performed by the javadoc:org.springframework.data.mongodb.core.MongoExceptionTranslator[] are `com.mongodb.Network` to `DataAccessResourceFailureException` and `MongoException` error codes 1003, 12001, 12010, 12011, and 12012 to `InvalidDataAccessApiUsageException`.
Look into the implementation for more details on the mapping. Look into the implementation for more details on the mapping.
Exception Translation can be configured by setting a customized javadoc:org.springframework.data.mongodb.core.MongoExceptionTranslator[] on your `MongoDatabaseFactory` or its reactive variant.
You might also want to set the exception translator on the corresponding `MongoClientFactoryBean`.
.Configuring `MongoExceptionTranslator`
====
[source,java]
----
ConnectionString uri = new ConnectionString("mongodb://username:password@localhost/database");
SimpleMongoClientDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(uri);
mongoDbFactory.setExceptionTranslator(myCustomExceptionTranslator);
----
====
A motivation to customize exception can be MongoDB's behavior during transactions where some failures (such as write conflicts) can become transient and where a retry could lead to a successful operation.
In such a case, you could wrap exceptions with a specific MongoDB label and apply a different exception translation stragegy.
[[mongo-template.type-mapping]] [[mongo-template.type-mapping]]
== Domain Type Mapping == Domain Type Mapping

Loading…
Cancel
Save