From f9cd955f7ad5bf033ec5fdb9d98c760e48fbc16b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 31 Jan 2018 13:28:42 +1300 Subject: [PATCH] #1245 - Refactor internals - Add to TransactionManager externalRemoveTransaction() and externalBeginTransaction() These provide a better/tidier way for external transaction managers (like ebean-spring-txn) to begin and end externally managed transactions. --- pom.xml | 2 +- .../io/ebeaninternal/api/SpiEbeanServer.java | 5 ++ .../api/SpiTransactionManager.java | 37 +++++++++ .../server/core/DefaultServer.java | 9 +++ .../transaction/TransactionManager.java | 20 ++++- .../ebeaninternal/api/TDSpiEbeanServer.java | 30 ++++++- .../transaction/TransactionManagerTest.java | 78 +++++++++++++++++++ 7 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/api/SpiTransactionManager.java create mode 100644 src/test/java/io/ebeaninternal/server/transaction/TransactionManagerTest.java diff --git a/pom.xml b/pom.xml index ac993c675..54321c66a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean - 11.9.2-SNAPSHOT + 11.10.1-SNAPSHOT jar ebean diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index a1a5c812e..423c37792 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -80,6 +80,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ void clearQueryStatistics(); + /** + * Return the transaction manager. + */ + SpiTransactionManager getTransactionManager(); + /** * Return all the descriptors. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiTransactionManager.java b/src/main/java/io/ebeaninternal/api/SpiTransactionManager.java new file mode 100644 index 000000000..e914d200e --- /dev/null +++ b/src/main/java/io/ebeaninternal/api/SpiTransactionManager.java @@ -0,0 +1,37 @@ +package io.ebeaninternal.api; + +import io.ebean.TxScope; + +import javax.sql.DataSource; + +/** + * Service provider interface for the transaction manager. + */ +public interface SpiTransactionManager { + + /** + * Return the main DataSource. + */ + DataSource getDataSource(); + + /** + * Return the read only DataSource (if defined). + */ + DataSource getReadOnlyDataSource(); + + /** + * Return the currently active transaction (can be null). + */ + SpiTransaction get(); + + /** + * Push an externally managed transaction into scope (e.g. Spring managed transaction). + */ + ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope); + + /** + * Called when an externally managed transaction has completed. + */ + void externalRemoveTransaction(); + +} diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index bd5c42eab..b9267a91b 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -61,6 +61,7 @@ import io.ebeaninternal.api.SpiJsonContext; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.SpiTransactionManager; import io.ebeaninternal.api.TransactionEventTable; import io.ebeaninternal.dbmigration.DdlGenerator; import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler; @@ -1879,6 +1880,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return beanDescriptorManager.getBeanDescriptorList(); } + /** + * Return the transaction manager. + */ + @Override + public SpiTransactionManager getTransactionManager() { + return transactionManager; + } + public void register(BeanPersistController c) { List> list = beanDescriptorManager.getBeanDescriptorList(); for (BeanDescriptor aList : list) { diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 54d847089..3b880777a 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -16,6 +16,7 @@ import io.ebeaninternal.api.ScopeTrans; import io.ebeaninternal.api.ScopedTransaction; import io.ebeaninternal.api.SpiProfileHandler; import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.SpiTransactionManager; import io.ebeaninternal.api.TransactionEvent; import io.ebeaninternal.api.TransactionEventTable; import io.ebeaninternal.api.TransactionEventTable.TableIUD; @@ -46,7 +47,7 @@ import java.util.Set; * Keeps the Cache and Cluster in synch when transactions are committed. *

*/ -public class TransactionManager { +public class TransactionManager implements SpiTransactionManager { private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class); @@ -558,6 +559,23 @@ public class TransactionManager { } } + @Override + public void externalRemoveTransaction() { + scopeManager.replace(null); + } + + /** + * Push an externally created transaction into scope. This transaction is usually managed externally + * (e.g. Spring managed transaction). + */ + @Override + public ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope) { + ScopedTransaction scopedTxn = new ScopedTransaction(scopeManager); + scopedTxn.push(new ScopeTrans(rollbackOnChecked, false, transaction, txScope)); + scopeManager.set(scopedTxn); + return scopedTxn; + } + /** * Begin a scoped transaction. */ diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index a880c246e..cd689fd65 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -1,6 +1,29 @@ package io.ebeaninternal.api; -import io.ebean.*; +import io.ebean.AutoTune; +import io.ebean.BackgroundExecutor; +import io.ebean.BeanState; +import io.ebean.CallableSql; +import io.ebean.DocumentStore; +import io.ebean.ExpressionFactory; +import io.ebean.Filter; +import io.ebean.FutureIds; +import io.ebean.FutureList; +import io.ebean.FutureRowCount; +import io.ebean.PagedList; +import io.ebean.PersistenceContextScope; +import io.ebean.Query; +import io.ebean.QueryIterator; +import io.ebean.SqlQuery; +import io.ebean.SqlRow; +import io.ebean.SqlUpdate; +import io.ebean.Transaction; +import io.ebean.TransactionCallback; +import io.ebean.TxScope; +import io.ebean.Update; +import io.ebean.UpdateQuery; +import io.ebean.ValuePair; +import io.ebean.Version; import io.ebean.annotation.TxIsolation; import io.ebean.bean.BeanCollection; import io.ebean.bean.CallStack; @@ -138,6 +161,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public SpiTransactionManager getTransactionManager() { + return null; + } + @Override public List> getBeanDescriptors() { return null; diff --git a/src/test/java/io/ebeaninternal/server/transaction/TransactionManagerTest.java b/src/test/java/io/ebeaninternal/server/transaction/TransactionManagerTest.java new file mode 100644 index 000000000..6b851c56d --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/transaction/TransactionManagerTest.java @@ -0,0 +1,78 @@ +package io.ebeaninternal.server.transaction; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.Transaction; +import io.ebean.TxScope; +import io.ebeaninternal.api.ScopedTransaction; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.SpiTransactionManager; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tests.model.basic.Customer; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TransactionManagerTest extends BaseTestCase { + + private static final Logger log = LoggerFactory.getLogger(TransactionManagerTest.class); + + @Test + public void beginExternalTransaction() throws SQLException { + + SpiEbeanServer server = spiEbeanServer(); + + SpiTransactionManager transactionManager = server.getTransactionManager(); + + DataSource dataSource = transactionManager.getDataSource(); + Connection connection = dataSource.getConnection(); + + SpiTransaction externalTxn = new ExternalJdbcTransaction("external0", true, connection, null); + + // push an externally managed transaction onto scope + ScopedTransaction scopedTransaction = transactionManager.externalBeginTransaction(externalTxn, TxScope.required()); + + Transaction current = Transaction.current(); + assertThat(current).as("external transaction is in scope").isSameAs(scopedTransaction); + + Customer.find.byName("In external"); + + log.info("inner begin"); + Transaction inner = Ebean.beginTransaction(); + try { + + current = Transaction.current(); + assertThat(current).as("still using external transaction, nested transaction pushed").isSameAs(scopedTransaction); + + Customer.find.byName("In inner"); + log.info("inner commit"); + inner.commit(); + } finally { + log.info("inner end"); + inner.end(); + } + + current = Transaction.current(); + assertThat(current).as("external transaction still in scope").isSameAs(scopedTransaction); + + Customer.find.byName("external still active"); + + + log.info("external transaction ends with commit and remove"); + connection.commit(); + // remove externally managed transaction out of scope + transactionManager.externalRemoveTransaction(); + + + current = Transaction.current(); + assertThat(current).as("external transaction out of scope").isNull(); + + Customer.find.byNameStatus("New implicit transaction", Customer.Status.ACTIVE); + } +}