diff --git a/pom.xml b/pom.xml index cec97b66d..e3199a057 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ io.ebean ebean-agent - 11.1.1 + 11.4.1-SNAPSHOT test @@ -288,7 +288,7 @@ io.ebean ebean-maven-plugin - 11.1.1 + 11.4.1-SNAPSHOT test diff --git a/src/main/java/io/ebean/TxScope.java b/src/main/java/io/ebean/TxScope.java index 6cbfee3ce..76bb55582 100644 --- a/src/main/java/io/ebean/TxScope.java +++ b/src/main/java/io/ebean/TxScope.java @@ -25,32 +25,32 @@ import java.util.concurrent.Callable; */ public final class TxScope { - int profileId; + private int profileId; - TxType type; + private TxType type; - String serverName; + private String serverName; - TxIsolation isolation; + private TxIsolation isolation; - PersistBatch batch; + private PersistBatch batch; - PersistBatch batchOnCascade; + private PersistBatch batchOnCascade; - int batchSize; + private int batchSize; - boolean skipGeneratedKeys; + private boolean skipGeneratedKeys; - boolean readOnly; + private boolean readOnly; /** * Set this to false if the JDBC batch should not be automatically be flushed when a query is executed. */ - boolean flushOnQuery = true; + private boolean flushOnQuery = true; - ArrayList> rollbackFor; + private ArrayList> rollbackFor; - ArrayList> noRollbackFor; + private ArrayList> noRollbackFor; /** * Helper method to create a TxScope with REQUIRES. @@ -275,6 +275,13 @@ public final class TxScope { return this; } + /** + * Return the isolation level. + */ + public int getIsolationLevel() { + return isolation != null ? isolation.getLevel() : -1; + } + /** * Return the Isolation level this transaction should run with. */ diff --git a/src/main/java/io/ebeaninternal/api/HelpScopeTrans.java b/src/main/java/io/ebeaninternal/api/HelpScopeTrans.java index 882a622c5..6de5fb396 100644 --- a/src/main/java/io/ebeaninternal/api/HelpScopeTrans.java +++ b/src/main/java/io/ebeaninternal/api/HelpScopeTrans.java @@ -1,7 +1,6 @@ package io.ebeaninternal.api; import io.ebean.Ebean; -import io.ebean.EbeanServer; import io.ebean.TxScope; /** @@ -10,28 +9,20 @@ import io.ebean.TxScope; public class HelpScopeTrans { /** - * Create a ScopeTrans for a given methods TxScope. + * Entering an enhanced transactional method. */ - public static ScopeTrans createScopeTrans(TxScope txScope) { - - EbeanServer server = Ebean.getServer(txScope.getServerName()); - SpiEbeanServer iserver = (SpiEbeanServer) server; - return iserver.createScopeTrans(txScope); + public static void enter(TxScope txScope) { + server().scopedTransactionEnter(txScope); } /** - * Exiting the method in an expected fashion. - *

- * That is returning successfully or via a caught exception. - * Unexpected exceptions are caught via the Thread uncaughtExceptionHandler. - *

- * - * @param returnOrThrowable the return or throwable object - * @param opCode the opcode for ATHROW or ARETURN etc - * @param scopeTrans the scoped transaction the method was run with. + * Exiting an enhanced transactional method. */ - public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans) { + public static void exit(Object returnOrThrowable, int opCode) { + server().scopedTransactionExit(returnOrThrowable, opCode); + } - scopeTrans.onExit(returnOrThrowable, opCode); + private static SpiEbeanServer server() { + return (SpiEbeanServer) Ebean.getDefaultServer(); } } diff --git a/src/main/java/io/ebeaninternal/api/ScopeTrans.java b/src/main/java/io/ebeaninternal/api/ScopeTrans.java index 0cd93d702..641a8c639 100644 --- a/src/main/java/io/ebeaninternal/api/ScopeTrans.java +++ b/src/main/java/io/ebeaninternal/api/ScopeTrans.java @@ -8,17 +8,10 @@ import java.util.ArrayList; /** * Used internally to handle the scoping of transactions for methods. */ -public class ScopeTrans implements Thread.UncaughtExceptionHandler { +public class ScopeTrans { private static final int OPCODE_ATHROW = 191; - private final SpiTransactionScopeManager scopeMgr; - - /** - * The suspended transaction (can be null). - */ - private final SpiTransaction suspendedTransaction; - /** * The transaction in scope (can be null). */ @@ -61,15 +54,11 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler { private boolean rolledBack; - public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope, - SpiTransaction suspendedTransaction, SpiTransactionScopeManager scopeMgr) { + public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) { this.rollbackOnChecked = rollbackOnChecked; this.created = created; this.transaction = transaction; - this.suspendedTransaction = suspendedTransaction; - this.scopeMgr = scopeMgr; - this.noRollbackFor = txScope.getNoRollbackFor(); this.rollbackFor = txScope.getRollbackFor(); @@ -108,55 +97,30 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler { } /** - * Called when the Thread catches any uncaught exception. - * For example, an unexpected NullPointerException or Error. + * Complete the transaction from enhanced transactional. Try to commit. */ - @Override - public void uncaughtException(Thread thread, Throwable e) { - - // rollback transaction if required - caughtThrowable(e); - - // reinstate suspended transaction - onFinally(); - } - - /** - * Returned via RETURN or expected Exception from the method. - * - * @param returnOrThrowable the return value or Throwable - * @param opCode indicates - */ - public void onExit(Object returnOrThrowable, int opCode) { + void complete(Object returnOrThrowable, int opCode) { if (opCode == OPCODE_ATHROW) { // exited with a Throwable caughtThrowable((Throwable) returnOrThrowable); } - onFinally(); + complete(); } /** - * Commit if the transaction exists and has not already been rolled back. - * Also reinstate the suspended transaction if there was one. + * Complete the transaction programmatically. Try to commit. */ - public void onFinally() { - - try { - if (!rolledBack) { - commitTransaction(); - } - } finally { - restoreSuspended(); + public void complete() { + if (!rolledBack) { + commitTransaction(); } } - protected void restoreSuspended() { - if (created || suspendedTransaction != null) { - // put the previously suspended transaction - // back onto the ThreadLocal or equivalent - scopeMgr.replace(suspendedTransaction); + public void end() { + if (created) { + transaction.end(); } } @@ -247,17 +211,10 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler { } } - - if (e instanceof RuntimeException) { - return true; - - } else { - // checked exceptions... - // EJB defaults this to false which is not intuitive IMO - // Ebean makes this configurable (default to true) - return rollbackOnChecked; - } + // checked exceptions... + // EJB defaults this to false which is not intuitive IMO + // Ebean makes this configurable (default to true) + return e instanceof RuntimeException || rollbackOnChecked; } - } diff --git a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java index a4028904f..337f12595 100644 --- a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java +++ b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java @@ -1,432 +1,116 @@ package io.ebeaninternal.api; -import io.ebean.annotation.PersistBatch; -import io.ebean.TransactionCallback; -import io.ebean.annotation.DocStoreMode; -import io.ebean.bean.PersistenceContext; -import io.ebean.event.changelog.BeanChange; -import io.ebean.event.changelog.ChangeSet; -import io.ebeaninternal.server.core.PersistDeferredRelationship; -import io.ebeaninternal.server.core.PersistRequest; -import io.ebeaninternal.server.core.PersistRequestBean; -import io.ebeaninternal.server.persist.BatchControl; -import io.ebeaninternal.server.transaction.ProfileStream; -import io.ebeanservice.docstore.api.DocStoreTransaction; +import io.ebeaninternal.server.transaction.TransactionScopeManager; +import io.ebeaninternal.server.util.ArrayStack; import javax.persistence.PersistenceException; -import java.sql.Connection; -import java.sql.SQLException; /** - * Wrapper of a ScopeTrans request and it's underlying transaction. + * Manage scoped (typically thread local) transactions. + * + * These can be nested and internally they are pushed and popped from a stack. */ -public class ScopedTransaction implements SpiTransaction { +public class ScopedTransaction extends SpiTransactionProxy { - private final ScopeTrans scopeTrans; + private final TransactionScopeManager manager; - private final SpiTransaction transaction; + /** + * Stack of 'nested' transactions. + */ + private ArrayStack stack = new ArrayStack<>(); - private boolean committed; + private ScopeTrans current; - public ScopedTransaction(ScopeTrans scopeTrans) { - this.scopeTrans = scopeTrans; - this.transaction = scopeTrans.getTransaction(); + public ScopedTransaction(TransactionScopeManager manager) { + this.manager = manager; } - @Override - public PersistenceException translate(String message, SQLException cause) { - return transaction.translate(message, cause); + /** + * Push the scope transaction. + */ + public void push(ScopeTrans scopeTrans) { + + if (current != null) { + stack.push(current); + } + current = scopeTrans; + transaction = scopeTrans.getTransaction(); } - @Override - public void commitAndContinue() { - transaction.commitAndContinue(); + /** + * Exiting an enhanced transactional method. + */ + public void complete(Object returnOrThrowable, int opCode) { + current.complete(returnOrThrowable, opCode); + pop(); } - @Override - public void commit() { - scopeTrans.commitTransaction(); - committed = true; + /** + * Programmatic complete - finally block, try to commit. + */ + public void complete() { + current.complete(); + pop(); } - @Override - public void rollback() throws PersistenceException { - scopeTrans.rollback(null); - } - - @Override - public void rollback(Throwable e) throws PersistenceException { - scopeTrans.rollback(e); - } - - @Override - public void setRollbackOnly() { - scopeTrans.setRollbackOnly(); - } - - @Override - public boolean isRollbackOnly() { - return transaction.isRollbackOnly(); - } - - @Override - public void end() throws PersistenceException { - try { - if (!committed) { - scopeTrans.rollback(null); - } - } finally { - scopeTrans.restoreSuspended(); + private void pop() { + if (!stack.isEmpty()) { + current = stack.pop(); + } else { + manager.set(null); } } @Override - public long profileOffset() { - return transaction.profileOffset(); - } - - @Override - public void profileEvent(SpiProfileTransactionEvent event) { - transaction.profileEvent(event); - } - - @Override - public ProfileStream profileStream() { - return transaction.profileStream(); - } - - @Override - public void setTenantId(Object tenantId) { - transaction.setTenantId(tenantId); - } - - @Override - public Object getTenantId() { - return transaction.getTenantId(); - } - - @Override - public DocStoreTransaction getDocStoreTransaction() { - return transaction.getDocStoreTransaction(); - } - - @Override - public DocStoreMode getDocStoreMode() { - return transaction.getDocStoreMode(); - } - - @Override - public void setDocStoreMode(DocStoreMode mode) { - transaction.setDocStoreMode(mode); - } - - @Override - public int getDocStoreBatchSize() { - return transaction.getDocStoreBatchSize(); - } - - @Override - public void setDocStoreBatchSize(int batchSize) { - transaction.setDocStoreBatchSize(batchSize); - } - - @Override - public String getLogPrefix() { - return transaction.getLogPrefix(); - } - - @Override - public boolean isLogSql() { - return transaction.isLogSql(); - } - - @Override - public boolean isLogSummary() { - return transaction.isLogSummary(); - } - - @Override - public void logSql(String msg) { - transaction.logSql(msg); - } - - @Override - public void logSummary(String msg) { - transaction.logSummary(msg); - } - - @Override - public void setSkipCache(boolean skipCache) { - transaction.setSkipCache(skipCache); - } - - @Override - public boolean isSkipCache() { - return transaction.isSkipCache(); - } - - @Override - public void addBeanChange(BeanChange beanChange) { - transaction.addBeanChange(beanChange); - } - - @Override - public void sendChangeLog(ChangeSet changes) { - transaction.sendChangeLog(changes); - } - - @Override - public void registerDeferred(PersistDeferredRelationship derived) { - transaction.registerDeferred(derived); - } - - @Override - public void registerDeleteBean(Integer hash) { - transaction.registerDeleteBean(hash); - } - - @Override - public void unregisterDeleteBean(Integer hash) { - transaction.unregisterDeleteBean(hash); - } - - @Override - public boolean isRegisteredDeleteBean(Integer hash) { - return transaction.isRegisteredDeleteBean(hash); - } - - @Override - public void unregisterBean(Object bean) { - transaction.unregisterBean(bean); - } - - @Override - public boolean isRegisteredBean(Object bean) { - return transaction.isRegisteredBean(bean); - } - - @Override - public String getId() { - return transaction.getId(); - } - - @Override - public void register(TransactionCallback callback) { - transaction.register(callback); - } - - @Override - public boolean isReadOnly() { - return transaction.isReadOnly(); - } - - @Override - public void setReadOnly(boolean readOnly) { - transaction.setReadOnly(readOnly); - } - - @Override - public boolean isActive() { - return transaction.isActive(); - } - - @Override - public void setPersistCascade(boolean persistCascade) { - transaction.setPersistCascade(persistCascade); - } - - @Override - public void setUpdateAllLoadedProperties(boolean updateAllLoaded) { - transaction.setUpdateAllLoadedProperties(updateAllLoaded); - } - - @Override - public Boolean isUpdateAllLoadedProperties() { - return transaction.isUpdateAllLoadedProperties(); - } - - @Override - public void setBatchMode(boolean useBatch) { - transaction.setBatchMode(useBatch); - } - - @Override - public void setBatch(PersistBatch persistBatchMode) { - transaction.setBatch(persistBatchMode); - } - - @Override - public PersistBatch getBatch() { - return transaction.getBatch(); - } - - @Override - public void setBatchOnCascade(PersistBatch batchOnCascadeMode) { - transaction.setBatchOnCascade(batchOnCascadeMode); - } - - @Override - public PersistBatch getBatchOnCascade() { - return transaction.getBatchOnCascade(); - } - - @Override - public void setBatchSize(int batchSize) { - transaction.setBatchSize(batchSize); - } - - @Override - public int getBatchSize() { - return transaction.getBatchSize(); - } - - @Override - public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) { - transaction.setBatchGetGeneratedKeys(getGeneratedKeys); - } - - @Override - public Boolean getBatchGetGeneratedKeys() { - return transaction.getBatchGetGeneratedKeys(); - } - - @Override - public void setBatchFlushOnMixed(boolean batchFlushOnMixed) { - transaction.setBatchFlushOnMixed(batchFlushOnMixed); - } - - @Override - public void setBatchFlushOnQuery(boolean batchFlushOnQuery) { - transaction.setBatchFlushOnQuery(batchFlushOnQuery); - } - - @Override - public boolean isBatchFlushOnQuery() { - return transaction.isBatchFlushOnQuery(); - } - - @Override - public void flush() throws PersistenceException { - transaction.flush(); - } - - @Override - public void flushBatch() throws PersistenceException { - flush(); - } - - @Override - public Connection getConnection() { - return transaction.getConnection(); - } - - @Override - public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) { - transaction.addModification(tableName, inserts, updates, deletes); - } - - @Override - public void putUserObject(String name, Object value) { - transaction.putUserObject(name, value); - } - - @Override - public Object getUserObject(String name) { - return transaction.getUserObject(name); - } - - @Override - public void depth(int diff) { - transaction.depth(); - } - - @Override - public int depth() { - return transaction.depth(); - } - - @Override - public boolean isExplicit() { - return transaction.isExplicit(); - } - - @Override - public TransactionEvent getEvent() { - return transaction.getEvent(); - } - - @Override - public boolean isPersistCascade() { - return transaction.isPersistCascade(); - } - - @Override - public boolean isBatchThisRequest(PersistRequest.Type type) { - return transaction.isBatchThisRequest(type); - } - - @Override - public BatchControl getBatchControl() { - return transaction.getBatchControl(); - } - - @Override - public void setBatchControl(BatchControl control) { - transaction.setBatchControl(control); - } - - @Override - public PersistenceContext getPersistenceContext() { - return transaction.getPersistenceContext(); - } - - @Override - public void setPersistenceContext(PersistenceContext context) { - transaction.setPersistenceContext(context); - } - - @Override - public Connection getInternalConnection() { - return transaction.getInternalConnection(); - } - - @Override - public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) { - return transaction.isSaveAssocManyIntersection(intersectionTable, beanName); - } - - @Override - public boolean checkBatchEscalationOnCascade(PersistRequestBean request) { - return transaction.checkBatchEscalationOnCascade(request); - } - - @Override - public void flushBatchOnCascade() { - transaction.flushBatchOnCascade(); - } - - @Override - public void flushBatchOnRollback() { - transaction.flushBatchOnRollback(); - } - - @Override - public void markNotQueryOnly() { - transaction.markNotQueryOnly(); - } - - @Override - public void checkBatchEscalationOnCollection() { - transaction.checkBatchEscalationOnCollection(); - } - - @Override - public void flushBatchOnCollection() { - transaction.flushBatchOnCollection(); + public void end() throws PersistenceException { + current.end(); + pop(); } @Override public void close() { - transaction.close(); + end(); } + + @Override + public void commit() { + current.commitTransaction(); + } + + @Override + public void rollback() throws PersistenceException { + current.rollback(null); + } + + @Override + public void rollback(Throwable e) throws PersistenceException { + current.rollback(e); + } + + @Override + public void setRollbackOnly() { + current.setRollbackOnly(); + } + + /** + * Return the current transaction. + */ + public SpiTransaction current() { + return transaction; + } + + /** + * Rollback for Error. + */ + public Error caughtError(Error e) { + return current.caughtError(e); + } + + /** + * Maybe rollback based on TxScope rollback on settings. + */ + public Exception caughtThrowable(Exception e) { + return current.caughtThrowable(e); + } + } diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index 158e2278f..a1a5c812e 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -125,11 +125,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ SpiTransaction currentServerTransaction(); - /** - * Create a ScopeTrans for a method for the given scope definition. - */ - ScopeTrans createScopeTrans(TxScope txScope); - /** * Create a ServerTransaction for query purposes. * @@ -213,4 +208,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL * Create DDL handler given the platform and configuration of the server. */ DdlHandler createDdlHandler(); + + /** + * Start an enhanced transactional method. + */ + void scopedTransactionEnter(TxScope txScope); + + /** + * Handle the end of an enhanced Transactional method. + */ + void scopedTransactionExit(Object returnOrThrowable, int opCode); + } diff --git a/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java new file mode 100644 index 000000000..a534898ce --- /dev/null +++ b/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java @@ -0,0 +1,387 @@ +package io.ebeaninternal.api; + +import io.ebean.TransactionCallback; +import io.ebean.annotation.DocStoreMode; +import io.ebean.annotation.PersistBatch; +import io.ebean.bean.PersistenceContext; +import io.ebean.event.changelog.BeanChange; +import io.ebean.event.changelog.ChangeSet; +import io.ebeaninternal.server.core.PersistDeferredRelationship; +import io.ebeaninternal.server.core.PersistRequest; +import io.ebeaninternal.server.core.PersistRequestBean; +import io.ebeaninternal.server.persist.BatchControl; +import io.ebeaninternal.server.transaction.ProfileStream; +import io.ebeanservice.docstore.api.DocStoreTransaction; + +import javax.persistence.PersistenceException; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Proxy for an underlying SpiTransaction (most of the API). + */ +abstract class SpiTransactionProxy implements SpiTransaction { + + protected SpiTransaction transaction; + + @Override + public PersistenceException translate(String message, SQLException cause) { + return transaction.translate(message, cause); + } + + @Override + public void commitAndContinue() { + transaction.commitAndContinue(); + } + + @Override + public boolean isRollbackOnly() { + return transaction.isRollbackOnly(); + } + + @Override + public long profileOffset() { + return transaction.profileOffset(); + } + + @Override + public void profileEvent(SpiProfileTransactionEvent event) { + transaction.profileEvent(event); + } + + @Override + public ProfileStream profileStream() { + return transaction.profileStream(); + } + + @Override + public void setTenantId(Object tenantId) { + transaction.setTenantId(tenantId); + } + + @Override + public Object getTenantId() { + return transaction.getTenantId(); + } + + @Override + public DocStoreTransaction getDocStoreTransaction() { + return transaction.getDocStoreTransaction(); + } + + @Override + public DocStoreMode getDocStoreMode() { + return transaction.getDocStoreMode(); + } + + @Override + public void setDocStoreMode(DocStoreMode mode) { + transaction.setDocStoreMode(mode); + } + + @Override + public int getDocStoreBatchSize() { + return transaction.getDocStoreBatchSize(); + } + + @Override + public void setDocStoreBatchSize(int batchSize) { + transaction.setDocStoreBatchSize(batchSize); + } + + @Override + public String getLogPrefix() { + return transaction.getLogPrefix(); + } + + @Override + public boolean isLogSql() { + return transaction.isLogSql(); + } + + @Override + public boolean isLogSummary() { + return transaction.isLogSummary(); + } + + @Override + public void logSql(String msg) { + transaction.logSql(msg); + } + + @Override + public void logSummary(String msg) { + transaction.logSummary(msg); + } + + @Override + public void setSkipCache(boolean skipCache) { + transaction.setSkipCache(skipCache); + } + + @Override + public boolean isSkipCache() { + return transaction.isSkipCache(); + } + + @Override + public void addBeanChange(BeanChange beanChange) { + transaction.addBeanChange(beanChange); + } + + @Override + public void sendChangeLog(ChangeSet changes) { + transaction.sendChangeLog(changes); + } + + @Override + public void registerDeferred(PersistDeferredRelationship derived) { + transaction.registerDeferred(derived); + } + + @Override + public void registerDeleteBean(Integer hash) { + transaction.registerDeleteBean(hash); + } + + @Override + public void unregisterDeleteBean(Integer hash) { + transaction.unregisterDeleteBean(hash); + } + + @Override + public boolean isRegisteredDeleteBean(Integer hash) { + return transaction.isRegisteredDeleteBean(hash); + } + + @Override + public void unregisterBean(Object bean) { + transaction.unregisterBean(bean); + } + + @Override + public boolean isRegisteredBean(Object bean) { + return transaction.isRegisteredBean(bean); + } + + @Override + public String getId() { + return transaction.getId(); + } + + @Override + public void register(TransactionCallback callback) { + transaction.register(callback); + } + + @Override + public boolean isReadOnly() { + return transaction.isReadOnly(); + } + + @Override + public void setReadOnly(boolean readOnly) { + transaction.setReadOnly(readOnly); + } + + @Override + public boolean isActive() { + return transaction.isActive(); + } + + @Override + public void setPersistCascade(boolean persistCascade) { + transaction.setPersistCascade(persistCascade); + } + + @Override + public void setUpdateAllLoadedProperties(boolean updateAllLoaded) { + transaction.setUpdateAllLoadedProperties(updateAllLoaded); + } + + @Override + public Boolean isUpdateAllLoadedProperties() { + return transaction.isUpdateAllLoadedProperties(); + } + + @Override + public void setBatchMode(boolean useBatch) { + transaction.setBatchMode(useBatch); + } + + @Override + public void setBatch(PersistBatch persistBatchMode) { + transaction.setBatch(persistBatchMode); + } + + @Override + public PersistBatch getBatch() { + return transaction.getBatch(); + } + + @Override + public void setBatchOnCascade(PersistBatch batchOnCascadeMode) { + transaction.setBatchOnCascade(batchOnCascadeMode); + } + + @Override + public PersistBatch getBatchOnCascade() { + return transaction.getBatchOnCascade(); + } + + @Override + public void setBatchSize(int batchSize) { + transaction.setBatchSize(batchSize); + } + + @Override + public int getBatchSize() { + return transaction.getBatchSize(); + } + + @Override + public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) { + transaction.setBatchGetGeneratedKeys(getGeneratedKeys); + } + + @Override + public Boolean getBatchGetGeneratedKeys() { + return transaction.getBatchGetGeneratedKeys(); + } + + @Override + public void setBatchFlushOnMixed(boolean batchFlushOnMixed) { + transaction.setBatchFlushOnMixed(batchFlushOnMixed); + } + + @Override + public void setBatchFlushOnQuery(boolean batchFlushOnQuery) { + transaction.setBatchFlushOnQuery(batchFlushOnQuery); + } + + @Override + public boolean isBatchFlushOnQuery() { + return transaction.isBatchFlushOnQuery(); + } + + @Override + public void flush() throws PersistenceException { + transaction.flush(); + } + + @Override + public void flushBatch() throws PersistenceException { + flush(); + } + + @Override + public Connection getConnection() { + return transaction.getConnection(); + } + + @Override + public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) { + transaction.addModification(tableName, inserts, updates, deletes); + } + + @Override + public void putUserObject(String name, Object value) { + transaction.putUserObject(name, value); + } + + @Override + public Object getUserObject(String name) { + return transaction.getUserObject(name); + } + + @Override + public void depth(int diff) { + transaction.depth(); + } + + @Override + public int depth() { + return transaction.depth(); + } + + @Override + public boolean isExplicit() { + return transaction.isExplicit(); + } + + @Override + public TransactionEvent getEvent() { + return transaction.getEvent(); + } + + @Override + public boolean isPersistCascade() { + return transaction.isPersistCascade(); + } + + @Override + public boolean isBatchThisRequest(PersistRequest.Type type) { + return transaction.isBatchThisRequest(type); + } + + @Override + public BatchControl getBatchControl() { + return transaction.getBatchControl(); + } + + @Override + public void setBatchControl(BatchControl control) { + transaction.setBatchControl(control); + } + + @Override + public PersistenceContext getPersistenceContext() { + return transaction.getPersistenceContext(); + } + + @Override + public void setPersistenceContext(PersistenceContext context) { + transaction.setPersistenceContext(context); + } + + @Override + public Connection getInternalConnection() { + return transaction.getInternalConnection(); + } + + @Override + public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) { + return transaction.isSaveAssocManyIntersection(intersectionTable, beanName); + } + + @Override + public boolean checkBatchEscalationOnCascade(PersistRequestBean request) { + return transaction.checkBatchEscalationOnCascade(request); + } + + @Override + public void flushBatchOnCascade() { + transaction.flushBatchOnCascade(); + } + + @Override + public void flushBatchOnRollback() { + transaction.flushBatchOnRollback(); + } + + @Override + public void markNotQueryOnly() { + transaction.markNotQueryOnly(); + } + + @Override + public void checkBatchEscalationOnCollection() { + transaction.checkBatchEscalationOnCollection(); + } + + @Override + public void flushBatchOnCollection() { + transaction.flushBatchOnCollection(); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 1c59ca5bf..842a5c468 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -659,7 +659,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public Transaction createTransaction() { - return transactionManager.createTransaction(0,true, -1); + return transactionManager.createTransaction(0, true, -1); } /** @@ -671,7 +671,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public Transaction createTransaction(TxIsolation isolation) { - return transactionManager.createTransaction(0,true, isolation.getLevel()); + return transactionManager.createTransaction(0, true, isolation.getLevel()); } @Override @@ -681,7 +681,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public T executeCall(TxScope scope, Callable c) { - ScopeTrans scopeTrans = createScopeTrans(scope); + ScopedTransaction scopeTrans = scopedTransaction(scope); try { return c.call(); @@ -692,7 +692,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { throw new PersistenceException(scopeTrans.caughtThrowable(e)); } finally { - scopeTrans.onFinally(); + scopeTrans.complete(); } } @@ -703,18 +703,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public void execute(TxScope scope, Runnable r) { - ScopeTrans scopeTrans = createScopeTrans(scope); + ScopedTransaction t = scopedTransaction(scope); try { r.run(); } catch (Error e) { - throw scopeTrans.caughtError(e); + throw t.caughtError(e); } catch (Exception e) { - throw new PersistenceException(scopeTrans.caughtThrowable(e)); + throw new PersistenceException(t.caughtThrowable(e)); } finally { - scopeTrans.onFinally(); + t.complete(); } } @@ -758,51 +758,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public ScopeTrans createScopeTrans(TxScope txScope) { + public void scopedTransactionEnter(TxScope txScope) { + beginTransaction(txScope); + } - if (txScope == null) { - // create a TxScope with default settings - txScope = new TxScope(); - } else { - // check for implied batch mode via setting batchSize - txScope.checkBatchMode(); - } - - SpiTransaction suspended = null; - - // get current transaction from ThreadLocal or equivalent - SpiTransaction t = transactionScopeManager.get(); - - boolean newTransaction; - if (txScope.getType().equals(TxType.NOT_SUPPORTED)) { - // Suspend existing transaction and - // run without a transaction in scope - newTransaction = false; - suspended = t; - t = null; - transactionScopeManager.replace(null); - - } else { - // create a new Transaction based on TxType and t - newTransaction = createNewTransaction(t, txScope); - - if (newTransaction) { - // suspend existing transaction (if there is one) - suspended = t; - - // create a new transaction - int isoLevel = -1; - TxIsolation isolation = txScope.getIsolation(); - if (isolation != null) { - isoLevel = isolation.getLevel(); - } - t = transactionManager.createTransaction(txScope.getProfileId(), true, isoLevel); - // note ScopeTrans.onFinally() restores the suspended transaction - transactionScopeManager.replace(t); - } - } - - return new ScopeTrans(rollbackOnChecked, newTransaction, t, txScope, suspended, transactionScopeManager); + @Override + public void scopedTransactionExit(Object returnOrThrowable, int opCode) { + ScopedTransaction st = (ScopedTransaction) transactionScopeManager.getScoped(); + st.complete(returnOrThrowable, opCode); } /** @@ -828,9 +791,52 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public Transaction beginTransaction(TxScope scope) { - ScopeTrans scopeTrans = createScopeTrans(scope); - return new ScopedTransaction(scopeTrans); + public Transaction beginTransaction(TxScope txScope) { + return scopedTransaction(txScope); + } + + /** + * Create a Scoped transaction which internally can 'nest' transactions on it's own stack. + */ + ScopedTransaction scopedTransaction(TxScope txScope) { + + txScope = initTxScope(txScope); + + boolean setToScope = false; + ScopedTransaction txnContainer = (ScopedTransaction) transactionScopeManager.get(); + if (txnContainer == null) { + setToScope = true; + txnContainer = new ScopedTransaction(transactionScopeManager); + } + + SpiTransaction transaction = txnContainer.current(); + + boolean createTransaction; + if (txScope.getType() == TxType.NOT_SUPPORTED) { + createTransaction = false; + transaction = null; + } else { + createTransaction = createNewTransaction(transaction, txScope); + if (createTransaction) { + transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel()); + } + } + + txnContainer.push(new ScopeTrans(rollbackOnChecked, createTransaction, transaction, txScope)); + if (setToScope) { + transactionScopeManager.set(txnContainer); + } + return txnContainer; + } + + private TxScope initTxScope(TxScope txScope) { + if (txScope == null) { + return new TxScope(); + } else { + // check for implied batch mode via setting batchSize + txScope.checkBatchMode(); + return txScope; + } } /** @@ -842,7 +848,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public Transaction beginTransaction(TxIsolation isolation) { // start an explicit transaction - SpiTransaction t = transactionManager.createTransaction(0,true, isolation.getLevel()); + SpiTransaction t = transactionManager.createTransaction(0, true, isolation.getLevel()); try { transactionScopeManager.set(t); } catch (PersistenceException existingTransactionError) { @@ -1717,7 +1723,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public List draftRestore(Query query, Transaction transaction) { - return executeInTrans((txn)-> persister.draftRestore(query, txn), transaction); + return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction); } @Override @@ -2063,7 +2069,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { transactionManager.remoteTransactionEvent(event); } - private

P executeInTrans(Function fun, Transaction t) { + private

P executeInTrans(Function fun, Transaction t) { ObtainedTransaction wrap = initTransIfRequired(t); try { P result = fun.apply(wrap.transaction()); @@ -2094,7 +2100,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public SpiTransaction beginServerTransaction() { - SpiTransaction t = transactionManager.createTransaction(0,false, -1); + SpiTransaction t = transactionManager.createTransaction(0, false, -1); transactionScopeManager.set(t); return t; } diff --git a/src/main/java/io/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java b/src/main/java/io/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java index 3d9945ac5..0f1083346 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/DefaultTransactionScopeManager.java @@ -22,6 +22,11 @@ public class DefaultTransactionScopeManager extends TransactionScopeManager { DefaultTransactionThreadLocal.end(serverName); } + @Override + public SpiTransaction getScoped() { + return DefaultTransactionThreadLocal.get(serverName); + } + @Override public SpiTransaction get() { SpiTransaction t = DefaultTransactionThreadLocal.get(serverName); diff --git a/src/main/java/io/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java b/src/main/java/io/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java index da24c50e2..a3c9e57a8 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/ExternalTransactionScopeManager.java @@ -32,8 +32,12 @@ public class ExternalTransactionScopeManager extends TransactionScopeManager { } @Override - public SpiTransaction get() { + public SpiTransaction getScoped() { + return get(); + } + @Override + public SpiTransaction get() { return (SpiTransaction) externalManager.getCurrentTransaction(); } diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionScopeManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionScopeManager.java index e833f82bc..791c491bb 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionScopeManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionScopeManager.java @@ -17,6 +17,11 @@ public abstract class TransactionScopeManager implements SpiTransactionScopeMana this.serverName = transactionManager.getServerName(); } + /** + * Return the current Transaction allowing it to be inactive. + */ + public abstract SpiTransaction getScoped(); + /** * Return the current Transaction for this serverName and Thread. */ diff --git a/src/main/resources/META-INF/ebean-version.mf b/src/main/resources/META-INF/ebean-version.mf index c8fd396a6..851cd0b1a 100644 --- a/src/main/resources/META-INF/ebean-version.mf +++ b/src/main/resources/META-INF/ebean-version.mf @@ -1 +1 @@ -api-version: 11.3 +api-version: 11.4 diff --git a/src/test/java/io/ebeaninternal/api/HelpScopeTransTest.java b/src/test/java/io/ebeaninternal/api/HelpScopeTransTest.java new file mode 100644 index 000000000..a1bec1c87 --- /dev/null +++ b/src/test/java/io/ebeaninternal/api/HelpScopeTransTest.java @@ -0,0 +1,36 @@ +package io.ebeaninternal.api; + +import io.ebean.Ebean; +import io.ebean.TxScope; +import org.junit.Test; +import org.tests.model.basic.Contact; +import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; +import org.tests.model.basic.UUOne; + +public class HelpScopeTransTest { + + @Test + public void begin() throws Exception { + + ResetBasicData.reset(); + + HelpScopeTrans.enter(TxScope.required()); + HelpScopeTrans.enter(TxScope.required()); + + Ebean.find(Customer.class).findList(); + + HelpScopeTrans.enter(TxScope.required()); + Ebean.find(Contact.class).findList(); + + HelpScopeTrans.exit(null, 1); + + UUOne one = new UUOne(); + one.setName("junk"); + Ebean.save(one); + + HelpScopeTrans.exit(null, 1); + HelpScopeTrans.exit(null, 1); + } + +} diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index df53b34a9..a880c246e 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -53,6 +53,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } + @Override + public void scopedTransactionEnter(TxScope txScope) { + + } + + @Override + public void scopedTransactionExit(Object returnOrThrowable, int opCode) { + + } + @Override public Object currentTenantId() { return null; @@ -163,11 +173,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } - @Override - public ScopeTrans createScopeTrans(TxScope txScope) { - return null; - } - @Override public SpiTransaction createQueryTransaction(Object tenantId) { return null;