summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE-NOTES.txt6
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java68
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java57
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java31
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java116
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java5
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransaction.java46
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java7
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java24
-rw-r--r--src/main/java/com/amazon/carbonado/spi/TransactionManager.java38
10 files changed, 303 insertions, 95 deletions
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index c844408..a56b521 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -43,6 +43,12 @@ Carbonado change history
- Added support for "where exists" in queries via new syntax.
- Added support for outer joins via new syntax.
+1.1.2 to 1.1.3
+-------------------------------
+- Reduce creation of unnecessary nested transactions.
+- Added feature to JDBCRepository to suppress Storable reloading after insert
+ or update.
+
1.1 to 1.1.2
-------------------------------
- Fixed query engine rule which sometimes favored index with incorrect ordering.
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<S extends Storable> {
}
/**
- * 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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
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<JDBCTransaction>
// Maps Storable types which should have automatic version management.
private Map<String, Boolean> mAutoVersioningMap;
+ // Maps Storable types which should not auto reload after insert or update.
+ private Map<String, Boolean> mSuppressReloadMap;
+
// Track all open connections so that they can be closed when this
// repository is closed.
private Map<Connection, Object> mOpenConnections;
@@ -217,6 +220,7 @@ public class JDBCRepository extends AbstractRepository<JDBCTransaction>
String catalog, String schema,
Integer fetchSize,
Map<String, Boolean> autoVersioningMap,
+ Map<String, Boolean> suppressReloadMap,
String sequenceSelectStatement, boolean forceStoredSequence)
throws RepositoryException
{
@@ -234,6 +238,7 @@ public class JDBCRepository extends AbstractRepository<JDBCTransaction>
mFetchSize = fetchSize;
mAutoVersioningMap = autoVersioningMap;
+ mSuppressReloadMap = suppressReloadMap;
mOpenConnections = new IdentityHashMap<Connection, Object>();
mOpenConnectionsLock = new ReentrantLock(true);
@@ -291,7 +296,11 @@ public class JDBCRepository extends AbstractRepository<JDBCTransaction>
} 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<JDBCTransaction>
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<JDBCTransaction>
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<JDBCTransaction>
}
}
- return new JDBCStorage<S>(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<S>(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<String, Boolean> mAutoVersioningMap;
+ private Map<String, Boolean> 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;
@@ -321,6 +323,35 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder {
}
/**
+ * 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.
+ *
+ * <p>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<String, Boolean>();
+ }
+ mSuppressReloadMap.put(className, suppress);
+ }
+
+ private Map<String, Boolean> getSuppressReloadMap() {
+ if (mSuppressReloadMap == null) {
+ return null;
+ }
+ return new HashMap<String, Boolean>(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<S extends Storable> {
}
static <S extends Storable> Class<? extends S> getGeneratedClass(JDBCStorableInfo<S> 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<? extends S> generatedClass = (Class<? extends S>) cCache.get(key);
if (generatedClass != null) {
return generatedClass;
}
- generatedClass =
- new JDBCStorableGenerator<S>(info, autoVersioning).generateAndInjectClass();
+ generatedClass = new JDBCStorableGenerator<S>(info, autoVersioning, suppressReload)
+ .generateAndInjectClass();
cCache.put(key, generatedClass);
return generatedClass;
}
@@ -110,13 +111,15 @@ class JDBCStorableGenerator<S extends Storable> {
private final Class<S> mStorableType;
private final JDBCStorableInfo<S> mInfo;
private final boolean mAutoVersioning;
+ private final boolean mSuppressReload;
private final Map<String, ? extends JDBCStorableProperty<S>> mAllProperties;
private final ClassLoader mParentClassLoader;
private final ClassInjector mClassInjector;
private final ClassFile mClassFile;
- private JDBCStorableGenerator(JDBCStorableInfo<S> info, boolean autoVersioning)
+ private JDBCStorableGenerator(JDBCStorableInfo<S> info,
+ boolean autoVersioning, boolean suppressReload)
throws SupportException
{
mStorableType = info.getStorableType();
@@ -130,6 +133,35 @@ class JDBCStorableGenerator<S extends Storable> {
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<String, JDBCStorableProperty<S>> identityProperties =
+ info.getIdentityProperties();
+
+ for (JDBCStorableProperty<S> 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<? extends S> abstractClass =
MasterStorableGenerator.getAbstractClass(mStorableType, features);
@@ -711,23 +743,25 @@ class JDBCStorableGenerator<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
}
}
- // 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<S extends Storable> extends StandardQueryFactory<S>
final TriggerManager<S> mTriggerManager;
- JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info, boolean autoVersioning)
+ JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info,
+ boolean autoVersioning, boolean suppressReload)
throws SupportException, RepositoryException
{
super(info.getStorableType());
@@ -99,7 +100,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
mInfo = info;
Class<? extends S> 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<JDBCLob> 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<JDBCTransaction> {
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<JDBCTransaction> {
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<Txn> {
/**
* 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();
@@ -342,6 +342,17 @@ public abstract class TransactionManager<Txn> {
}
/**
+ * 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.
*
* @return true if transaction object is still valid
@@ -502,13 +513,28 @@ public abstract class TransactionManager<Txn> {
// Caller must hold mLock.
Txn getTxn() throws Exception {
- if (mTxn == null) {
- TransactionManager<Txn> txnMgr = mTxnMgr;
- Txn parent = (mParent == null || mTop) ? null : mParent.getTxn();
+ TransactionManager<Txn> 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;