StalactitePlatformTransactionManager.java
package org.codefilarete.stalactite.spring.transaction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Set;
import org.codefilarete.stalactite.sql.CommitListener;
import org.codefilarete.stalactite.sql.ConnectionConfiguration;
import org.codefilarete.stalactite.sql.RollbackListener;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
/**
* A {@link org.springframework.transaction.TransactionManager} appropriate for Stalactite.
* It creates a bridge between Spring {@link org.springframework.transaction.TransactionManager} and Stalactite
* {@link org.codefilarete.stalactite.sql.ConnectionProvider} to make the latter uses transactions managed by Spring :
* the only thing to do is to get the {@link Connection} from {@link TransactionSynchronizationManager}. Rollbacks and
* commits are managed Spring (Stalactite never manages transaction).
* <p>
* It inherits from {@link JdbcTransactionManager} because Stalactite doesn't require much more than a {@link DataSource}
* to run.
*
* @author Guillaume Mary
*/
public class StalactitePlatformTransactionManager extends JdbcTransactionManager implements ConnectionConfiguration.TransactionalConnectionProvider {
private final Set<CommitListener> commitListeners = new KeepOrderSet<>();
private final Set<RollbackListener> rollbackListeners = new KeepOrderSet<>();
public StalactitePlatformTransactionManager(DataSource dataSource) {
super(dataSource);
}
@Override
public void addCommitListener(CommitListener commitListener) {
this.commitListeners.add(commitListener);
}
@Override
public void addRollbackListener(RollbackListener rollbackListener) {
this.rollbackListeners.add(rollbackListener);
}
/**
* Implemented to make it get the connection from current transaction given by Spring {@link TransactionSynchronizationManager}
*
* @return the current {@link Connection} available in Spring transaction context.
*/
@Override
public Connection giveConnection() {
if (!TransactionSynchronizationManager.isActualTransactionActive()) {
throw new IllegalStateException("No active transaction");
} else {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(getDataSource());
if (resource == null) {
throw new IllegalStateException("No connection available");
} else {
return resource.getConnection();
}
}
}
@Override
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewSynchronization()) {
this.commitListeners.forEach(commitListener -> TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void beforeCommit(boolean readOnly) {
commitListener.beforeCommit();
}
@Override
public void afterCommit() {
commitListener.afterCommit();
}
}));
this.rollbackListeners.forEach(rollbackListener -> TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
rollbackListener.afterRollback();
}
}
// Note that we can't implement a call to beforeRollback() because TransactionSynchronization doesn't propose it
// The closest method would be beforeCompletion() but it is also invoked on beforeCommit()
}));
}
}
/**
* Implemented to ask for a new transaction to Spring context and run given {@link org.codefilarete.stalactite.engine.SeparateTransactionExecutor.JdbcOperation}
* in it.
* @param jdbcOperation a sql operation that will call {@link #giveConnection()} to execute its statements.
*/
@Override
public void executeInNewTransaction(JdbcOperation jdbcOperation) {
TransactionTemplate transactionTemplate = new TransactionTemplate(this);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcOperation.execute(giveConnection());
}
});
}
}