You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2018/03/27 10:06:34 UTC
cayenne git commit: CAY-2415 Transaction isolation and propagation
support
Repository: cayenne
Updated Branches:
refs/heads/master 23c3a8c5d -> 8c8a2f0be
CAY-2415 Transaction isolation and propagation support
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/8c8a2f0b
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/8c8a2f0b
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/8c8a2f0b
Branch: refs/heads/master
Commit: 8c8a2f0bea2679cab65d9a1de05d5a72e883dcb7
Parents: 23c3a8c
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Mar 27 13:06:22 2018 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Mar 27 13:06:22 2018 +0300
----------------------------------------------------------------------
RELEASE-NOTES.txt | 1 +
.../org/apache/cayenne/tx/BaseTransaction.java | 25 ++-
.../apache/cayenne/tx/CayenneTransaction.java | 11 +-
.../tx/DefaultTransactionDescriptor.java | 36 ++++
.../cayenne/tx/DefaultTransactionFactory.java | 12 +-
.../cayenne/tx/DefaultTransactionManager.java | 168 +++++++++++++++----
.../apache/cayenne/tx/ExternalTransaction.java | 8 +
.../cayenne/tx/TransactionDescriptor.java | 93 ++++++++++
.../apache/cayenne/tx/TransactionFactory.java | 9 +
.../apache/cayenne/tx/TransactionManager.java | 30 ++++
.../cayenne/tx/TransactionPropagation.java | 41 +++++
.../cayenne/tx/TransactionalOperation.java | 1 +
.../configuration/server/ServerRuntimeTest.java | 3 +
.../cayenne/tx/DefaultTransactionManagerIT.java | 151 ++++++++++++++---
.../cayenne/tx/TransactionIsolationIT.java | 137 +++++++++++++++
.../tx/TransactionPropagationRollbackIT.java | 159 ++++++++++++++++++
.../cayenne/unit/OracleUnitDbAdapter.java | 5 +
.../cayenne/unit/PostgresUnitDbAdapter.java | 5 +
.../org/apache/cayenne/unit/UnitDbAdapter.java | 4 +
19 files changed, 838 insertions(+), 61 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 5228c96..0ce5f8d 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -27,6 +27,7 @@ CAY-2406 Add prefetch-related API to SQLSelect
CAY-2407 Modeler: add prefetch support for the SQLTemplate query
CAY-2410 Add prefetch type support for SQLTemplate query and SelectQuery
CAY-2414 Modeler: new icon design
+CAY-2415 Transaction isolation and propagation support
Bug Fixes:
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
index 9b7ac0f..5c9793e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
@@ -28,6 +28,8 @@ import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
+import org.apache.cayenne.CayenneRuntimeException;
+
/**
* A Cayenne transaction. Currently supports managing JDBC connections.
*
@@ -51,6 +53,8 @@ public abstract class BaseTransaction implements Transaction {
protected Map<String, Connection> connections;
protected Collection<TransactionListener> listeners;
protected int status;
+ protected int defaultIsolationLevel = -1;
+ protected TransactionDescriptor descriptor;
static String decodeStatus(int status) {
switch (status) {
@@ -91,8 +95,9 @@ public abstract class BaseTransaction implements Transaction {
/**
* Creates new inactive transaction.
*/
- protected BaseTransaction() {
+ protected BaseTransaction(TransactionDescriptor descriptor) {
this.status = STATUS_NO_TRANSACTION;
+ this.descriptor = descriptor;
}
@Override
@@ -214,6 +219,15 @@ public abstract class BaseTransaction implements Transaction {
protected Connection addConnection(String connectionName, Connection connection) {
+ if(descriptor.getIsolation() != TransactionDescriptor.ISOLATION_DEFAULT) {
+ try {
+ defaultIsolationLevel = connection.getTransactionIsolation();
+ connection.setTransactionIsolation(descriptor.getIsolation());
+ } catch (SQLException ex) {
+ throw new CayenneRuntimeException("Unable to set required isolation level: " + descriptor.getIsolation(), ex);
+ }
+ }
+
TransactionConnectionDecorator wrapper = new TransactionConnectionDecorator(connection);
if (listeners != null) {
@@ -262,6 +276,15 @@ public abstract class BaseTransaction implements Transaction {
} catch (Throwable th) {
// TODO: chain exceptions...
// ignore for now
+ } finally {
+ // restore connection default isolation level ...
+ if(defaultIsolationLevel != -1) {
+ try {
+ c.setTransactionIsolation(defaultIsolationLevel);
+ } catch (SQLException ignore) {
+ // have no meaningful options here...
+ }
+ }
}
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
index 5554f07..9115b17 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
@@ -35,7 +35,15 @@ public class CayenneTransaction extends BaseTransaction {
protected JdbcEventLogger logger;
public CayenneTransaction(JdbcEventLogger logger) {
- this.logger = logger;
+ this(logger, DefaultTransactionDescriptor.getInstance());
+ }
+
+ /**
+ * @since 4.1
+ */
+ public CayenneTransaction(JdbcEventLogger jdbcEventLogger, TransactionDescriptor descriptor) {
+ super(descriptor);
+ this.logger = jdbcEventLogger;
}
@Override
@@ -127,6 +135,7 @@ public class CayenneTransaction extends BaseTransaction {
}
}
+ logger.logRollbackTransaction("transaction rolledback.");
if (deferredException != null) {
throw new CayenneRuntimeException(deferredException);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionDescriptor.java
new file mode 100644
index 0000000..bad8e3c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionDescriptor.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.tx;
+
+/**
+ * @since 4.1
+ */
+public class DefaultTransactionDescriptor extends TransactionDescriptor {
+
+ private static final DefaultTransactionDescriptor instance = new DefaultTransactionDescriptor();
+
+ public static TransactionDescriptor getInstance() {
+ return instance;
+ }
+
+ private DefaultTransactionDescriptor() {
+ super(TransactionDescriptor.ISOLATION_DEFAULT, TransactionPropagation.NESTED);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionFactory.java
index 8687204..116f7d7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionFactory.java
@@ -39,8 +39,16 @@ public class DefaultTransactionFactory implements TransactionFactory {
@Override
public Transaction createTransaction() {
- return externalTransactions ? new ExternalTransaction(jdbcEventLogger) : new CayenneTransaction(
- jdbcEventLogger);
+ return createTransaction(DefaultTransactionDescriptor.getInstance());
+ }
+
+ /**
+ * @since 4.1
+ */
+ @Override
+ public Transaction createTransaction(TransactionDescriptor descriptor) {
+ return externalTransactions ? new ExternalTransaction(jdbcEventLogger, descriptor) : new CayenneTransaction(
+ jdbcEventLogger, descriptor);
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java
index 61ed4b6..e239af7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/DefaultTransactionManager.java
@@ -18,6 +18,9 @@
****************************************************************/
package org.apache.cayenne.tx;
+import java.sql.Connection;
+import java.sql.SQLException;
+
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.log.JdbcEventLogger;
@@ -37,54 +40,149 @@ public class DefaultTransactionManager implements TransactionManager {
@Override
public <T> T performInTransaction(TransactionalOperation<T> op) {
- return performInTransaction(op, DoNothingTransactionListener.getInstance());
+ return performInTransaction(op, DoNothingTransactionListener.getInstance(), DefaultTransactionDescriptor.getInstance());
}
@Override
public <T> T performInTransaction(TransactionalOperation<T> op, TransactionListener callback) {
+ return performInTransaction(op, callback, DefaultTransactionDescriptor.getInstance());
+ }
+
+ /**
+ * @since 4.1
+ */
+ @Override
+ public <T> T performInTransaction(TransactionalOperation<T> op, TransactionDescriptor descriptor) {
+ return performInTransaction(op, DoNothingTransactionListener.getInstance(), descriptor);
+ }
- // Either join existing tx (in such case do not try to commit or rollback), or start a new tx and manage it
- // till the end
+ /**
+ * @since 4.1
+ */
+ @Override
+ public <T> T performInTransaction(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor) {
+ BaseTransactionHandler handler = getHandler(descriptor);
+ return handler.handle(op, callback, descriptor);
+ }
+
+ protected BaseTransactionHandler getHandler(TransactionDescriptor descriptor) {
+ switch (descriptor.getPropagation()) {
+ // MANDATORY requires transaction to exists
+ case MANDATORY:
+ return new MandatoryTransactionHandler(txFactory, jdbcEventLogger);
+
+ // NESTED can join existing or create new
+ case NESTED:
+ return new NestedTransactionHandler(txFactory, jdbcEventLogger);
+
+ // REQUIRES_NEW should always create new transaction
+ case REQUIRES_NEW:
+ return new RequiresNewTransactionHandler(txFactory, jdbcEventLogger);
+ }
- Transaction currentTx = BaseTransaction.getThreadTransaction();
- return (currentTx != null)
- ? performInTransaction(currentTx, op, callback)
- : performInLocalTransaction(op, callback);
+ throw new CayenneRuntimeException("Unsupported transaction propagation: " + descriptor.getPropagation());
}
- protected <T> T performInLocalTransaction(TransactionalOperation<T> op, TransactionListener callback) {
- Transaction tx = txFactory.createTransaction();
- BaseTransaction.bindThreadTransaction(tx);
- try {
- T result = performInTransaction(tx, op, callback);
- tx.commit();
- return result;
-
- } catch (CayenneRuntimeException ex) {
- tx.setRollbackOnly();
- throw ex;
- } catch (Exception ex) {
- tx.setRollbackOnly();
- throw new CayenneRuntimeException(ex);
- } finally {
- BaseTransaction.bindThreadTransaction(null);
-
- if (tx.isRollbackOnly()) {
- try {
- tx.rollback();
- } catch (Exception e) {
- // although we don't expect an exception here, print the
- // stack, as there have been some Cayenne bugs already
- // (CAY-557) that were masked by this 'catch' clause.
- jdbcEventLogger.logQueryError(e);
+ private static class NestedTransactionHandler extends BaseTransactionHandler {
+
+ private NestedTransactionHandler(TransactionFactory txFactory, JdbcEventLogger jdbcEventLogger) {
+ super(txFactory, jdbcEventLogger);
+ }
+
+ @Override
+ protected <T> T handle(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor) {
+ Transaction currentTx = BaseTransaction.getThreadTransaction();
+ if(currentTx != null) {
+ return performInTransaction(currentTx, op, callback);
+ } else {
+ return performInNewTransaction(op, callback, descriptor);
+ }
+ }
+ }
+
+ private static class MandatoryTransactionHandler extends BaseTransactionHandler {
+
+ private MandatoryTransactionHandler(TransactionFactory txFactory, JdbcEventLogger jdbcEventLogger) {
+ super(txFactory, jdbcEventLogger);
+ }
+
+ @Override
+ protected <T> T handle(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor) {
+ Transaction currentTx = BaseTransaction.getThreadTransaction();
+ if(currentTx == null) {
+ throw new CayenneRuntimeException("Transaction operation should join to existing transaction but none found.");
+ }
+ return performInTransaction(currentTx, op, callback);
+ }
+ }
+
+ private static class RequiresNewTransactionHandler extends BaseTransactionHandler {
+
+ private RequiresNewTransactionHandler(TransactionFactory txFactory, JdbcEventLogger jdbcEventLogger) {
+ super(txFactory, jdbcEventLogger);
+ }
+
+ @Override
+ protected <T> T handle(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor) {
+ Transaction currentTx = BaseTransaction.getThreadTransaction();
+ try {
+ return performInNewTransaction(op, callback, descriptor);
+ } finally {
+ if(currentTx != null) {
+ // restore old transaction, if where set
+ BaseTransaction.bindThreadTransaction(currentTx);
}
}
}
}
- protected <T> T performInTransaction(Transaction tx, TransactionalOperation<T> op, TransactionListener callback) {
- tx.addListener(callback);
- return op.perform();
+ protected static abstract class BaseTransactionHandler {
+
+ private TransactionFactory txFactory;
+ private JdbcEventLogger jdbcEventLogger;
+
+ private BaseTransactionHandler(TransactionFactory txFactory, JdbcEventLogger jdbcEventLogger) {
+ this.txFactory = txFactory;
+ this.jdbcEventLogger = jdbcEventLogger;
+ }
+
+ protected abstract <T> T handle(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor);
+
+ protected <T> T performInNewTransaction(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor) {
+ Transaction tx = txFactory.createTransaction(descriptor);
+ BaseTransaction.bindThreadTransaction(tx);
+ try {
+ T result = performInTransaction(tx, op, callback);
+ tx.commit();
+ return result;
+
+ } catch (CayenneRuntimeException ex) {
+ tx.setRollbackOnly();
+ throw ex;
+ } catch (Exception ex) {
+ tx.setRollbackOnly();
+ throw new CayenneRuntimeException(ex);
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+
+ if (tx.isRollbackOnly()) {
+ try {
+ tx.rollback();
+ } catch (Exception e) {
+ // although we don't expect an exception here, print the
+ // stack, as there have been some Cayenne bugs already
+ // (CAY-557) that were masked by this 'catch' clause.
+ jdbcEventLogger.logQueryError(e);
+ }
+ }
+ }
+ }
+
+ protected <T> T performInTransaction(Transaction tx, TransactionalOperation<T> op, TransactionListener callback) {
+ tx.addListener(callback);
+ return op.perform();
+ }
+
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/ExternalTransaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/ExternalTransaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/ExternalTransaction.java
index 8afa94f..18b6f92 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/ExternalTransaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/ExternalTransaction.java
@@ -31,6 +31,14 @@ public class ExternalTransaction extends BaseTransaction {
protected JdbcEventLogger logger;
public ExternalTransaction(JdbcEventLogger jdbcEventLogger) {
+ this(jdbcEventLogger, DefaultTransactionDescriptor.getInstance());
+ }
+
+ /**
+ * @since 4.1
+ */
+ public ExternalTransaction(JdbcEventLogger jdbcEventLogger, TransactionDescriptor descriptor) {
+ super(descriptor);
this.logger = jdbcEventLogger;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java
new file mode 100644
index 0000000..7c29700
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java
@@ -0,0 +1,93 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.tx;
+
+/**
+ *
+ * Descriptor that provide desired transaction isolation level and propagation logic.
+ *
+ * @since 4.1
+ */
+public class TransactionDescriptor {
+
+ /**
+ * Keep database default isolation level
+ */
+ public static final int ISOLATION_DEFAULT = -1;
+
+ private final int isolation;
+
+ private final TransactionPropagation propagation;
+
+ /**
+ * @param isolation one of the following <code>Connection</code> constants:
+ * <code>Connection.TRANSACTION_READ_UNCOMMITTED</code>,
+ * <code>Connection.TRANSACTION_READ_COMMITTED</code>,
+ * <code>Connection.TRANSACTION_REPEATABLE_READ</code>,
+ * <code>Connection.TRANSACTION_SERIALIZABLE</code>, or
+ * <code>TransactionDescriptor.ISOLATION_DEFAULT</code>
+ *
+ * @param propagation transaction propagation behaviour
+ *
+ * @see TransactionPropagation
+ */
+ public TransactionDescriptor(int isolation, TransactionPropagation propagation) {
+ this.isolation = isolation;
+ this.propagation = propagation;
+ }
+
+ /**
+ *
+ * Create transaction descriptor with desired isolation level and <code>NESTED</code> propagation
+ *
+ * @param isolation one of the following <code>Connection</code> constants:
+ * <code>Connection.TRANSACTION_READ_UNCOMMITTED</code>,
+ * <code>Connection.TRANSACTION_READ_COMMITTED</code>,
+ * <code>Connection.TRANSACTION_REPEATABLE_READ</code>,
+ * <code>Connection.TRANSACTION_SERIALIZABLE</code>, or
+ * <code>TransactionDescriptor.ISOLATION_DEFAULT</code>
+ */
+ public TransactionDescriptor(int isolation) {
+ this(isolation, TransactionPropagation.NESTED);
+ }
+
+ /**
+ *
+ * @param propagation transaction propagation behaviour
+ * @see TransactionPropagation
+ */
+ public TransactionDescriptor(TransactionPropagation propagation) {
+ this(ISOLATION_DEFAULT, propagation);
+ }
+
+ /**
+ * @return required isolation level
+ */
+ public int getIsolation() {
+ return isolation;
+ }
+
+ /**
+ * @return required propagation behaviour
+ */
+ public TransactionPropagation getPropagation() {
+ return propagation;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionFactory.java
index 6c31eb5..a01be38 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionFactory.java
@@ -25,4 +25,13 @@ package org.apache.cayenne.tx;
public interface TransactionFactory {
Transaction createTransaction();
+
+ /**
+ *
+ * @param descriptor with required transaction properties
+ * @return new transaction
+ *
+ * @since 4.1
+ */
+ Transaction createTransaction(TransactionDescriptor descriptor);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionManager.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionManager.java
index cb17c8f..c058850 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionManager.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionManager.java
@@ -33,6 +33,7 @@ public interface TransactionManager {
* transaction.
*
* @param op an operation to perform within the transaction.
+ * @param <T> returned value type
* @return a value returned by the "op" operation.
*/
<T> T performInTransaction(TransactionalOperation<T> op);
@@ -45,7 +46,36 @@ public interface TransactionManager {
*
* @param op an operation to perform within the transaction.
* @param callback a callback to notify as transaction progresses through stages.
+ * @param <T> returned value type
* @return a value returned by the "op" operation.
*/
<T> T performInTransaction(TransactionalOperation<T> op, TransactionListener callback);
+
+
+ /**
+ * Performs operation in a transaction which parameters described by descriptor.
+ *
+ * @param op an operation to perform within the transaction.
+ * @param descriptor transaction descriptor
+ * @param <T> result type
+ * @return a value returned by the "op" operation.
+ *
+ * @since 4.1
+ */
+ <T> T performInTransaction(TransactionalOperation<T> op, TransactionDescriptor descriptor);
+
+ /**
+ * Performs operation in a transaction which parameters described by descriptor.
+ * As transaction goes through stages, callback methods are invoked allowing the caller to customize
+ * transaction parameters.
+ *
+ * @param op an operation to perform within the transaction.
+ * @param callback a callback to notify as transaction progresses through stages.
+ * @param descriptor transaction descriptor
+ * @param <T> returned value type
+ * @return a value returned by the "op" operation.
+ *
+ * @since 4.1
+ */
+ <T> T performInTransaction(TransactionalOperation<T> op, TransactionListener callback, TransactionDescriptor descriptor);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionPropagation.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionPropagation.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionPropagation.java
new file mode 100644
index 0000000..5aea027
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionPropagation.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.tx;
+
+/**
+ * Propagation behaviour of transaction
+ */
+public enum TransactionPropagation {
+ /**
+ * Support a current transaction, throw an exception if none exists.
+ */
+ MANDATORY,
+
+ /**
+ * Execute within a nested transaction if a current transaction exists,
+ * create a new one if none exists.
+ */
+ NESTED,
+
+ /**
+ * Create a new transaction, and suspend the current transaction if one exists.
+ */
+ REQUIRES_NEW
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionalOperation.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionalOperation.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionalOperation.java
index 94f519e..a698d51 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionalOperation.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionalOperation.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.tx;
/**
* @since 4.0
*/
+@FunctionalInterface
public interface TransactionalOperation<T> {
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
index c4c4ce8..98df8bd 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.tx.BaseTransaction;
+import org.apache.cayenne.tx.TransactionDescriptor;
import org.apache.cayenne.tx.TransactionFactory;
import org.apache.cayenne.tx.TransactionalOperation;
import org.junit.Test;
@@ -42,6 +43,7 @@ import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -53,6 +55,7 @@ public class ServerRuntimeTest {
final BaseTransaction tx = mock(BaseTransaction.class);
final TransactionFactory txFactory = mock(TransactionFactory.class);
when(txFactory.createTransaction()).thenReturn(tx);
+ when(txFactory.createTransaction(any(TransactionDescriptor.class))).thenReturn(tx);
Module module = binder -> binder.bind(TransactionFactory.class).toInstance(txFactory);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/tx/DefaultTransactionManagerIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/tx/DefaultTransactionManagerIT.java b/cayenne-server/src/test/java/org/apache/cayenne/tx/DefaultTransactionManagerIT.java
index 6b9f001..0c006d0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/tx/DefaultTransactionManagerIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/DefaultTransactionManagerIT.java
@@ -18,35 +18,32 @@
****************************************************************/
package org.apache.cayenne.tx;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Test;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class DefaultTransactionManagerIT extends ServerCase {
+public class DefaultTransactionManagerIT {
@Test
public void testPerformInTransaction_Local() {
final BaseTransaction tx = mock(BaseTransaction.class);
- TransactionFactory txFactory = mock(TransactionFactory.class);
- when(txFactory.createTransaction()).thenReturn(tx);
- DefaultTransactionManager txManager = new DefaultTransactionManager(txFactory, mock(JdbcEventLogger.class));
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> tx);
final Object expectedResult = new Object();
- Object result = txManager.performInTransaction(new TransactionalOperation<Object>() {
- public Object perform() {
- assertNotNull(BaseTransaction.getThreadTransaction());
- return expectedResult;
- }
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx, BaseTransaction.getThreadTransaction());
+ return expectedResult;
});
assertSame(expectedResult, result);
@@ -56,21 +53,17 @@ public class DefaultTransactionManagerIT extends ServerCase {
public void testPerformInTransaction_ExistingTx() {
final BaseTransaction tx1 = mock(BaseTransaction.class);
- TransactionFactory txFactory = mock(TransactionFactory.class);
- when(txFactory.createTransaction()).thenReturn(tx1);
- DefaultTransactionManager txManager = new DefaultTransactionManager(txFactory, mock(JdbcEventLogger.class));
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> tx1);
final BaseTransaction tx2 = mock(BaseTransaction.class);
BaseTransaction.bindThreadTransaction(tx2);
try {
final Object expectedResult = new Object();
- Object result = txManager.performInTransaction(new TransactionalOperation<Object>() {
- public Object perform() {
- assertSame(tx2, BaseTransaction.getThreadTransaction());
- return expectedResult;
- }
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx2, BaseTransaction.getThreadTransaction());
+ return expectedResult;
});
assertSame(expectedResult, result);
@@ -79,5 +72,119 @@ public class DefaultTransactionManagerIT extends ServerCase {
}
}
+ @Test
+ public void testNestedPropagation() {
+ final BaseTransaction tx = mock(BaseTransaction.class);
+
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> tx);
+
+ try {
+ final Object expectedResult = new Object();
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx, BaseTransaction.getThreadTransaction());
+ return expectedResult;
+ },
+ new TransactionDescriptor(TransactionPropagation.NESTED)
+ );
+ assertSame(expectedResult, result);
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+ }
+
+ }
+
+ @Test(expected = CayenneRuntimeException.class)
+ public void testMandatoryPropagationNotStarted() {
+ final BaseTransaction tx = mock(BaseTransaction.class);
+
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> tx);
+
+ try {
+ final Object expectedResult = new Object();
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx, BaseTransaction.getThreadTransaction());
+ return expectedResult;
+ },
+ new TransactionDescriptor(TransactionPropagation.MANDATORY)
+ );
+ assertSame(expectedResult, result);
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+ }
+
+ }
+
+ @Test
+ public void testMandatoryPropagation() {
+ final BaseTransaction tx = mock(BaseTransaction.class);
+
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> tx);
+ BaseTransaction.bindThreadTransaction(tx);
+
+ try {
+ final Object expectedResult = new Object();
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx, BaseTransaction.getThreadTransaction());
+ return expectedResult;
+ },
+ new TransactionDescriptor(TransactionPropagation.MANDATORY)
+ );
+ assertSame(expectedResult, result);
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+ }
+
+ }
+
+ @Test
+ public void testRequiresNewPropagation() {
+ final BaseTransaction tx1 = mock(BaseTransaction.class);
+ final BaseTransaction tx2 = mock(BaseTransaction.class);
+ final AtomicInteger counter = new AtomicInteger(0);
+
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ DefaultTransactionManager txManager = createDefaultTxManager(() -> {
+ counter.incrementAndGet();
+ return tx2;
+ });
+
+ BaseTransaction.bindThreadTransaction(tx1);
+
+ try {
+ final Object expectedResult = new Object();
+ Object result = txManager.performInTransaction(() -> {
+ assertSame(tx2, BaseTransaction.getThreadTransaction());
+ return expectedResult;
+ },
+ new TransactionDescriptor(TransactionPropagation.REQUIRES_NEW)
+ );
+ assertSame(expectedResult, result);
+ assertSame(tx1, BaseTransaction.getThreadTransaction());
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+ }
+
+ }
+
+ private DefaultTransactionManager createDefaultTxManager(final Supplier<Transaction> txSupplier) {
+ return new DefaultTransactionManager(
+ createMockFactory(txSupplier),
+ mock(JdbcEventLogger.class)
+ );
+ }
+
+ private TransactionFactory createMockFactory(final Supplier<Transaction> supplier) {
+ TransactionFactory txFactory = mock(TransactionFactory.class);
+ when(txFactory.createTransaction()).thenReturn(supplier.get());
+ when(txFactory.createTransaction(any(TransactionDescriptor.class))).thenReturn(supplier.get());
+ return txFactory;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java
new file mode 100644
index 0000000..2671352
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java
@@ -0,0 +1,137 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.tx;
+
+import java.sql.Connection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.1
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class TransactionIsolationIT extends ServerCase {
+
+ private final Logger logger = LoggerFactory.getLogger(TransactionIsolationIT.class);
+
+ @Inject
+ DataContext context;
+
+ @Inject
+ ServerRuntime runtime;
+
+ @Inject
+ UnitDbAdapter unitDbAdapter;
+
+ TransactionManager manager;
+
+ @Before
+ public void initTransactionManager() {
+ // no binding in test container, get it from runtime
+ manager = runtime.getInjector().getInstance(TransactionManager.class);
+ }
+
+ @Test
+ public void testIsolationLevel() throws Exception {
+
+ if(!unitDbAdapter.supportsSerializableTransactionIsolation()) {
+ return;
+ }
+
+ TransactionDescriptor descriptor = new TransactionDescriptor(
+ Connection.TRANSACTION_REPEATABLE_READ,
+ TransactionPropagation.REQUIRES_NEW
+ );
+
+ CountDownLatch startSignal = new CountDownLatch(1);
+ CountDownLatch resumeSerializableTransaction = new CountDownLatch(1);
+ ExecutorService service = Executors.newFixedThreadPool(2);
+
+ Future<Boolean> thread1Result = service.submit(() -> {
+ try {
+ return manager.performInTransaction(() -> {
+ long result;
+ try {
+ result = ObjectSelect.query(Artist.class).selectCount(context);
+ } finally {
+ startSignal.countDown();
+ }
+ if(result != 0) {
+ logger.error("First fetch returned " + result);
+ return false;
+ }
+ try {
+ resumeSerializableTransaction.await();
+ } catch (InterruptedException e) {
+ logger.error("Resume signal await failed", e);
+ return false;
+ }
+
+ result = ObjectSelect.query(Artist.class).selectCount(context);
+ logger.info("Second fetch returned " + result);
+ return result == 0;
+ }, descriptor);
+ } catch (Exception ex) {
+ logger.error("Perform in transaction failed", ex);
+ return false;
+ }
+ });
+
+ Future<Boolean> thread2Result = service.submit(() -> {
+ try {
+ startSignal.await();
+ try {
+ Artist artist = context.newObject(Artist.class);
+ artist.setArtistName("artist");
+ context.commitChanges();
+ } finally {
+ resumeSerializableTransaction.countDown();
+ }
+ } catch (Exception ex) {
+ logger.error("Unable to create Artist", ex);
+ return false;
+ }
+ return true;
+ });
+
+ assertTrue(thread1Result.get(30, TimeUnit.SECONDS));
+ assertTrue(thread2Result.get(30, TimeUnit.SECONDS));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java
new file mode 100644
index 0000000..ae94b8a
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java
@@ -0,0 +1,159 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.tx;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * This test checks rollback behaviour of different propagation modes.
+ *
+ * @see TransactionPropagation
+ * @since 4.1
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class TransactionPropagationRollbackIT extends ServerCase {
+
+ @Inject
+ DataContext context;
+
+ @Inject
+ ServerRuntime runtime;
+
+ @Inject
+ UnitDbAdapter unitDbAdapter;
+
+ TransactionManager manager;
+
+ @Before
+ public void initTransactionManager() {
+ // no binding in test container, get it from runtime
+ manager = runtime.getInjector().getInstance(TransactionManager.class);
+ }
+
+ /**
+ * @see TransactionPropagation#REQUIRES_NEW
+ */
+ @Test
+ public void testPropagationRequiresNew() {
+ TransactionDescriptor descriptor = new TransactionDescriptor(
+ Connection.TRANSACTION_SERIALIZABLE, // ensure that transaction not visible to each other
+ TransactionPropagation.REQUIRES_NEW // require new transaction for every operation
+ );
+
+ performInTransaction(descriptor);
+
+ // rollback should be performed and no artist will be in DB
+ assertEquals(0L, ObjectSelect.query(Artist.class).selectCount(context));
+
+ // painting should be there
+ assertEquals(1L, ObjectSelect.query(Painting.class).selectCount(context));
+ }
+
+ /**
+ * @see TransactionPropagation#NESTED
+ */
+ @Test
+ public void testPropagationNested() {
+
+ TransactionDescriptor descriptor = new TransactionDescriptor(
+ Connection.TRANSACTION_SERIALIZABLE, // ensure that transaction not visible to each other
+ TransactionPropagation.NESTED // allow joining to existing transaction
+ );
+
+ performInTransaction(descriptor);
+
+ // nested rollback shouldn't affect outer transaction
+ assertEquals(1L, ObjectSelect.query(Artist.class).selectCount(context));
+
+ // painting should be there
+ assertEquals(1L, ObjectSelect.query(Painting.class).selectCount(context));
+ }
+
+ /**
+ * @see TransactionPropagation#MANDATORY
+ */
+ @Test
+ public void testPropagationMandatory() {
+
+ TransactionDescriptor descriptor = new TransactionDescriptor(
+ Connection.TRANSACTION_SERIALIZABLE, // ensure that transaction not visible to each other
+ TransactionPropagation.MANDATORY // requires existing transaction to join
+ );
+
+ performInTransaction(descriptor);
+
+ // nested rollback shouldn't affect outer transaction
+ assertEquals(1L, ObjectSelect.query(Artist.class).selectCount(context));
+
+ // painting should be there
+ assertEquals(1L, ObjectSelect.query(Painting.class).selectCount(context));
+ }
+
+ private void performInTransaction(TransactionDescriptor descriptor) {
+ Artist artist = context.newObject(Artist.class);
+ artist.setArtistName("test");
+
+ manager.performInTransaction(() -> {
+ // try to perform illegal operation in nested transaction
+ try {
+ manager.performInTransaction(() -> {
+ artist.setArtistName("test3");
+ context.commitChanges(); // this should pass
+
+ artist.setArtistName(null);
+ context.commitChanges(); // this should throw
+ return null;
+ }, descriptor);
+ fail("Exception should be thrown");
+ } catch (Exception ignore) {
+ }
+
+ // perform some valid commit
+ artist.setArtistName("test2");
+
+ Painting painting = context.newObject(Painting.class);
+ painting.setPaintingTitle("painting");
+
+ // Outcome of this will depend on transaction propagation
+ // if it's nested or mandatory we'll have here artist committed,
+ // if it's new no artist should be in database
+ context.commitChanges();
+ return null;
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
index 633a03d..a6b381a 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
@@ -145,4 +145,9 @@ public class OracleUnitDbAdapter extends UnitDbAdapter {
public boolean supportsSelectBooleanExpression() {
return false;
}
+
+ @Override
+ public boolean supportsSerializableTransactionIsolation() {
+ return true;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/unit/PostgresUnitDbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/PostgresUnitDbAdapter.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/PostgresUnitDbAdapter.java
index 1843bc2..d9d6383 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/PostgresUnitDbAdapter.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/PostgresUnitDbAdapter.java
@@ -70,4 +70,9 @@ public class PostgresUnitDbAdapter extends UnitDbAdapter {
public boolean supportsGeneratedKeysDrop() {
return true;
}
+
+ @Override
+ public boolean supportsSerializableTransactionIsolation() {
+ return true;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8c8a2f0b/cayenne-server/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
index 0a83243..54e68a8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
@@ -403,4 +403,8 @@ public class UnitDbAdapter {
public boolean supportsExtractPart(ASTExtract.DateTimePart part) {
return true;
}
+
+ public boolean supportsSerializableTransactionIsolation() {
+ return false;
+ }
}