Skip to content

Instantly share code, notes, and snippets.

@sepgh
Last active March 5, 2020 09:21
Show Gist options
  • Save sepgh/efc389f829c1ddaf922593b9d8256382 to your computer and use it in GitHub Desktop.
Save sepgh/efc389f829c1ddaf922593b9d8256382 to your computer and use it in GitHub Desktop.
Simple transaction manager with rollback.
com.github.gist.sepehrgh.transactions;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TransactionManager {
private List<Transaction> transactions = new ArrayList<>();
private volatile boolean started = false;
private final Lock rollbackLock = new ReentrantLock();
private boolean rollingBack = false;
private final Config config;
public TransactionManager(){
this(new Config());
}
public TransactionManager(Config config) {
this.config = config;
}
public synchronized TransactionManager addTransaction(Transaction transaction) throws TransactionAlreadyRunning {
if(started || rollingBack)
throw new TransactionAlreadyRunning();
transaction.setTransactionManager(this);
transactions.add(transaction);
return this;
}
public void commit(){
if(started || rollingBack)
return;
started = true;
for (Transaction transaction : transactions) {
if(rollingBack)
return;
try {
transaction.process();
}catch (RuntimeException e){
rollbackAll(transaction);
started = false;
throw e;
}
}
started = false;
}
private void rollbackAll(Transaction transaction){
boolean locked = rollbackLock.tryLock();
if(!locked)
return;
rollingBack = true;
int transactionIndex = transactions.indexOf(transaction);
for(int i = (transactionIndex - 1); i > -1; i--){
if(config.isIgnoreRollbackExceptions())
try {
transactions.get(i).onRollBack();
}catch (Exception ignored){}
else
transactions.get(i).onRollBack();
}
rollingBack = false;
}
static public abstract class Transaction {
private TransactionManager transactionManager;
public abstract void process();
public abstract void onRollBack();
protected final void rollback(){
transactionManager.rollbackAll(this);
}
private void setTransactionManager(TransactionManager transactionManager){
this.transactionManager = transactionManager;
}
}
public class TransactionAlreadyRunning extends Exception {
public TransactionAlreadyRunning() {
super("You cant add another transaction to already running transaction manager");
}
}
public static class Config {
private boolean ignoreRollbackExceptions = false;
public Config setIgnoreRollbackExceptions(boolean ignoreRollbackExceptions) {
this.ignoreRollbackExceptions = ignoreRollbackExceptions;
return this;
}
public boolean isIgnoreRollbackExceptions() {
return ignoreRollbackExceptions;
}
}
public static void main(String[] args) throws TransactionAlreadyRunning {
TransactionManager transactionManager = new TransactionManager();
final int[] i = {0};
transactionManager.addTransaction(new Transaction() {
@Override
public void process() {
i[0]++;
}
@Override
public void onRollBack() {
i[0]--;
}
});
transactionManager.addTransaction(new Transaction() {
@Override
public void process() {
i[0] += 5;
}
@Override
public void onRollBack() {
i[0] -= 5;
}
});
transactionManager.addTransaction(new Transaction() {
@Override
public void process() {
throw new RuntimeException("Error and rollback");
}
@Override
public void onRollBack() {
i[0] -= 5;
}
});
try {
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("Final value of i is " + i[0]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment