DATAMONGO-2075 - Open up MongoTransactionManager to allow transaction commit customization and commit retry.

Original pull request: #606.
This commit is contained in:
Christoph Strobl
2018-08-29 14:57:17 +02:00
committed by Mark Paluch
parent a991b96518
commit 04e53316c6
2 changed files with 52 additions and 9 deletions

View File

@@ -47,13 +47,18 @@ import com.mongodb.client.ClientSession;
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
* {@link MongoDatabaseUtils#getDatabase(MongoDbFactory)} instead of a standard {@link MongoDbFactory#getDb()} call.
* Spring classes such as {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
* <p />
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override
* {@link #doCommit(MongoTransactionObject)} to implement the
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
* behaviour as outlined in the MongoDB reference manual.
*
* @author Christoph Strobl
* @author Mark Paluch
* @currentRead Shadow's Edge - Brent Weeks
* @since 2.1
* @see <a href="https://www.mongodb.com/transactions">MongoDB Transaction Documentation</a>
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
*/
public class MongoTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
@@ -181,7 +186,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus)
*/
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
protected final void doCommit(DefaultTransactionStatus status) throws TransactionException {
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(status);
@@ -191,14 +196,44 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
}
try {
mongoTransactionObject.commitTransaction();
} catch (MongoException ex) {
doCommit(mongoTransactionObject);
} catch (Exception ex) {
throw new TransactionSystemException(String.format("Could not commit Mongo transaction for session %s.",
debugString(mongoTransactionObject.getSession())), ex);
}
}
/**
* Customization hook to perform an actual commit of the given transaction.<br />
* If a commit operation encounters an error, the MongoDB driver throws a {@link MongoException} holding
* {@literal error labels}. <br />
* By default those labels are ignored, nevertheless one might check for
* {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the
* commit. <br />
* <code>
* <pre>
* int retries = 3;
* do {
* try {
* transactionObject.commitTransaction();
* break;
* } catch (MongoException ex) {
* if (!ex.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
* throw ex;
* }
* }
* Thread.sleep(500);
* } while (--retries > 0);
* </pre>
* </code>
*
* @param transactionObject never {@literal null}.
*/
protected void doCommit(MongoTransactionObject transactionObject) throws Exception {
transactionObject.commitTransaction();
}
/*
* (non-Javadoc)
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
@@ -386,7 +421,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
* @since 2.1
* @see MongoResourceHolder
*/
static class MongoTransactionObject implements SmartTransactionObject {
protected static class MongoTransactionObject implements SmartTransactionObject {
private @Nullable MongoResourceHolder resourceHolder;
@@ -406,7 +441,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
/**
* @return {@literal true} if a {@link MongoResourceHolder} is set.
*/
boolean hasResourceHolder() {
final boolean hasResourceHolder() {
return resourceHolder != null;
}
@@ -428,14 +463,14 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
/**
* Commit the transaction.
*/
void commitTransaction() {
public void commitTransaction() {
getRequiredSession().commitTransaction();
}
/**
* Rollback (abort) the transaction.
*/
void abortTransaction() {
public void abortTransaction() {
getRequiredSession().abortTransaction();
}
@@ -451,7 +486,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
}
@Nullable
ClientSession getSession() {
public ClientSession getSession() {
return resourceHolder != null ? resourceHolder.getSession() : null;
}

View File

@@ -315,6 +315,14 @@ MongoDB does *not* support collection operations, such as collection creation, w
affects the on the fly collection creation that happens on first usage. Therefore make sure to have all required
structures in place.
*Transient Errors*
MongoDB can add special labels to errors raised during a transactional execution. Those may indicate transient failures
that might vanish by simply retrying the operation.
We highly recommend https://github.com/spring-projects/spring-retry[Spring Retry] for those purposes. Nevertheless
one may override `MongoTransactionManager#doCommit(MongoTransactionObject)` to implement a https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation[Retry Commit Operation]
behaviour as outlined in the MongoDB reference manual.
*Count*
MongoDB `count` operates upon collection statistics which may not reflect the actual situation within a transaction.