DATACMNS-310 - Integrated ChainedTransactionManager from Spring Data Neo4j.
We now integrate a best-effort PlatformTransactionManager that will propagate commits and rollbacks to all PlatformTransactionManager instances registered. This assumes that the exceptional cases which will cause a rollback to happen either before the commits or during the first commit.
This commit is contained in:
committed by
Oliver Gierke
parent
a93c2bff94
commit
a8b912c09f
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2011-2013 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.transaction;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.HeuristicCompletionException;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.UnexpectedRollbackException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link PlatformTransactionManager} implementation that orchestrates transaction creation, commits and rollbacks to a
|
||||
* list of delegates. Using this implementation assumes that errors causing a transaction rollback will usually happen
|
||||
* before the transaction completion or during the commit of the most inner {@link PlatformTransactionManager}.
|
||||
* <p />
|
||||
* The configured instances will start transactions in the order given and commit/rollback in <em>reverse</em> order,
|
||||
* which means the {@link PlatformTransactionManager} most likely to break the transaction should be the <em>last</em>
|
||||
* in the list configured. A {@link PlatformTransactionManager} throwing an exception during commit will automatically
|
||||
* cause the remaining transaction managers to roll back instead of committing.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
public class ChainedTransactionManager implements PlatformTransactionManager {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(ChainedTransactionManager.class);
|
||||
|
||||
private final List<PlatformTransactionManager> transactionManagers;
|
||||
private final SynchronizationManager synchronizationManager;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ChainedTransactionManager} delegating to the given {@link PlatformTransactionManager}s.
|
||||
*
|
||||
* @param transactionManagers must not be {@literal null} or empty.
|
||||
*/
|
||||
public ChainedTransactionManager(PlatformTransactionManager... transactionManagers) {
|
||||
this(SpringTransactionSynchronizationManager.INSTANCE, transactionManagers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ChainedTransactionManager} using the given {@link SynchronizationManager} and
|
||||
* {@link PlatformTransactionManager}s.
|
||||
*
|
||||
* @param synchronizationManager must not be {@literal null}.
|
||||
* @param transactionManagers must not be {@literal null} or empty.
|
||||
*/
|
||||
ChainedTransactionManager(SynchronizationManager synchronizationManager,
|
||||
PlatformTransactionManager... transactionManagers) {
|
||||
|
||||
Assert.notNull(synchronizationManager, "SynchronizationManager must not be null!");
|
||||
Assert.notNull(transactionManagers, "Transaction managers must not be null!");
|
||||
Assert.isTrue(transactionManagers.length > 0, "At least one PlatformTransactionManager must be given!");
|
||||
|
||||
this.synchronizationManager = synchronizationManager;
|
||||
this.transactionManagers = asList(transactionManagers);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.PlatformTransactionManager#getTransaction(org.springframework.transaction.TransactionDefinition)
|
||||
*/
|
||||
public MultiTransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
|
||||
|
||||
MultiTransactionStatus mts = new MultiTransactionStatus(transactionManagers.get(0));
|
||||
|
||||
if (!synchronizationManager.isSynchronizationActive()) {
|
||||
synchronizationManager.initSynchronization();
|
||||
mts.setNewSynchonization();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
for (PlatformTransactionManager transactionManager : transactionManagers) {
|
||||
mts.registerTransactionManager(definition, transactionManager);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
|
||||
Map<PlatformTransactionManager, TransactionStatus> transactionStatuses = mts.getTransactionStatuses();
|
||||
|
||||
for (PlatformTransactionManager transactionManager : transactionManagers) {
|
||||
try {
|
||||
if (transactionStatuses.get(transactionManager) != null) {
|
||||
transactionManager.rollback(transactionStatuses.get(transactionManager));
|
||||
}
|
||||
} catch (Exception ex2) {
|
||||
LOGGER.warn("Rollback exception (" + transactionManager + ") " + ex2.getMessage(), ex2);
|
||||
}
|
||||
}
|
||||
|
||||
if (mts.isNewSynchonization()) {
|
||||
synchronizationManager.clearSynchronization();
|
||||
}
|
||||
|
||||
throw new CannotCreateTransactionException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return mts;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.PlatformTransactionManager#commit(org.springframework.transaction.TransactionStatus)
|
||||
*/
|
||||
public void commit(TransactionStatus status) throws TransactionException {
|
||||
|
||||
MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
|
||||
|
||||
boolean commit = true;
|
||||
Exception commitException = null;
|
||||
PlatformTransactionManager commitExceptionTransactionManager = null;
|
||||
|
||||
for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
|
||||
|
||||
if (commit) {
|
||||
|
||||
try {
|
||||
multiTransactionStatus.commit(transactionManager);
|
||||
} catch (Exception ex) {
|
||||
commit = false;
|
||||
commitException = ex;
|
||||
commitExceptionTransactionManager = transactionManager;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// after unsucessfull commit we must try to rollback remaining transaction managers
|
||||
|
||||
try {
|
||||
multiTransactionStatus.rollback(transactionManager);
|
||||
} catch (Exception ex) {
|
||||
LOGGER.warn("Rollback exception (after commit) (" + transactionManager + ") " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (multiTransactionStatus.isNewSynchonization()) {
|
||||
synchronizationManager.clearSynchronization();
|
||||
}
|
||||
|
||||
if (commitException != null) {
|
||||
boolean firstTransactionManagerFailed = commitExceptionTransactionManager == getLastTransactionManager();
|
||||
int transactionState = firstTransactionManagerFailed ? HeuristicCompletionException.STATE_ROLLED_BACK
|
||||
: HeuristicCompletionException.STATE_MIXED;
|
||||
throw new HeuristicCompletionException(transactionState, commitException);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.PlatformTransactionManager#rollback(org.springframework.transaction.TransactionStatus)
|
||||
*/
|
||||
public void rollback(TransactionStatus status) throws TransactionException {
|
||||
|
||||
Exception rollbackException = null;
|
||||
PlatformTransactionManager rollbackExceptionTransactionManager = null;
|
||||
|
||||
MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
|
||||
|
||||
for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
|
||||
try {
|
||||
multiTransactionStatus.rollback(transactionManager);
|
||||
} catch (Exception ex) {
|
||||
if (rollbackException == null) {
|
||||
rollbackException = ex;
|
||||
rollbackExceptionTransactionManager = transactionManager;
|
||||
} else {
|
||||
LOGGER.warn("Rollback exception (" + transactionManager + ") " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (multiTransactionStatus.isNewSynchonization()) {
|
||||
synchronizationManager.clearSynchronization();
|
||||
}
|
||||
|
||||
if (rollbackException != null) {
|
||||
throw new UnexpectedRollbackException("Rollback exception, originated at (" + rollbackExceptionTransactionManager
|
||||
+ ") " + rollbackException.getMessage(), rollbackException);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> Iterable<T> reverse(Collection<T> collection) {
|
||||
|
||||
List<T> list = new ArrayList<T>(collection);
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private PlatformTransactionManager getLastTransactionManager() {
|
||||
return transactionManagers.get(lastTransactionManagerIndex());
|
||||
}
|
||||
|
||||
private int lastTransactionManagerIndex() {
|
||||
return transactionManagers.size() - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 2011-2013 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.transaction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link TransactionStatus} implementation to orchestrate {@link TransactionStatus} instances for multiple
|
||||
* {@link PlatformTransactionManager} instances.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
class MultiTransactionStatus implements TransactionStatus {
|
||||
|
||||
private final PlatformTransactionManager mainTransactionManager;
|
||||
private final Map<PlatformTransactionManager, TransactionStatus> transactionStatuses = Collections
|
||||
.synchronizedMap(new HashMap<PlatformTransactionManager, TransactionStatus>());
|
||||
|
||||
private boolean newSynchonization;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MultiTransactionStatus} for the given {@link PlatformTransactionManager}.
|
||||
*
|
||||
* @param mainTransactionManager must not be {@literal null}.
|
||||
*/
|
||||
public MultiTransactionStatus(PlatformTransactionManager mainTransactionManager) {
|
||||
|
||||
Assert.notNull(mainTransactionManager, "TransactionManager must not be null!");
|
||||
this.mainTransactionManager = mainTransactionManager;
|
||||
}
|
||||
|
||||
public Map<PlatformTransactionManager, TransactionStatus> getTransactionStatuses() {
|
||||
return transactionStatuses;
|
||||
}
|
||||
|
||||
public void setNewSynchonization() {
|
||||
this.newSynchonization = true;
|
||||
}
|
||||
|
||||
public boolean isNewSynchonization() {
|
||||
return newSynchonization;
|
||||
}
|
||||
|
||||
public void registerTransactionManager(TransactionDefinition definition, PlatformTransactionManager transactionManager) {
|
||||
getTransactionStatuses().put(transactionManager, transactionManager.getTransaction(definition));
|
||||
}
|
||||
|
||||
public void commit(PlatformTransactionManager transactionManager) {
|
||||
TransactionStatus transactionStatus = getTransactionStatus(transactionManager);
|
||||
transactionManager.commit(transactionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back the {@link TransactionStatus} registered for the given {@link PlatformTransactionManager}.
|
||||
*
|
||||
* @param transactionManager must not be {@literal null}.
|
||||
*/
|
||||
public void rollback(PlatformTransactionManager transactionManager) {
|
||||
transactionManager.rollback(getTransactionStatus(transactionManager));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
|
||||
*/
|
||||
public boolean isRollbackOnly() {
|
||||
return getMainTransactionStatus().isRollbackOnly();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#isCompleted()
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return getMainTransactionStatus().isCompleted();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#isNewTransaction()
|
||||
*/
|
||||
public boolean isNewTransaction() {
|
||||
return getMainTransactionStatus().isNewTransaction();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#hasSavepoint()
|
||||
*/
|
||||
public boolean hasSavepoint() {
|
||||
return getMainTransactionStatus().hasSavepoint();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#setRollbackOnly()
|
||||
*/
|
||||
public void setRollbackOnly() {
|
||||
for (TransactionStatus ts : transactionStatuses.values()) {
|
||||
ts.setRollbackOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.SavepointManager#createSavepoint()
|
||||
*/
|
||||
public Object createSavepoint() throws TransactionException {
|
||||
|
||||
SavePoints savePoints = new SavePoints();
|
||||
|
||||
for (TransactionStatus transactionStatus : transactionStatuses.values()) {
|
||||
savePoints.save(transactionStatus);
|
||||
}
|
||||
return savePoints;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.SavepointManager#rollbackToSavepoint(java.lang.Object)
|
||||
*/
|
||||
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
|
||||
SavePoints savePoints = (SavePoints) savepoint;
|
||||
savePoints.rollback();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.SavepointManager#releaseSavepoint(java.lang.Object)
|
||||
*/
|
||||
public void releaseSavepoint(Object savepoint) throws TransactionException {
|
||||
((SavePoints) savepoint).release();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.TransactionStatus#flush()
|
||||
*/
|
||||
public void flush() {
|
||||
for (TransactionStatus transactionStatus : transactionStatuses.values()) {
|
||||
transactionStatus.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionStatus getMainTransactionStatus() {
|
||||
return transactionStatuses.get(mainTransactionManager);
|
||||
}
|
||||
|
||||
private TransactionStatus getTransactionStatus(PlatformTransactionManager transactionManager) {
|
||||
return this.getTransactionStatuses().get(transactionManager);
|
||||
}
|
||||
|
||||
private static class SavePoints {
|
||||
|
||||
private final Map<TransactionStatus, Object> savepoints = new HashMap<TransactionStatus, Object>();
|
||||
|
||||
private void addSavePoint(TransactionStatus status, Object savepoint) {
|
||||
|
||||
Assert.notNull(status, "TransactionStatus must not be null!");
|
||||
this.savepoints.put(status, savepoint);
|
||||
}
|
||||
|
||||
private void save(TransactionStatus transactionStatus) {
|
||||
Object savepoint = transactionStatus.createSavepoint();
|
||||
addSavePoint(transactionStatus, savepoint);
|
||||
}
|
||||
|
||||
public void rollback() {
|
||||
for (TransactionStatus transactionStatus : savepoints.keySet()) {
|
||||
transactionStatus.rollbackToSavepoint(savepointFor(transactionStatus));
|
||||
}
|
||||
}
|
||||
|
||||
private Object savepointFor(TransactionStatus transactionStatus) {
|
||||
return savepoints.get(transactionStatus);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
for (TransactionStatus transactionStatus : savepoints.keySet()) {
|
||||
transactionStatus.releaseSavepoint(savepointFor(transactionStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2011-2013 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.transaction;
|
||||
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
/**
|
||||
* {@link SynchronizationManager} delegating calls to Spring's {@link TransactionSynchronizationManager}.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @author Oliver Gierke
|
||||
* @since 1.6
|
||||
*/
|
||||
enum SpringTransactionSynchronizationManager implements SynchronizationManager {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.transaction.SynchronizationManager#initSynchronization()
|
||||
*/
|
||||
public void initSynchronization() {
|
||||
TransactionSynchronizationManager.initSynchronization();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.transaction.SynchronizationManager#isSynchronizationActive()
|
||||
*/
|
||||
public boolean isSynchronizationActive() {
|
||||
return TransactionSynchronizationManager.isSynchronizationActive();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.transaction.SynchronizationManager#clearSynchronization()
|
||||
*/
|
||||
public void clearSynchronization() {
|
||||
TransactionSynchronizationManager.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2011-2013 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.transaction;
|
||||
|
||||
/**
|
||||
* Strategy interface to allow providing a dedicated synchronization mechanism.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @author Oliver Gierke
|
||||
* @see SpringTransactionSynchronizationManager
|
||||
* @since 1.6
|
||||
*/
|
||||
interface SynchronizationManager {
|
||||
|
||||
void initSynchronization();
|
||||
|
||||
boolean isSynchronizationActive();
|
||||
|
||||
void clearSynchronization();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Contains advanced support for transactions, e.g. a best-effort delegating transaction manager.
|
||||
*
|
||||
* @see org.springframework.data.transaction.ChainedTransactionManager
|
||||
*/
|
||||
package org.springframework.data.transaction;
|
||||
Reference in New Issue
Block a user