From 054f7dc86f6c44b7461e853ad452c25819d85d11 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Fri, 18 Sep 2015 22:25:19 +1200 Subject: [PATCH] #415 - ENH: Add ExplicitJdbcTransaction ... so like AutoCommitJdbcTransaction but using explicit statements to commit and rollback --- .../com/avaje/ebean/config/ServerConfig.java | 30 +++++- .../server/core/InternalConfiguration.java | 5 + .../transaction/ExplicitJdbcTransaction.java | 45 +++++++++ .../ExplicitTransactionManager.java | 32 ++++++ .../TestExplicitTransactionMode.java | 98 +++++++++++++++++++ 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitJdbcTransaction.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java create mode 100644 src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index eeb0a9afe..40b7db04e 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -234,7 +234,12 @@ public class ServerConfig { * Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager. */ private boolean autoCommitMode; - + + /** + * Set to true if transaction begin should be started with explicit statement. + */ + private boolean explicitTransactionBeginMode; + /** * The data source JNDI name if using a JNDI DataSource. */ @@ -1220,6 +1225,28 @@ public class ServerConfig { this.autoCommitMode = autoCommitMode; } + /** + * Return true if transaction begin should be started with explicit statement. + */ + public boolean isExplicitTransactionBeginMode() { + return explicitTransactionBeginMode; + } + + /** + * Set to true if transaction begin should be started with explicit statement. + *

+ * This works for H2 and Postgres but not for Oracle - only use this if you first name + * is Daryl or you have explicitly talked to Rob about this feature. + *

+ *

+ * This is generally not expected to be turned on but instead allow transactions to start + * implicitly which is generally the standard approach. + *

+ */ + public void setExplicitTransactionBeginMode(boolean explicitTransactionBeginMode) { + this.explicitTransactionBeginMode = explicitTransactionBeginMode; + } + /** * Return a value used to represent TRUE in the database. *

@@ -2067,6 +2094,7 @@ public class ServerConfig { } loadDataSourceSettings(p); + explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode); autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode); useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index 23e010f8c..8fad1abf6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -39,6 +39,7 @@ import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare; import com.avaje.ebeaninternal.server.text.json.DJsonContext; import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager; import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager; +import com.avaje.ebeaninternal.server.transaction.ExplicitTransactionManager; import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager; import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager; import com.avaje.ebeaninternal.server.transaction.TransactionManager; @@ -220,6 +221,10 @@ public class InternalConfiguration { */ private TransactionManager createTransactionManager() { + if (serverConfig.isExplicitTransactionBeginMode()) { + return new ExplicitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses()); + } + if (isAutoCommitMode()) { return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses()); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitJdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitJdbcTransaction.java new file mode 100644 index 000000000..b8fc830da --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitJdbcTransaction.java @@ -0,0 +1,45 @@ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * This only works for Postgres and H2 (and doesn't work for Oracle). + * + * Uses explicit begin statement to start the transactions. + */ +public class ExplicitJdbcTransaction extends JdbcTransaction { + + public ExplicitJdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) { + super(id, explicit, connection, manager); + } + + @Override + protected void checkAutoCommit(Connection connection) throws SQLException { + // begin the transaction explicitly + executeStatement("begin"); + } + + @Override + protected void performRollback() throws SQLException { + // Postgres needs this explicit rollback statement when used with AutoCommit=true + executeStatement("rollback"); + } + + @Override + protected void performCommit() throws SQLException { + // Postgres needs this explicit commit statement when used with AutoCommit=true + executeStatement("commit"); + } + + private void executeStatement(String statement) throws SQLException { + PreparedStatement stmt = connection.prepareStatement(statement); + try { + stmt.execute(); + } finally { + stmt.close(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java new file mode 100644 index 000000000..52b416617 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java @@ -0,0 +1,32 @@ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebean.BackgroundExecutor; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + +import java.sql.Connection; + +/** + * TransactionManager where the transactions start with explicit "begin" statement. + */ +public class ExplicitTransactionManager extends TransactionManager { + + public ExplicitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, + ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { + + super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses); + } + + /** + * Create a ExplicitJdbcTransaction. + */ + @Override + protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) { + + return new ExplicitJdbcTransaction(prefix + id, explicit, c, this); + } + +} diff --git a/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java b/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java new file mode 100644 index 000000000..04d63a0e5 --- /dev/null +++ b/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java @@ -0,0 +1,98 @@ +package com.avaje.tests.transaction; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.EbeanServerFactory; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.config.DataSourceConfig; +import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool; +import com.avaje.tests.model.basic.UTDetail; +import com.avaje.tests.model.basic.UTMaster; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestExplicitTransactionMode extends BaseTestCase { + + @Test + public void test() throws SQLException { + + DataSourceConfig dsConfig = new DataSourceConfig(); + dsConfig.loadSettings("h2autocommit");//"h2autocommit","pg" + dsConfig.setAutoCommit(true); + + DataSourcePool pool = new DataSourcePool(null, "h2autocommit", dsConfig); + + Connection connection = pool.getConnection(); + assertTrue(connection.getAutoCommit()); + connection.close(); + + ServerConfig config = new ServerConfig(); + config.setName("h2autocommit"); + config.loadFromProperties(); + config.setDataSource(pool); + config.setDefaultServer(false); + config.setRegister(false); + config.setExplicitTransactionBeginMode(true); + + config.addClass(UTMaster.class); + config.addClass(UTDetail.class); + config.setDdlGenerate(true); + config.setDdlRun(true); + + EbeanServer ebeanServer = EbeanServerFactory.create(config); + + Query query = ebeanServer.find(UTMaster.class); + List details = ebeanServer.findList(query, null); + assertEquals(0, details.size()); + + UTMaster bean0 = new UTMaster("one0"); + Transaction txn0 = ebeanServer.beginTransaction(); + try { + ebeanServer.save(bean0); + txn0.rollback(); + } finally { + txn0.end(); + } + + // rollback as expected + assertEquals(0, ebeanServer.find(UTMaster.class).findRowCount()); + + UTMaster bean1 = new UTMaster("one1"); + UTMaster bean2 = new UTMaster("two2"); + UTMaster bean3 = new UTMaster("three3"); + + // use a different transaction to do final query check + Transaction otherTxn = ebeanServer.createTransaction(); + Transaction txn = ebeanServer.beginTransaction(); + + try { + ebeanServer.save(bean1); + ebeanServer.save(bean2); + + // not visible in other transaction + Query query2 = ebeanServer.find(UTMaster.class); + details = ebeanServer.findList(query2, otherTxn); + assertEquals(0, details.size()); + + ebeanServer.save(bean3); + + txn.commit(); + + } finally { + txn.end(); + } + + // commit as expected + Query query3 = ebeanServer.find(UTMaster.class); + details = ebeanServer.findList(query3, otherTxn); + assertEquals(3, details.size()); + } +}