From 67ae8e7430ed8222ba7582fcf3dfd8ce62b62c5d Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 4 Nov 2007 00:36:29 +0000 Subject: Reduce creation of unnecessary nested transactions. Added feature to JDBCRepository to suppress Storable reloading after insert or update. --- .../carbonado/gen/MasterStorableGenerator.java | 68 +++++++++++- .../amazon/carbonado/repo/jdbc/JDBCRepository.java | 57 +++++----- .../carbonado/repo/jdbc/JDBCRepositoryBuilder.java | 31 ++++++ .../carbonado/repo/jdbc/JDBCStorableGenerator.java | 116 ++++++++++++++------- .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 5 +- .../carbonado/repo/jdbc/JDBCTransaction.java | 46 +++++--- .../repo/jdbc/JDBCTransactionManager.java | 7 +- .../carbonado/repo/jdbc/LoggingConnection.java | 24 +++++ .../amazon/carbonado/spi/TransactionManager.java | 38 +++++-- 9 files changed, 297 insertions(+), 95 deletions(-) (limited to 'src/main/java/com/amazon/carbonado') diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java index b9da366..d8d2901 100644 --- a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java @@ -36,6 +36,7 @@ import org.cojen.util.KeyFactory; import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.ConstraintException; +import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.OptimisticLockException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Repository; @@ -795,7 +796,7 @@ public final class MasterStorableGenerator { } /** - * Generates code to enter a transaction, if required. + * Generates code to enter a transaction, if required and if none in progress. * * @param opType type of operation, Insert, Update, or Delete * @param txnVar required variable of type Transaction for storing transaction @@ -806,26 +807,59 @@ public final class MasterStorableGenerator { return null; } - // txn = masterSupport.getRootRepository().enterTransaction(); + // Repository repo = masterSupport.getRootRepository(); TypeDesc repositoryType = TypeDesc.forClass(Repository.class); TypeDesc transactionType = TypeDesc.forClass(Transaction.class); TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); + TypeDesc isolationLevelType = TypeDesc.forClass(IsolationLevel.class); b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.invokeInterface(masterSupportType, "getRootRepository", repositoryType, null); - b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, - transactionType, null); - b.storeLocal(txnVar); + if (requiresTxnForUpdate(opType)) { + // Always create nested transaction. + + // txn = repo.enterTransaction(); // txn.setForUpdate(true); + + b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, + transactionType, null); + b.storeLocal(txnVar); b.loadLocal(txnVar); b.loadConstant(true); b.invokeInterface(transactionType, SET_FOR_UPDATE_METHOD_NAME, null, new TypeDesc[] {TypeDesc.BOOLEAN}); + } else { + LocalVariable repoVar = b.createLocalVariable(null, repositoryType); + b.storeLocal(repoVar); + + // if (repo.getTransactionIsolationLevel() != null) { + // txn = null; + // } else { + // txn = repo.enterTransaction(); + // } + + b.loadLocal(repoVar); + b.invokeInterface(repositoryType, GET_TRANSACTION_ISOLATION_LEVEL_METHOD_NAME, + isolationLevelType, null); + Label notInTxn = b.createLabel(); + b.ifNullBranch(notInTxn, true); + + b.loadNull(); + Label storeTxn = b.createLabel(); + b.branch(storeTxn); + + notInTxn.setLocation(); + b.loadLocal(repoVar); + b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, + transactionType, null); + + storeTxn.setLocation(); + b.storeLocal(txnVar); } return b.createLabel().setLocation(); @@ -879,12 +913,24 @@ public final class MasterStorableGenerator { TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + Label noTxn = b.createLabel(); + + if (!requiresTxnForUpdate(opType)) { + // Might be null, if transaction was already in progress. If + // requires transaction for update, then a new transaction is + // always created. + b.loadLocal(txnVar); + b.ifNullBranch(noTxn, true); + } + // txn.commit(); // txn.exit(); b.loadLocal(txnVar); b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); b.loadLocal(txnVar); b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + + noTxn.setLocation(); } /** @@ -898,9 +944,21 @@ public final class MasterStorableGenerator { TypeDesc transactionType = TypeDesc.forClass(Transaction.class); + Label noTxn = b.createLabel(); + + if (!requiresTxnForUpdate(opType)) { + // Might be null, if transaction was already in progress. If + // requires transaction for update, then a new transaction is + // always created. + b.loadLocal(txnVar); + b.ifNullBranch(noTxn, true); + } + // txn.exit(); b.loadLocal(txnVar); b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); + + noTxn.setLocation(); } /** diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java index 9e54694..0d93570 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java @@ -176,6 +176,9 @@ public class JDBCRepository extends AbstractRepository // Maps Storable types which should have automatic version management. private Map mAutoVersioningMap; + // Maps Storable types which should not auto reload after insert or update. + private Map mSuppressReloadMap; + // Track all open connections so that they can be closed when this // repository is closed. private Map mOpenConnections; @@ -217,6 +220,7 @@ public class JDBCRepository extends AbstractRepository String catalog, String schema, Integer fetchSize, Map autoVersioningMap, + Map suppressReloadMap, String sequenceSelectStatement, boolean forceStoredSequence) throws RepositoryException { @@ -234,6 +238,7 @@ public class JDBCRepository extends AbstractRepository mFetchSize = fetchSize; mAutoVersioningMap = autoVersioningMap; + mSuppressReloadMap = suppressReloadMap; mOpenConnections = new IdentityHashMap(); mOpenConnectionsLock = new ReentrantLock(true); @@ -291,7 +296,11 @@ public class JDBCRepository extends AbstractRepository } catch (SQLException e) { throw toRepositoryException(e); } finally { - forceYieldConnection(con); + try { + closeConnection(con); + } catch (SQLException e) { + // Don't care. + } } mSupportStrategy = JDBCSupportStrategy.createStrategy(this); @@ -483,34 +492,16 @@ public class JDBCRepository extends AbstractRepository public void yieldConnection(Connection con) throws FetchException { try { if (con.getAutoCommit()) { - mOpenConnectionsLock.lock(); - try { - if (mOpenConnections != null) { - mOpenConnections.remove(con); - } - } finally { - mOpenConnectionsLock.unlock(); - } - // Close connection outside lock section since it may block. - if (con.getTransactionIsolation() != mJdbcDefaultIsolationLevel) { - con.setTransactionIsolation(mJdbcDefaultIsolationLevel); - } - con.close(); + closeConnection(con); } - - // Connections which aren't auto-commit are in a transaction. - // When transaction is finished, JDBCTransactionManager switches - // connection back to auto-commit and calls yieldConnection. + // Connections which aren't auto-commit are in a transaction. Keep + // them around instead of closing them. } catch (Exception e) { throw toFetchException(e); } } - /** - * Yields connection without attempting to restore isolation level. Ignores - * any exceptions too. - */ - private void forceYieldConnection(Connection con) { + void closeConnection(Connection con) throws SQLException { mOpenConnectionsLock.lock(); try { if (mOpenConnections != null) { @@ -520,11 +511,7 @@ public class JDBCRepository extends AbstractRepository mOpenConnectionsLock.unlock(); } // Close connection outside lock section since it may block. - try { - con.close(); - } catch (SQLException e) { - // Don't care. - } + con.close(); } boolean supportsSavepoints() { @@ -693,7 +680,19 @@ public class JDBCRepository extends AbstractRepository } } - return new JDBCStorage(this, info, autoVersioning); + Boolean suppressReload = false; + if (mSuppressReloadMap != null) { + suppressReload = mSuppressReloadMap.get(type.getName()); + if (suppressReload == null) { + // No explicit setting, so check wildcard setting. + suppressReload = mSuppressReloadMap.get(null); + if (suppressReload == null) { + suppressReload = false; + } + } + } + + return new JDBCStorage(this, info, autoVersioning, suppressReload); } protected SequenceValueProducer createSequenceValueProducer(String name) diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java index 12e21cc..1fbe2dd 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java @@ -72,6 +72,7 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { private String mPassword; private Integer mFetchSize; private Map mAutoVersioningMap; + private Map mSuppressReloadMap; private String mSequenceSelectStatement; private boolean mForceStoredSequence; @@ -86,6 +87,7 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { mCatalog, mSchema, mFetchSize, getAutoVersioningMap(), + getSuppressReloadMap(), mSequenceSelectStatement, mForceStoredSequence); rootRef.set(repo); return repo; @@ -320,6 +322,35 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { return new HashMap(mAutoVersioningMap); } + /** + * By default, JDBCRepository reloads Storables after every insert or + * update. This ensures that any applied defaults or triggered changes are + * available to the Storable. If the database has no such defaults or + * triggers, suppressing reload can improve performance. + * + *

Note: If Storable has a version property and auto versioning is not + * enabled, or if the Storable has any automatic properties, the Storable + * might still be reloaded. + * + * @param suppress true to suppress, false to unsuppress + * @param className name of Storable type to suppress reload for; pass null + * to suppress all + * @since 1.1.3 + */ + public void setSuppressReload(boolean suppress, String className) { + if (mSuppressReloadMap == null) { + mSuppressReloadMap = new HashMap(); + } + mSuppressReloadMap.put(className, suppress); + } + + private Map getSuppressReloadMap() { + if (mSuppressReloadMap == null) { + return null; + } + return new HashMap(mSuppressReloadMap); + } + /** * Returns the native sequence select statement, which is null if the * default is chosen. diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java index ad568d8..d136206 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java @@ -90,18 +90,19 @@ class JDBCStorableGenerator { } static Class getGeneratedClass(JDBCStorableInfo info, - boolean autoVersioning) + boolean autoVersioning, + boolean suppressReload) throws SupportException { - Object key = KeyFactory.createKey(new Object[] {info, autoVersioning}); + Object key = KeyFactory.createKey(new Object[] {info, autoVersioning, suppressReload}); synchronized (cCache) { Class generatedClass = (Class) cCache.get(key); if (generatedClass != null) { return generatedClass; } - generatedClass = - new JDBCStorableGenerator(info, autoVersioning).generateAndInjectClass(); + generatedClass = new JDBCStorableGenerator(info, autoVersioning, suppressReload) + .generateAndInjectClass(); cCache.put(key, generatedClass); return generatedClass; } @@ -110,13 +111,15 @@ class JDBCStorableGenerator { private final Class mStorableType; private final JDBCStorableInfo mInfo; private final boolean mAutoVersioning; + private final boolean mSuppressReload; private final Map> mAllProperties; private final ClassLoader mParentClassLoader; private final ClassInjector mClassInjector; private final ClassFile mClassFile; - private JDBCStorableGenerator(JDBCStorableInfo info, boolean autoVersioning) + private JDBCStorableGenerator(JDBCStorableInfo info, + boolean autoVersioning, boolean suppressReload) throws SupportException { mStorableType = info.getStorableType(); @@ -130,6 +133,35 @@ class JDBCStorableGenerator { MasterFeature.INSERT_TXN, // Required because of reload after insert. MasterFeature.UPDATE_TXN); // Required because of reload after update. + if (suppressReload) { + // No need to be in a transaction if reload never happens. + honorSuppression: { + Map> identityProperties = + info.getIdentityProperties(); + + for (JDBCStorableProperty prop : mAllProperties.values()) { + if (prop.isAutomatic() && !identityProperties.containsKey(prop.getName())) { + // Might still need to reload. This could be determined + // dynamically, but this is an optimization that be + // implemented later. + // TODO: leave suppressReload alone and perform dynamic check + suppressReload = false; + break honorSuppression; + } + if (prop.isVersion() && !mAutoVersioning) { + // Always need to reload for version. + suppressReload = false; + break honorSuppression; + } + } + + features.remove(MasterFeature.INSERT_TXN); + features.remove(MasterFeature.UPDATE_TXN); + } + } + + mSuppressReload = suppressReload; + final Class abstractClass = MasterStorableGenerator.getAbstractClass(mStorableType, features); @@ -711,23 +743,25 @@ class JDBCStorableGenerator { closeStatement(b, psVar, tryAfterPs); - // Immediately reload object, to ensure that any database supplied - // default values are properly retrieved. Since INSERT_TXN is - // enabled, superclass ensures that transaction is still in - // progress at this point. + if (!mSuppressReload) { + // Immediately reload object, to ensure that any database supplied + // default values are properly retrieved. Since INSERT_TXN is + // enabled, superclass ensures that transaction is still in + // progress at this point. - b.loadThis(); - b.loadLocal(repoVar); - b.loadLocal(conVar); - if (lobArrayVar == null) { - b.loadNull(); - } else { - b.loadLocal(lobArrayVar); + b.loadThis(); + b.loadLocal(repoVar); + b.loadLocal(conVar); + if (lobArrayVar == null) { + b.loadNull(); + } else { + b.loadLocal(lobArrayVar); + } + b.invokeVirtual(MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, + TypeDesc.BOOLEAN, + new TypeDesc[] {jdbcRepoType, connectionType, lobArrayType}); + b.pop(); } - b.invokeVirtual(MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, - TypeDesc.BOOLEAN, - new TypeDesc[] {jdbcRepoType, connectionType, lobArrayType}); - b.pop(); // Note: yieldConAndHandleException is not called, allowing any // SQLException to be thrown. The insert or tryInsert methods must handle it. @@ -787,8 +821,8 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - // Assume database trigger manages version. if (property.isVersion() && !mAutoVersioning) { + // Assume database trigger manages version. continue; } @@ -938,8 +972,8 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - // Assume database trigger manages version. if (property.isVersion() && !mAutoVersioning) { + // Assume database trigger manages version. continue; } @@ -1061,26 +1095,28 @@ class JDBCStorableGenerator { } } - // Immediately reload object, to ensure that any database supplied - // default values are properly retrieved. Since UPDATE_TXN is - // enabled, superclass ensures that transaction is still in - // progress at this point. - doReload.setLocation(); - b.loadThis(); - b.loadLocal(repoVar); - b.loadLocal(conVar); - if (lobArrayVar == null) { - b.loadNull(); - } else { - b.loadLocal(lobArrayVar); + if (!mSuppressReload) { + // Immediately reload object, to ensure that any database supplied + // default values are properly retrieved. Since UPDATE_TXN is + // enabled, superclass ensures that transaction is still in + // progress at this point. + + b.loadThis(); + b.loadLocal(repoVar); + b.loadLocal(conVar); + if (lobArrayVar == null) { + b.loadNull(); + } else { + b.loadLocal(lobArrayVar); + } + b.invokeVirtual(MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, + TypeDesc.BOOLEAN, + new TypeDesc[] {jdbcRepoType, connectionType, lobArrayType}); + // Even though a boolean is returned, the actual value for true and + // false is an int, 1 or 0. + b.storeLocal(updateCount); } - b.invokeVirtual(MasterStorableGenerator.DO_TRY_LOAD_MASTER_METHOD_NAME, - TypeDesc.BOOLEAN, - new TypeDesc[] {jdbcRepoType, connectionType, lobArrayType}); - // Even though a boolean is returned, the actual value for true and - // false is an int, 1 or 0. - b.storeLocal(updateCount); skipReload.setLocation(); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 2a1212a..393986e 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -90,7 +90,8 @@ class JDBCStorage extends StandardQueryFactory final TriggerManager mTriggerManager; - JDBCStorage(JDBCRepository repository, JDBCStorableInfo info, boolean autoVersioning) + JDBCStorage(JDBCRepository repository, JDBCStorableInfo info, + boolean autoVersioning, boolean suppressReload) throws SupportException, RepositoryException { super(info.getStorableType()); @@ -99,7 +100,7 @@ class JDBCStorage extends StandardQueryFactory mInfo = info; Class generatedStorableClass = JDBCStorableGenerator - .getGeneratedClass(info, autoVersioning); + .getGeneratedClass(info, autoVersioning, suppressReload); mInstanceFactory = QuickConstructorGenerator .getInstance(generatedStorableClass, InstanceFactory.class); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransaction.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransaction.java index 832745b..07fc04e 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransaction.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransaction.java @@ -39,13 +39,18 @@ class JDBCTransaction { // avoid a round trip call to the remote database. private static final int LEVEL_NOT_CHANGED = -1; + private final boolean mIsNested; private final Connection mConnection; private final int mOriginalLevel; + + private boolean mReady = true; + private Savepoint mSavepoint; private List mRegisteredLobs; JDBCTransaction(Connection con) { + mIsNested = false; mConnection = con; // Don't change level upon abort. mOriginalLevel = LEVEL_NOT_CHANGED; @@ -55,6 +60,7 @@ class JDBCTransaction { * Construct a nested transaction. */ JDBCTransaction(JDBCTransaction parent, IsolationLevel level) throws SQLException { + mIsNested = true; mConnection = parent.mConnection; if (level == null) { @@ -83,14 +89,20 @@ class JDBCTransaction { return mConnection; } + void reuse() throws SQLException { + if (mIsNested && mSavepoint == null) { + mSavepoint = mConnection.setSavepoint(); + } + mReady = true; + } + void commit() throws SQLException { - if (mSavepoint == null) { - mConnection.commit(); + if (mIsNested) { + mSavepoint = null; } else { - // Don't commit, make a new savepoint. Root transaction has no - // savepoint, and so it will do the real commit. - mSavepoint = mConnection.setSavepoint(); + mConnection.commit(); } + mReady = false; } /** @@ -104,12 +116,16 @@ class JDBCTransaction { } mRegisteredLobs = null; } - if (mSavepoint == null) { - mConnection.rollback(); - mConnection.setAutoCommit(true); - return mConnection; - } else { - mConnection.rollback(mSavepoint); + + if (mIsNested) { + if (mReady) { + if (mSavepoint != null) { + mConnection.rollback(mSavepoint); + mSavepoint = null; + } + mReady = false; + } + if (mOriginalLevel != LEVEL_NOT_CHANGED) { if (mOriginalLevel == Connection.TRANSACTION_NONE) { mConnection.setAutoCommit(true); @@ -117,8 +133,14 @@ class JDBCTransaction { mConnection.setTransactionIsolation(mOriginalLevel); } } - mSavepoint = null; + return null; + } else { + if (mReady) { + mConnection.rollback(); + mReady = false; + } + return mConnection; } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java index 862152d..a70e4c3 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java @@ -78,6 +78,11 @@ class JDBCTransactionManager extends TransactionManager { return new JDBCTransaction(repo.getConnectionForTxn(level)); } + @Override + protected void reuseTxn(JDBCTransaction txn) throws SQLException { + txn.reuse(); + } + protected boolean commitTxn(JDBCTransaction txn) throws PersistException { try { txn.commit(); @@ -95,7 +100,7 @@ class JDBCTransactionManager extends TransactionManager { if (repo == null) { con.close(); } else { - repo.yieldConnection(con); + repo.closeConnection(con); } } } catch (Throwable e) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java index 8ca4226..3f79473 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java @@ -126,6 +126,7 @@ class LoggingConnection implements Connection { } public void setAutoCommit(boolean autoCommit) throws SQLException { + mLog.debug("Connection.setAutoCommit(" + autoCommit + ')'); mCon.setAutoCommit(autoCommit); } @@ -174,6 +175,29 @@ class LoggingConnection implements Connection { } public void setTransactionIsolation(int level) throws SQLException { + String levelStr; + switch (level) { + default: + levelStr = String.valueOf(level); + break; + case Connection.TRANSACTION_NONE: + levelStr = "TRANSACTION_NONE"; + break; + case Connection.TRANSACTION_READ_UNCOMMITTED: + levelStr = "TRANSACTION_READ_UNCOMMITTED"; + break; + case Connection.TRANSACTION_READ_COMMITTED: + levelStr = "TRANSACTION_READ_COMMITTED"; + break; + case Connection.TRANSACTION_REPEATABLE_READ: + levelStr = "TRANSACTION_REPEATABLE_READ"; + break; + case Connection.TRANSACTION_SERIALIZABLE: + levelStr = "TRANSACTION_SERIALIZABLE"; + break; + } + + mLog.debug("Connection.setTransactionIsolation(" + levelStr + ')'); mCon.setTransactionIsolation(level); } diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java index f16f548..fe08a01 100644 --- a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java +++ b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java @@ -249,7 +249,7 @@ public abstract class TransactionManager { /** * Returns null if no transaction is in progress. * - * @throws Exception thrown by createTxn + * @throws Exception thrown by createTxn or reuseTxn */ public Txn getTxn() throws Exception { mLock.lock(); @@ -341,6 +341,17 @@ public abstract class TransactionManager { return createTxn(parent, level); } + /** + * Called when a transaction is about to be reused. The default + * implementation of this method does nothing. Override if any preparation + * is required to ready a transaction for reuse. + * + * @param txn transaction to reuse, never null + * @since 1.1.3 + */ + protected void reuseTxn(Txn txn) throws Exception { + } + /** * Commits and closes the given internal transaction. * @@ -502,13 +513,28 @@ public abstract class TransactionManager { // Caller must hold mLock. Txn getTxn() throws Exception { - if (mTxn == null) { - TransactionManager txnMgr = mTxnMgr; - Txn parent = (mParent == null || mTop) ? null : mParent.getTxn(); + TransactionManager txnMgr = mTxnMgr; + if (mTxn != null) { + txnMgr.reuseTxn(mTxn); + } else { + Txn parentTxn; + if (mParent == null || mTop) { + parentTxn = null; + } else if ((parentTxn = mParent.mTxn) == null) { + // No point in creating nested transaction if parent + // has never been used. Create parent transaction + // and use it in child transaction, just like a fake + // nested transaction. + if ((parentTxn = mParent.getTxn()) != null) { + return mTxn = parentTxn; + } + // Isolation level of parent is none, so proceed to create + // a real transaction. + } if (mTimeoutUnit == null) { - mTxn = txnMgr.createTxn(parent, mLevel); + mTxn = txnMgr.createTxn(parentTxn, mLevel); } else { - mTxn = txnMgr.createTxn(parent, mLevel, mDesiredLockTimeout, mTimeoutUnit); + mTxn = txnMgr.createTxn(parentTxn, mLevel, mDesiredLockTimeout, mTimeoutUnit); } } return mTxn; -- cgit v1.2.3