You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@empire-db.apache.org by do...@apache.org on 2022/01/21 23:06:15 UTC

[empire-db] branch version3 updated: EMPIREDB-364 new class DBRollbackManager

This is an automated email from the ASF dual-hosted git repository.

doebele pushed a commit to branch version3
in repository https://gitbox.apache.org/repos/asf/empire-db.git


The following commit(s) were added to refs/heads/version3 by this push:
     new e8337ff  EMPIREDB-364 new class DBRollbackManager
e8337ff is described below

commit e8337ff2af837489eeba313dfb4fee7822d0e18e
Author: Rainer Döbele <do...@apache.org>
AuthorDate: Sat Jan 22 00:06:13 2022 +0100

    EMPIREDB-364 new class DBRollbackManager
---
 .../org/apache/empire/samples/db/SampleApp.java    |   2 -
 .../websample/web/pages/EmployeeDetailPage.java    |   7 +-
 .../org/apache/empire/rest/service/Service.java    |  13 ++-
 .../org/apache/empire/jsf2/app/WebApplication.java | 114 ++++++++++-----------
 .../org/apache/empire/jsf2/app/WebDBContext.java   |  72 ++++---------
 .../apache/empire/db/context/DBContextBase.java    | 111 ++++++++++----------
 .../apache/empire/db/context/DBContextStatic.java  |  32 ++++--
 .../empire/db/context/DBRollbackManager.java       | 100 ++++++++++++++++++
 8 files changed, 270 insertions(+), 181 deletions(-)

diff --git a/empire-db-examples/empire-db-example-basic/src/main/java/org/apache/empire/samples/db/SampleApp.java b/empire-db-examples/empire-db-example-basic/src/main/java/org/apache/empire/samples/db/SampleApp.java
index 8d7d90a..edd76a4 100644
--- a/empire-db-examples/empire-db-example-basic/src/main/java/org/apache/empire/samples/db/SampleApp.java
+++ b/empire-db-examples/empire-db-example-basic/src/main/java/org/apache/empire/samples/db/SampleApp.java
@@ -135,11 +135,9 @@ public class SampleApp
 			// commit
 			context.commit();
 			
-			/*
             int idEmp = testTransactionCreate(idDevDep);
             testTransactionUpdate(idEmp);
             testTransactionDelete(idEmp);
-            */
 
 			// STEP 7: Update Records (by setting the phone Number)
 			System.out.println("*** Step 7: updateEmployee() ***");
diff --git a/empire-db-examples/empire-db-example-jsf2/src/main/java/org/apache/empire/jsf2/websample/web/pages/EmployeeDetailPage.java b/empire-db-examples/empire-db-example-jsf2/src/main/java/org/apache/empire/jsf2/websample/web/pages/EmployeeDetailPage.java
index 7bcf4f1..a6682db 100644
--- a/empire-db-examples/empire-db-example-jsf2/src/main/java/org/apache/empire/jsf2/websample/web/pages/EmployeeDetailPage.java
+++ b/empire-db-examples/empire-db-example-jsf2/src/main/java/org/apache/empire/jsf2/websample/web/pages/EmployeeDetailPage.java
@@ -18,8 +18,10 @@
  */
 package org.apache.empire.jsf2.websample.web.pages;
 
+import org.apache.empire.exceptions.MiscellaneousErrorException;
 import org.apache.empire.jsf2.pageelements.RecordPageElement;
 import org.apache.empire.jsf2.pages.PageOutcome;
+import org.apache.empire.jsf2.websample.db.SampleDB;
 import org.apache.empire.jsf2.websample.db.records.EmployeeRecord;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -101,11 +103,12 @@ public class EmployeeDetailPage extends SamplePage
 
         getEmployeeRecord().update();
         
-        /* test transaction  
+        getEmployeeRecord().getContext().getConnection();
+        
+        /* test transaction */  
         SampleDB db = this.getDatabase();
         if (getEmployeeRecord().isNull(db.T_EMPLOYEES.PHONE_NUMBER))
             throw new MiscellaneousErrorException("Phone number must not be empty!");
-        */
         
         return getParentOutcome(true);
     }
diff --git a/empire-db-examples/empire-db-example-vue/src/main/java/org/apache/empire/rest/service/Service.java b/empire-db-examples/empire-db-example-vue/src/main/java/org/apache/empire/rest/service/Service.java
index 2116e71..b263e09 100644
--- a/empire-db-examples/empire-db-example-vue/src/main/java/org/apache/empire/rest/service/Service.java
+++ b/empire-db-examples/empire-db-example-vue/src/main/java/org/apache/empire/rest/service/Service.java
@@ -31,6 +31,7 @@ import javax.ws.rs.core.Response;
 
 import org.apache.empire.db.DBDatabaseDriver;
 import org.apache.empire.db.context.DBContextBase;
+import org.apache.empire.db.context.DBRollbackManager;
 import org.apache.empire.exceptions.EmpireException;
 import org.apache.empire.rest.app.SampleServiceApp;
 import org.apache.empire.rest.app.TextResolver;
@@ -90,15 +91,21 @@ public abstract class Service {
         }
 
         @Override
-        public Connection getConnection()
+        public TextResolver getTextResolver()
+        {
+            return textResolver;
+        }
+
+        @Override
+        protected Connection getConnection(boolean required)
         {
             return conn;
         }
 
         @Override
-        public TextResolver getTextResolver()
+        protected DBRollbackManager getRollbackManager(boolean required)
         {
-            return textResolver;
+            return null; /* No Rollbacks allowed */
         }
     }
     
diff --git a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebApplication.java b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebApplication.java
index 9956457..31c0ba0 100644
--- a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebApplication.java
+++ b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebApplication.java
@@ -20,10 +20,8 @@ package org.apache.empire.jsf2.app;
 
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.ResourceBundle;
@@ -44,6 +42,8 @@ import javax.sql.DataSource;
 import org.apache.empire.commons.StringUtils;
 import org.apache.empire.data.DataType;
 import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.context.DBRollbackManager;
+import org.apache.empire.db.context.DBRollbackManager.ReleaseAction;
 import org.apache.empire.exceptions.InternalException;
 import org.apache.empire.exceptions.InvalidArgumentException;
 import org.apache.empire.exceptions.NotSupportedException;
@@ -59,7 +59,9 @@ public abstract class WebApplication
 {
     private static final Logger log                   = LoggerFactory.getLogger(WebApplication.class);
     
-    private static final String CONNECTION_CONTEXT_INFO_MAP = "CONNECTION_CONTEXT_INFO_MAP";
+    private static final String REQUEST_CONNECTION_MAP = "requestConnectionMap";
+
+    private static final String CONN_ROLLBACK_MANAGER = "connRollbackManager";
     
     public static String        APPLICATION_BEAN_NAME = "webApplication";
 
@@ -455,55 +457,48 @@ public abstract class WebApplication
         }
     }
 
-    /**
-     * Internally used to manage Connections and Contexts
-     */
-    private static class ConnectionContextInfo
+    public DBRollbackManager getRollbackManagerForRequest(FacesContext fc, boolean create)
     {
-        ConnectionContextInfo(Connection conn, WebDBContext<? extends DBDatabase> ctx)
-        {
-            this.connection = conn;
-            ctxList.add(ctx);
+        DBRollbackManager dbrm = (DBRollbackManager)FacesUtils.getRequestAttribute(fc, CONN_ROLLBACK_MANAGER);
+        if (dbrm==null && create)
+        {   dbrm = new DBRollbackManager(1, 8);
+            FacesUtils.setRequestAttribute(fc, CONN_ROLLBACK_MANAGER, dbrm);
         }
-        public Connection connection;
-        public final List<WebDBContext<? extends DBDatabase>> ctxList = new ArrayList<WebDBContext<? extends DBDatabase>>(1);
+        return dbrm;
     }
-
+    
     /**
      * Obtains a connection for the current request
      * A WebDBContext must be provided which must store the connection util releaseConnection is called
      */
-    public Connection getConnectionForRequest(FacesContext fc, WebDBContext<? extends DBDatabase> context)
+    public Connection getConnectionForRequest(FacesContext fc, DBDatabase db, boolean create)
     {
         if (fc == null)
             throw new InvalidArgumentException("FacesContext", fc);
-        if (context == null)
-            throw new InvalidArgumentException("WebDBContext", context);
+        if (db == null)
+            throw new InvalidArgumentException("DBDatabase", db);
         // Get the ConnectionContextInfo map
         @SuppressWarnings("unchecked")
-        Map<DBDatabase, ConnectionContextInfo> cciMap = (Map<DBDatabase, ConnectionContextInfo>) FacesUtils.getRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP);
-        if (cciMap== null)
-        {   cciMap = new HashMap<DBDatabase, ConnectionContextInfo>();
-            FacesUtils.setRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP, cciMap);
+        Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+        if (connMap== null && create)
+        {   connMap = new HashMap<DBDatabase, Connection>(1);
+            FacesUtils.setRequestAttribute(fc, REQUEST_CONNECTION_MAP, connMap);
+        }
+        else if (connMap==null)
+        {   // Nothing to do
+            return null; 
         }
-        DBDatabase db = context.getDatabase();
-        ConnectionContextInfo cci = cciMap.get(db); 
-        if (cci==null)
+        Connection conn = connMap.get(db); 
+        if (conn==null && create)
         {   // Get Pooled Connection
-            Connection conn = getConnection(db);
+            conn = getConnection(db);
             if (conn== null)
                 throw new UnexpectedReturnValueException(this, "getConnection"); 
             // Add to map
-            cci = new ConnectionContextInfo(conn, context);
-            cciMap.put(db, cci);
-        }
-        else
-        {   // add context
-            if (cci.ctxList.contains(context)==false)
-                cci.ctxList.add(context);
+            connMap.put(db, conn);
         }
         // done
-        return cci.connection;
+        return conn;
     }
 
     /**
@@ -514,20 +509,22 @@ public abstract class WebApplication
     public void releaseAllConnections(final FacesContext fc, boolean commit)
     {
         @SuppressWarnings("unchecked")
-        Map<DBDatabase, ConnectionContextInfo> cciMap = (Map<DBDatabase, ConnectionContextInfo>) FacesUtils.getRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP);
-        if (cciMap != null)
-        {   // Walk the connection map
-            for (Map.Entry<DBDatabase, ConnectionContextInfo> e : cciMap.entrySet())
-            {
-                ConnectionContextInfo cci = e.getValue();
-                releaseConnection(e.getKey(), cci.connection, commit);
-                // release connection
-                for (WebDBContext<? extends DBDatabase> ctx : cci.ctxList)
-                    ctx.releaseConnection(commit);
-            }
-            // remove from request map
-            FacesUtils.setRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP, null);
+        Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+        if (connMap == null)
+            return; // Nothing to do
+        // Walk the connection map
+        DBRollbackManager dbrm = getRollbackManagerForRequest(fc, false);
+        ReleaseAction action = (commit ? ReleaseAction.Discard : ReleaseAction.Rollback);
+        for (Map.Entry<DBDatabase, Connection> e : connMap.entrySet())
+        {
+            Connection conn = e.getValue();
+            releaseConnection(e.getKey(), conn, commit);
+            // release connection
+            if (dbrm!=null)
+                dbrm.releaseConnection(conn, action);
         }
+        // remove from request map
+        FacesUtils.setRequestAttribute(fc, REQUEST_CONNECTION_MAP, null);
     }
 
     /**
@@ -549,19 +546,18 @@ public abstract class WebApplication
     public void releaseConnection(final FacesContext fc, DBDatabase db, boolean commit)
     {
         @SuppressWarnings("unchecked")
-        Map<DBDatabase, ConnectionContextInfo> cciMap = (Map<DBDatabase, ConnectionContextInfo>) FacesUtils.getRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP);
-        if (cciMap != null && cciMap.containsKey(db))
-        {   
-            ConnectionContextInfo cci = cciMap.get(db);
-            releaseConnection(db, cci.connection, commit);
-            // release connection
-            for (WebDBContext<? extends DBDatabase> ctx : cci.ctxList)
-                ctx.releaseConnection(commit);
-            // Walk the connection map
-            cciMap.remove(db);
-            if (cciMap.isEmpty())
-                FacesUtils.setRequestAttribute(fc, CONNECTION_CONTEXT_INFO_MAP, null);
-        }
+        Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+        if (connMap == null || !connMap.containsKey(db))
+            return; // Nothing to do;
+        // Release Connection   
+        Connection conn = connMap.get(db);
+        releaseConnection(db, conn, commit);
+        // release connection
+        DBRollbackManager dbrm = getRollbackManagerForRequest(fc, false);
+        if (dbrm!=null)
+            dbrm.releaseConnection(conn, (commit ? ReleaseAction.Discard : ReleaseAction.Rollback));
+        // Remove from map
+        connMap.remove(db);
     }
 
     /**
diff --git a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebDBContext.java b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebDBContext.java
index b383ab7..6dfd633 100644
--- a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebDBContext.java
+++ b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/app/WebDBContext.java
@@ -7,6 +7,7 @@ import javax.faces.context.FacesContext;
 import org.apache.empire.db.DBDatabase;
 import org.apache.empire.db.DBDatabaseDriver;
 import org.apache.empire.db.context.DBContextBase;
+import org.apache.empire.db.context.DBRollbackManager;
 import org.apache.empire.exceptions.NotSupportedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -21,17 +22,16 @@ import org.slf4j.LoggerFactory;
  */
 public class WebDBContext<DB extends DBDatabase> extends DBContextBase
 {
-    private static final Logger  log  = LoggerFactory.getLogger(WebDBContext.class);
+    private static final Logger    log = LoggerFactory.getLogger(WebDBContext.class);
 
-    private final WebApplication app;
-    private final DB             database;
-
-    // Held for request only
-    private Connection           conn;
+    private final WebApplication   app;
+    private final DBDatabaseDriver driver;
+    private final DB               database;
 
     public WebDBContext(WebApplication app, DB db)
     {
         this.app = app;
+        this.driver = db.getDriver();
         this.database = db;
         // check driver
         if (db.getDriver() == null)
@@ -46,50 +46,7 @@ public class WebDBContext<DB extends DBDatabase> extends DBContextBase
     @Override
     public DBDatabaseDriver getDriver()
     {
-        return database.getDriver();
-    }
-
-    /**
-     * gets a Connection for the current request
-     * IMPORTANT: Do not hold the connection!
-     */
-    @Override
-    public Connection getConnection()
-    {
-        if (conn==null)
-        {   // get a new connection
-            FacesContext fc = FacesContext.getCurrentInstance();
-            conn = this.app.getConnectionForRequest(fc, this);
-        }
-        return conn;
-    }
-
-    @Override
-    public void commit()
-    {
-        if (conn!=null)
-            super.commit();
-        else
-            log.info("No Connection to commmit changes");
-    }
-
-    @Override
-    public void rollback()
-    {
-        if (conn!=null)
-            super.rollback();
-        else
-            log.info("No Connection to rollback changes");
-    }
-    
-    public void releaseConnection(boolean commitPerformed)
-    {
-        this.conn = null;
-        // commit or rollback?
-        if (commitPerformed)
-            discardAllHandlers();
-        else
-            rollbackAllHandlers();
+        return driver;
     }
 
     /**
@@ -107,4 +64,19 @@ public class WebDBContext<DB extends DBDatabase> extends DBContextBase
         */
         throw new NotSupportedException(this, "discard");
     }
+
+    @Override
+    protected Connection getConnection(boolean create)
+    {
+        FacesContext fc = FacesContext.getCurrentInstance();
+        return this.app.getConnectionForRequest(fc, database, create);
+    }
+
+    @Override
+    protected DBRollbackManager getRollbackManager(boolean create)
+    {
+        FacesContext fc = FacesContext.getCurrentInstance();
+        return this.app.getRollbackManagerForRequest(fc, create);
+    }
+    
 }
diff --git a/empire-db/src/main/java/org/apache/empire/db/context/DBContextBase.java b/empire-db/src/main/java/org/apache/empire/db/context/DBContextBase.java
index bd15277..2e66b75 100644
--- a/empire-db/src/main/java/org/apache/empire/db/context/DBContextBase.java
+++ b/empire-db/src/main/java/org/apache/empire/db/context/DBContextBase.java
@@ -5,12 +5,11 @@ package org.apache.empire.db.context;
 
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.Map;
 
 import org.apache.empire.db.DBContext;
 import org.apache.empire.db.DBObject;
 import org.apache.empire.db.DBUtils;
+import org.apache.empire.db.context.DBRollbackManager.ReleaseAction;
 import org.apache.empire.db.exceptions.EmpireSQLException;
 import org.apache.empire.exceptions.InvalidArgumentException;
 import org.slf4j.Logger;
@@ -26,9 +25,18 @@ public abstract class DBContextBase implements DBContext
     // Logger
     private static final Logger log = LoggerFactory.getLogger(DBContextBase.class);
     
-    private Map<DBObject, DBRollbackHandler> rollbackHandler;
-    
     private DBUtils utils = null;
+    
+    private boolean noRollbacksWarnOnce = true;
+    
+    /**
+     * Factory function for Utils creation 
+     * @return the utils implementation
+     */
+    protected DBUtils createUtils()
+    {
+        return new DBUtils(this);
+    }
 
     @SuppressWarnings("unchecked")
     @Override
@@ -38,20 +46,34 @@ public abstract class DBContextBase implements DBContext
             utils = createUtils();
         return ((T)utils);
     }
+
+    protected abstract Connection getConnection(boolean required);
+
+    protected abstract DBRollbackManager getRollbackManager(boolean required);
+
+    @Override
+    public Connection getConnection()
+    {
+        return getConnection(true);
+    }
     
     @Override
     public void commit()
     {
         try
         {   // Check argument
-            Connection conn = getConnection();
+            Connection conn = getConnection(false);
             if (conn==null)
-                throw new InvalidArgumentException("conn", conn);
+            {   log.info("No Connection to commmit changes");
+                return; // Nothing to do
+            }
             // Commit
             if (conn.getAutoCommit()==false)
                 conn.commit();
             // discard rollbacks
-            discardAllHandlers();
+            DBRollbackManager dbrm = getRollbackManager(false);
+            if (dbrm!=null)
+                dbrm.releaseConnection(conn, ReleaseAction.Discard);
             // Done
             return;
         } catch (SQLException sqle) { 
@@ -72,14 +94,18 @@ public abstract class DBContextBase implements DBContext
     {
         try
         {   // Check argument
-            Connection conn = getConnection();
+            Connection conn = getConnection(false);
             if (conn==null)
-                throw new InvalidArgumentException("conn", conn);
+            {   log.info("No Connection to rollback changes");
+                return; // Nothing to do
+            }
             // rollback
             log.info("Database rollback issued!");
             conn.rollback();
-            // rollback
-            rollbackAllHandlers();
+            // perform Rollback
+            DBRollbackManager dbrm = getRollbackManager(false);
+            if (dbrm!=null)
+                dbrm.releaseConnection(conn, ReleaseAction.Rollback);
             // Done
             return;
         } catch (SQLException sqle) { 
@@ -91,23 +117,27 @@ public abstract class DBContextBase implements DBContext
     @Override
     public void addRollbackHandler(DBRollbackHandler handler)
     {
-        if (rollbackHandler==null)
-            rollbackHandler = new LinkedHashMap<DBObject, DBRollbackHandler>();
-        // check
-        DBObject object = handler.getObject();
-        if (rollbackHandler.containsKey(object))
-            rollbackHandler.get(object).combine(handler);
-        else
-            rollbackHandler.put(object, handler);
+        if (handler==null || handler.getObject()==null)
+            throw new InvalidArgumentException("handler", handler);
+        // Add handler
+        DBRollbackManager dbrm = getRollbackManager(true);
+        if (dbrm!=null)
+            dbrm.addHandler(getConnection(true), handler);
+        else if (noRollbacksWarnOnce)
+        {   log.warn("*** No DBRollbackManager provided. Rollbacks will be disabled! ***");
+            noRollbacksWarnOnce = false;
+        }
     }
     
     @Override
     public void removeRollbackHandler(DBObject object)
     {
         if (object==null)
-            rollbackHandler=null;   // remove all
-        else if (rollbackHandler!=null && rollbackHandler.remove(object)!=null)
-            log.info("Rollback handler for object {} was removed", object.getClass().getSimpleName());
+            throw new InvalidArgumentException("object", object);
+        // Remove handler
+        DBRollbackManager dbrm = getRollbackManager(false);
+        if (dbrm!=null)
+            dbrm.removeHandler(getConnection(false), object);
     }
 
     /**
@@ -118,48 +148,13 @@ public abstract class DBContextBase implements DBContext
     public void discard()
     {
         /* don't close connection! */
-        discardAllHandlers();
-    }
-    
-    /**
-     * Discards all rollback handlers
-     */
-    protected void discardAllHandlers()
-    {   // rollback
-        if (rollbackHandler==null)
-            return;
-        for (DBRollbackHandler handler : rollbackHandler.values())
-            handler.discard();
-        rollbackHandler=null;
-    }
-    
-    /**
-     * Performs rollback on all rollback handlers
-     */
-    protected void rollbackAllHandlers()
-    {   // rollback
-        if (rollbackHandler==null)
-            return;
-        for (DBRollbackHandler handler : rollbackHandler.values())
-            handler.rollback();
-        rollbackHandler=null;
-    }
-
-    /**
-     * Factory function for Utils creation 
-     * @return the utils implementation
-     */
-    protected DBUtils createUtils()
-    {
-        return new DBUtils(this);
     }
     
     /**
      * helper to close a connection on discard
      */
     protected void closeConnection()
-    {
-        try
+    {   try
         {   // close connection
             Connection conn = getConnection();
             conn.close();
diff --git a/empire-db/src/main/java/org/apache/empire/db/context/DBContextStatic.java b/empire-db/src/main/java/org/apache/empire/db/context/DBContextStatic.java
index 1afbe4e..9897866 100644
--- a/empire-db/src/main/java/org/apache/empire/db/context/DBContextStatic.java
+++ b/empire-db/src/main/java/org/apache/empire/db/context/DBContextStatic.java
@@ -6,6 +6,7 @@ package org.apache.empire.db.context;
 import java.sql.Connection;
 
 import org.apache.empire.db.DBDatabaseDriver;
+import org.apache.empire.db.context.DBRollbackManager.ReleaseAction;
 
 public class DBContextStatic extends DBContextBase
 {
@@ -13,6 +14,14 @@ public class DBContextStatic extends DBContextBase
     private final Connection conn;
     private final boolean closeOnDiscard;
     
+    /**
+     *  Global DBRollbackManager
+     *  
+     *  initialConnectionCapacity = 2
+     *  initialObjectCapacity = 16
+     */
+    private static final DBRollbackManager staticRollbackManager = new DBRollbackManager(2, 16);
+    
     public DBContextStatic(DBDatabaseDriver driver, Connection conn, boolean closeOnDiscard)
     {
         this.driver = driver;
@@ -30,19 +39,28 @@ public class DBContextStatic extends DBContextBase
     {
         return driver;
     }
-
-    @Override
-    public Connection getConnection()
-    {
-        return conn;
-    }
     
     @Override
     public void discard()
     {
         super.discard();
         // close
-        if (closeOnDiscard)
+        if (closeOnDiscard) 
+        {   // Close the connection
             closeConnection();
+            staticRollbackManager.releaseConnection(conn, ReleaseAction.Discard);
+        }
+    }
+
+    @Override
+    protected Connection getConnection(boolean create)
+    {
+        return conn;
+    }
+
+    @Override
+    protected DBRollbackManager getRollbackManager(boolean create)
+    {
+        return staticRollbackManager;
     }
 }
diff --git a/empire-db/src/main/java/org/apache/empire/db/context/DBRollbackManager.java b/empire-db/src/main/java/org/apache/empire/db/context/DBRollbackManager.java
new file mode 100644
index 0000000..17c6175
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/context/DBRollbackManager.java
@@ -0,0 +1,100 @@
+package org.apache.empire.db.context;
+
+import java.sql.Connection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.empire.db.DBObject;
+import org.apache.empire.exceptions.ObjectNotValidException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBRollbackManager
+{    // Logger
+    private static final Logger log = LoggerFactory.getLogger(DBRollbackManager.class);
+    
+    /**
+     * Connection release action 
+     * @author rainer
+     */
+    public enum ReleaseAction
+    {
+        Discard,
+        Rollback;
+    }
+
+    private final Map<Integer, Map<DBObject, DBRollbackHandler>> connectionMap;
+    private final int initialObjectCapacity;
+    
+    public DBRollbackManager(int initialConnectionCapacity, int initialObjectCapacity)
+    {
+        this.connectionMap = new HashMap<Integer, Map<DBObject, DBRollbackHandler>>(initialConnectionCapacity);
+        this.initialObjectCapacity = initialObjectCapacity;
+    }
+
+    /**
+     * Add a rollback handler for a particular Connection
+     * @param conn
+     * @param handler
+     */
+    public synchronized void addHandler(Connection conn, DBRollbackHandler handler)
+    {
+        Map<DBObject, DBRollbackHandler> handlerMap = connectionMap.get(conn.hashCode());
+        if (handlerMap==null)
+        {   handlerMap = new LinkedHashMap<DBObject, DBRollbackHandler>(this.initialObjectCapacity);
+            connectionMap.put(conn.hashCode(), handlerMap);
+        }
+        // check
+        DBObject object = handler.getObject();
+        if (object==null)
+            throw new ObjectNotValidException(handler);
+        // Append or combine
+        if (handlerMap.containsKey(object))
+            handlerMap.get(object).combine(handler);
+        else
+            handlerMap.put(object, handler);
+    }
+    
+    /**
+     * Remove the rollback handler for a particular Connection and Object
+     * @param conn
+     * @param object
+     */
+    public synchronized void removeHandler(Connection conn, DBObject object)
+    {
+        if (object==null)
+        {   // Discard all
+            releaseConnection(conn, ReleaseAction.Discard);
+            return; 
+        }
+        Map<DBObject, DBRollbackHandler> handlerMap = connectionMap.get(conn.hashCode());
+        if (handlerMap==null)
+            return; // Nothing to do
+        // Remover handler
+        DBRollbackHandler handler = handlerMap.remove(object); 
+        if (handler==null)
+            return; // No handler
+        // dispose
+        log.info("Rollback handler for object {} was removed", object.getClass().getSimpleName());
+        handler.discard();
+    }
+    
+    /**
+     * releaseConnection from handler and perform 
+     */
+    public synchronized void releaseConnection(Connection conn, ReleaseAction action)
+    {
+        Map<DBObject, DBRollbackHandler> handlerMap = connectionMap.get(conn.hashCode());
+        if (handlerMap==null)
+            return; // Nothing to do
+        // rollback
+        for (DBRollbackHandler handler : handlerMap.values())
+            if (action==ReleaseAction.Rollback)
+                handler.rollback();
+            else
+                handler.discard();
+        // cleanup
+        connectionMap.remove(conn.hashCode());        
+    }
+}