keycloak-uncached
Changes
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml 1(+1 -0)
federation/ldap/pom.xml 5(+5 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java 215(+133 -82)
Details
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
index 5373067..94c80a2 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -25,6 +25,7 @@
     </resources>
 
     <dependencies>
+        <module name="javax.transaction.api"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
                diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
index 95f34b6..8b5632e 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
@@ -33,5 +33,6 @@
         <module name="javax.ws.rs.api"/>
         <module name="org.apache.httpcomponents"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
+        <module name="javax.transaction.api"/>
     </dependencies>
 </module>
                federation/ldap/pom.xml 5(+5 -0)
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index f6a8c4b..257c617 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -74,6 +74,11 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.transaction</groupId>
+            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
                diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
index db85a82..e35e5b0 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.connections.jpa;
 
+import org.jboss.logging.Logger;
+
 import javax.persistence.EntityManager;
 
 /**
@@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
  */
 public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
 
+    private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
     private final EntityManager em;
 
     public DefaultJpaConnectionProvider(EntityManager em) {
@@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
 
     @Override
     public void close() {
+        logger.trace("DefaultJpaConnectionProvider close()");
         em.close();
     }
 
                diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 3c74264..d029e16 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -29,12 +29,22 @@ import java.util.Map;
 import javax.naming.InitialContext;
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
+import javax.persistence.SynchronizationType;
 import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
 
 import org.hibernate.ejb.AvailableSettings;
+import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
@@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
 import org.keycloak.models.dblock.DBLockManager;
 import org.keycloak.ServerStartupError;
 import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     private volatile EntityManagerFactory emf;
 
     private Config.Scope config;
-    
-    private Map<String,String> operationalInfo;
+
+    private Map<String, String> operationalInfo;
+
+    private boolean jtaEnabled;
+    private JtaTransactionManagerLookup jtaLookup;
+
+    private KeycloakSessionFactory factory;
 
     @Override
     public JpaConnectionProvider create(KeycloakSession session) {
+        logger.trace("Create JpaConnectionProvider");
         lazyInit(session);
 
-        EntityManager em = emf.createEntityManager();
+        EntityManager em = null;
+        if (!jtaEnabled) {
+            logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
+            em = emf.createEntityManager();
+        } else {
+
+            em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
+        }
         em = PersistenceExceptionConverter.create(em);
-        session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
+        if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
         return new DefaultJpaConnectionProvider(em);
     }
 
@@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
+        this.factory = factory;
+        checkJtaEnabled(factory);
 
     }
 
+    protected void checkJtaEnabled(KeycloakSessionFactory factory) {
+        jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
+        if (jtaLookup != null) {
+            if (jtaLookup.getTransactionManager() != null) {
+                jtaEnabled = true;
+            }
+        }
+    }
+
     private void lazyInit(KeycloakSession session) {
         if (emf == null) {
             synchronized (this) {
                 if (emf == null) {
-                    logger.debug("Initializing JPA connections");
+                    KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+                        logger.debug("Initializing JPA connections");
 
-                    Map<String, Object> properties = new HashMap<String, Object>();
+                        Map<String, Object> properties = new HashMap<String, Object>();
 
-                    String unitName = "keycloak-default";
+                        String unitName = "keycloak-default";
 
-                    String dataSource = config.get("dataSource");
-                    if (dataSource != null) {
-                        if (config.getBoolean("jta", false)) {
-                            properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+                        String dataSource = config.get("dataSource");
+                        if (dataSource != null) {
+                            if (config.getBoolean("jta", jtaEnabled)) {
+                                properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+                            } else {
+                                properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
+                            }
                         } else {
-                            properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
-                        }
-                    } else {
-                        properties.put(AvailableSettings.JDBC_URL, config.get("url"));
-                        properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
-
-                        String user = config.get("user");
-                        if (user != null) {
-                            properties.put(AvailableSettings.JDBC_USER, user);
-                        }
-                        String password = config.get("password");
-                        if (password != null) {
-                            properties.put(AvailableSettings.JDBC_PASSWORD, password);
-                        }
-                    }
-
-                    String schema = getSchema();
-                    if (schema != null) {
-                        properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
-                    }
-
-                    MigrationStrategy migrationStrategy = getMigrationStrategy();
-                    boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
-                    File databaseUpdateFile = getDatabaseUpdateFile();
-
-                    properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
-                    properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
-
-                    Connection connection = getConnection();
-                    try{ 
-	                    prepareOperationalInfo(connection);
-
-                        String driverDialect = detectDialect(connection);
-                        if (driverDialect != null) {
-                            properties.put("hibernate.dialect", driverDialect);
+                            properties.put(AvailableSettings.JDBC_URL, config.get("url"));
+                            properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
+
+                            String user = config.get("user");
+                            if (user != null) {
+                                properties.put(AvailableSettings.JDBC_USER, user);
+                            }
+                            String password = config.get("password");
+                            if (password != null) {
+                                properties.put(AvailableSettings.JDBC_PASSWORD, password);
+                            }
                         }
 
-                        migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
-
-                        int globalStatsInterval = config.getInt("globalStatsInterval", -1);
-                        if (globalStatsInterval != -1) {
-                            properties.put("hibernate.generate_statistics", true);
+                        String schema = getSchema();
+                        if (schema != null) {
+                            properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
                         }
 
-	                    logger.trace("Creating EntityManagerFactory");
-	                    emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
-	                    logger.trace("EntityManagerFactory created");
-
-                        if (globalStatsInterval != -1) {
-                            startGlobalStats(session, globalStatsInterval);
+                        MigrationStrategy migrationStrategy = getMigrationStrategy();
+                        boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
+                        File databaseUpdateFile = getDatabaseUpdateFile();
+
+                        properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
+                        properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
+
+                        Connection connection = getConnection();
+                        try {
+                            prepareOperationalInfo(connection);
+
+                            String driverDialect = detectDialect(connection);
+                            if (driverDialect != null) {
+                                properties.put("hibernate.dialect", driverDialect);
+                            }
+
+                            migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
+
+                            int globalStatsInterval = config.getInt("globalStatsInterval", -1);
+                            if (globalStatsInterval != -1) {
+                                properties.put("hibernate.generate_statistics", true);
+                            }
+
+                            logger.trace("Creating EntityManagerFactory");
+                            logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
+                            if (jtaEnabled) {
+                                properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() {
+                                    @Override
+                                    protected TransactionManager locateTransactionManager() {
+                                        return jtaLookup.getTransactionManager();
+                                    }
+
+                                    @Override
+                                    protected UserTransaction locateUserTransaction() {
+                                        return null;
+                                    }
+                                });
+                            }
+                            emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled);
+                            logger.trace("EntityManagerFactory created");
+
+                            if (globalStatsInterval != -1) {
+                                startGlobalStats(session, globalStatsInterval);
+                            }
+                        } finally {
+                            // Close after creating EntityManagerFactory to prevent in-mem databases from closing
+                            if (connection != null) {
+                                try {
+                                    connection.close();
+                                } catch (SQLException e) {
+                                    logger.warn("Can't close connection", e);
+                                }
+                            }
                         }
-                    } finally {
-	                    // Close after creating EntityManagerFactory to prevent in-mem databases from closing
-	                    if (connection != null) {
-	                        try {
-	                            connection.close();
-	                        } catch (SQLException e) {
-	                            logger.warn("Can't close connection", e);
-	                        }
-	                    }
-                    }
+                    });
                 }
             }
         }
@@ -182,19 +233,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     }
 
     protected void prepareOperationalInfo(Connection connection) {
-  		try {
-  			operationalInfo = new LinkedHashMap<>();
-  			DatabaseMetaData md = connection.getMetaData();
-  			operationalInfo.put("databaseUrl",md.getURL());
-  			operationalInfo.put("databaseUser", md.getUserName());
-  			operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
-  			operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
+        try {
+            operationalInfo = new LinkedHashMap<>();
+            DatabaseMetaData md = connection.getMetaData();
+            operationalInfo.put("databaseUrl", md.getURL());
+            operationalInfo.put("databaseUser", md.getUserName());
+            operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
+            operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
 
             logger.debugf("Database info: %s", operationalInfo.toString());
-  		} catch (SQLException e) {
-  			logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
-  		}
-  	}
+        } catch (SQLException e) {
+            logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
+        }
+    }
 
 
     protected String detectDialect(Connection connection) {
@@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
     public String getSchema() {
         return config.get("schema");
     }
-    
+
     @Override
-  	public Map<String,String> getOperationalInfo() {
-  		return operationalInfo;
-  	}
+    public Map<String, String> getOperationalInfo() {
+        return operationalInfo;
+    }
 
     private MigrationStrategy getMigrationStrategy() {
         String migrationStrategy = config.get("migrationStrategy");
                diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
index 7c48499..d080542 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
@@ -23,14 +23,13 @@ import java.sql.SQLException;
 import liquibase.Liquibase;
 import liquibase.exception.DatabaseException;
 import liquibase.exception.LiquibaseException;
-import liquibase.exception.LockException;
-import liquibase.lockservice.LockService;
 import org.jboss.logging.Logger;
 import org.keycloak.connections.jpa.JpaConnectionProvider;
 import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
 import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.dblock.DBLockProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
         this.session = session;
     }
 
+
     private void lazyInit() {
         if (!initialized) {
             LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
@@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
 
     @Override
     public void waitForLock() {
-        lazyInit();
-
-        while (maxAttempts > 0) {
-            try {
-                lockService.waitForLock();
-                factory.setHasLock(true);
-                this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
-                return;
-            } catch (LockRetryException le) {
-                // Indicates we should try to acquire lock again in different transaction
-                safeRollbackConnection();
-                restart();
-                maxAttempts--;
-            } catch (RuntimeException re) {
-                safeRollbackConnection();
-                safeCloseConnection();
-                throw re;
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+
+            lazyInit();
+
+            while (maxAttempts > 0) {
+                try {
+                    lockService.waitForLock();
+                    factory.setHasLock(true);
+                    this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+                    return;
+                } catch (LockRetryException le) {
+                    // Indicates we should try to acquire lock again in different transaction
+                    safeRollbackConnection();
+                    restart();
+                    maxAttempts--;
+                } catch (RuntimeException re) {
+                    safeRollbackConnection();
+                    safeCloseConnection();
+                    throw re;
+                }
             }
-        }
+        });
+
     }
 
 
     @Override
     public void releaseLock() {
-        lazyInit();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            lazyInit();
 
-        lockService.releaseLock();
-        lockService.reset();
-        factory.setHasLock(false);
+            lockService.releaseLock();
+            lockService.reset();
+            factory.setHasLock(false);
+        });
     }
 
     @Override
@@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
 
     @Override
     public void destroyLockInfo() {
-        lazyInit();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            lazyInit();
 
-        try {
-            this.lockService.destroy();
-            dbConnection.commit();
-            logger.debug("Destroyed lock table");
-        } catch (DatabaseException | SQLException de) {
-            logger.error("Failed to destroy lock table");
-            safeRollbackConnection();
-        }
+            try {
+                this.lockService.destroy();
+                dbConnection.commit();
+                logger.debug("Destroyed lock table");
+            } catch (DatabaseException | SQLException de) {
+                logger.error("Failed to destroy lock table");
+                safeRollbackConnection();
+            }
+        });
     }
 
     @Override
     public void close() {
-        safeCloseConnection();
+        KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+            safeCloseConnection();
+        });
     }
 
     private void safeRollbackConnection() {
                diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
index 5ac7d2f..0385f6a 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
@@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
 import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
 import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
 import org.hibernate.jpa.boot.spi.Bootstrap;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
 import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
 import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
 import org.keycloak.models.KeycloakSession;
@@ -46,8 +48,9 @@ public class JpaUtils {
         return (schema==null) ? tableName : schema + "." + tableName;
     }
 
-    public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
-        PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
+    public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
+        PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
+        PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
         List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
         for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
             if (persistenceUnit.getName().equals(unitName)) {
@@ -58,6 +61,7 @@ public class JpaUtils {
                 }
                 // Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
                 // to find and load the extra provided entities. Set the provided classloader as parent classloader.
+                persistenceUnit.setTransactionType(txType);
                 return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
                         new ProxyClassLoader(providedEntities, classLoader)).build();
             }
                diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 0e2dcbe..a18d8cf 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -23,6 +23,21 @@ package org.keycloak.models;
  */
 public interface KeycloakTransactionManager extends KeycloakTransaction {
 
+    enum JTAPolicy {
+        /**
+         * Do not interact with JTA at all
+         *
+         */
+        NOT_SUPPORTED,
+        /**
+         * A new JTA Transaction will be created when Keycloak TM begin() is called.  If an existing JTA transaction
+         * exists, it is suspended and resumed after the Keycloak transaction finishes.
+         */
+        REQUIRES_NEW,
+    }
+
+    JTAPolicy getJTAPolicy();
+    void setJTAPolicy(JTAPolicy policy);
     void enlist(KeycloakTransaction transaction);
     void enlistAfterCompletion(KeycloakTransaction transaction);
 
                diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index bc20d49..b8adc62 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -44,8 +44,14 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.common.util.CertificateUtils;
 import org.keycloak.common.util.PemUtils;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 
 import javax.crypto.spec.SecretKeySpec;
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.security.Key;
@@ -56,6 +62,7 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
+import java.sql.DriverManager;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -63,6 +70,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Function;
 
 /**
  * Set of helper methods, which are useful in various model implementations.
@@ -303,6 +311,7 @@ public final class KeycloakModelUtils {
         }
     }
 
+
     public static String getMasterRealmAdminApplicationClientId(String realmName) {
         return realmName + "-realm";
     }
@@ -651,4 +660,33 @@ public final class KeycloakModelUtils {
             }
         }
     }
+
+    public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) {
+        JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class);
+        Transaction suspended = null;
+        try {
+            if (lookup != null) {
+                if (lookup.getTransactionManager() != null) {
+                    try {
+                        suspended = lookup.getTransactionManager().suspend();
+                    } catch (SystemException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+            runnable.run();
+        } finally {
+            if (suspended != null) {
+                try {
+                    lookup.getTransactionManager().resume(suspended);
+                } catch (InvalidTransactionException e) {
+                    throw new RuntimeException(e);
+                } catch (SystemException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+        }
+
+    }
 }
                diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index d2431d8..5ab0346 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -61,4 +61,5 @@ org.keycloak.authorization.AuthorizationSpi
 org.keycloak.models.cache.authorization.CachedStoreFactorySpi
 org.keycloak.protocol.oidc.TokenIntrospectionSpi
 org.keycloak.policy.PasswordPolicySpi
-org.keycloak.policy.PasswordPolicyManagerSpi
\ No newline at end of file
+org.keycloak.policy.PasswordPolicyManagerSpi
+org.keycloak.transaction.TransactionManagerLookupSpi
                diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 539bafb..b1ae4dd 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -27,6 +27,7 @@ import org.keycloak.scripting.ScriptingProvider;
 import org.keycloak.storage.UserStorageManager;
 import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
+import javax.transaction.TransactionManager;
 import java.util.*;
 
 /**
@@ -51,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
 
     public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
         this.factory = factory;
-        this.transactionManager = new DefaultKeycloakTransactionManager();
+        this.transactionManager = new DefaultKeycloakTransactionManager(this);
         federationManager = new UserFederationManager(this);
         context = new DefaultKeycloakContext(this);
     }
                diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 45bef3c..d5474dc 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -49,7 +49,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
     private Map<Class<? extends Provider>, String> provider = new HashMap<>();
     private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
     protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
-    private TransactionManager tm;
 
     // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
     protected long serverStartupTimestamp;
@@ -97,8 +96,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
         // make the session factory ready for hot deployment
         ProviderManagerRegistry.SINGLETON.setDeployer(this);
 
-        JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
-        if (lookup != null) tm = lookup.getTransactionManager();
     }
     protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
         Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
@@ -282,9 +279,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
 
     public KeycloakSession create() {
         KeycloakSession session =  new DefaultKeycloakSession(this);
-        if (tm != null) {
-            session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
-        }
         return session;
     }
 
                diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 53f92f4..81379a1 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -16,9 +16,13 @@
  */
 package org.keycloak.services;
 
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.KeycloakTransactionManager;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
+import org.keycloak.transaction.JtaTransactionWrapper;
 
+import javax.transaction.TransactionManager;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
     private boolean active;
     private boolean rollback;
+    private KeycloakSession session;
+    private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW;
+
+    public DefaultKeycloakTransactionManager(KeycloakSession session) {
+        this.session = session;
+    }
 
     @Override
     public void enlist(KeycloakTransaction transaction) {
@@ -63,11 +73,30 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     }
 
     @Override
+    public JTAPolicy getJTAPolicy() {
+        return jtaPolicy;
+    }
+
+    @Override
+    public void setJTAPolicy(JTAPolicy policy) {
+        jtaPolicy = policy;
+
+    }
+
+    @Override
     public void begin() {
         if (active) {
              throw new IllegalStateException("Transaction already active");
         }
 
+        if (jtaPolicy == JTAPolicy.REQUIRES_NEW) {
+            JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
+            TransactionManager tm = jtaLookup.getTransactionManager();
+            if (tm != null) {
+                enlist(new JtaTransactionWrapper(tm));
+            }
+        }
+
         for (KeycloakTransaction tx : transactions) {
             tx.begin();
         }
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 3986988..26daf84 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -184,6 +184,8 @@ public class UsersResource {
             return ErrorResponse.exists("User is read only!");
         } catch (ModelException me) {
             return ErrorResponse.exists("Could not update user!");
+        } catch (Exception me) { // JPA may be committed by JTA which can't 
+            return ErrorResponse.exists("Could not update user!");
         }
     }
 
                diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 6cdf52e..68e0806 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
 import org.keycloak.services.util.JsonConfigProvider;
 import org.keycloak.services.util.ObjectMapperResolver;
 import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.common.util.SystemEnvProperties;
 
 import javax.servlet.ServletContext;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.UriInfo;
@@ -155,11 +158,28 @@ public class KeycloakApplication extends Application {
     // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
     protected ExportImportManager migrateAndBootstrap() {
         ExportImportManager exportImportManager;
+        logger.debug("Calling migrateModel");
         migrateModel();
 
+        logger.debug("bootstrap");
         KeycloakSession session = sessionFactory.create();
         try {
             session.getTransactionManager().begin();
+            JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
+            if (lookup != null) {
+                if (lookup.getTransactionManager() != null) {
+                    try {
+                        Transaction transaction = lookup.getTransactionManager().getTransaction();
+                        logger.debugv("bootstrap current transaction? {0}", transaction != null);
+                        if (transaction != null) {
+                            logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
+                        }
+                    } catch (SystemException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+
 
             ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
             exportImportManager = new ExportImportManager(session);
                diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
index ecc3071..e387ff4 100644
--- a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
+++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
@@ -16,7 +16,9 @@
  */
 package org.keycloak.transaction;
 
+import org.jboss.logging.Logger;
 import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.storage.UserStorageManager;
 
 import javax.transaction.InvalidTransactionException;
 import javax.transaction.NotSupportedException;
@@ -31,16 +33,22 @@ import javax.transaction.UserTransaction;
  * @version $Revision: 1 $
  */
 public class JtaTransactionWrapper implements KeycloakTransaction {
+    private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class);
     protected TransactionManager tm;
     protected Transaction ut;
     protected Transaction suspended;
+    protected Exception ended;
 
     public JtaTransactionWrapper(TransactionManager tm) {
         this.tm = tm;
         try {
+
             suspended = tm.suspend();
+            logger.debug("new JtaTransactionWrapper");
+            logger.debugv("was existing? {0}", suspended != null);
             tm.begin();
             ut = tm.getTransaction();
+            //ended = new Exception();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public void commit() {
         try {
-            ut.commit();
+            logger.debug("JtaTransactionWrapper  commit");
+            tm.commit();
         } catch (Exception e) {
             throw new RuntimeException(e);
+        } finally {
+            end();
         }
     }
 
     @Override
     public void rollback() {
         try {
-            ut.rollback();
+            logger.debug("JtaTransactionWrapper rollback");
+            tm.rollback();
         } catch (Exception e) {
             throw new RuntimeException(e);
         } finally {
@@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public void setRollbackOnly() {
         try {
-            ut.setRollbackOnly();
+            tm.setRollbackOnly();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public boolean getRollbackOnly() {
         try {
-            return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
+            return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK;
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
     @Override
     public boolean isActive() {
         try {
-            return ut.getStatus() == Status.STATUS_ACTIVE;
+            return tm.getStatus() == Status.STATUS_ACTIVE;
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
+    /*
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (ended != null) {
+            logger.error("TX didn't close at position", ended);
+        }
+
+    }
+    */
 
     protected void end() {
+        ended = null;
+        logger.debug("JtaTransactionWrapper end");
         if (suspended != null) {
             try {
+                logger.debug("JtaTransactionWrapper resuming suspended");
                 tm.resume(suspended);
             } catch (Exception e) {
                 throw new RuntimeException(e);
                diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 77cba5e..55b31a0 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -18,5 +18,4 @@
 org.keycloak.exportimport.ClientDescriptionConverterSpi
 org.keycloak.wellknown.WellKnownSpi
 org.keycloak.services.clientregistration.ClientRegistrationSpi
-org.keycloak.transaction.TransactionManagerLookupSpi
 
                diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index 326fa7f..c823e4f 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -29,7 +29,7 @@
                     <password>sa</password>
                 </security>
             </datasource>
-            <datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+            <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
                 <?KEYCLOAK_DS_CONNECTION_URL?>
                 <driver>h2</driver>
                 <security>