This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 2ba9f341db00982a6506ad85c2d73bfe4bfeb337 Author: dmitrij <dmitriy.tarasie...@gmail.com> AuthorDate: Wed May 26 11:47:26 2021 +0300 feat: ability to add custom user connection to TransactionDescriptor --- .../org/apache/cayenne/tx/BaseTransaction.java | 44 +++++---- .../apache/cayenne/tx/TransactionDescriptor.java | 106 +++++++++++++++++---- .../org/apache/cayenne/tx/TransactionListener.java | 4 + .../cayenne/tx/DefaultTransactionManagerIT.java | 22 +++-- .../apache/cayenne/tx/TransactionIsolationIT.java | 8 +- .../tx/TransactionPropagationRollbackIT.java | 24 ++--- 6 files changed, 151 insertions(+), 57 deletions(-) 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 a83990b..e3215b5 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 @@ -22,11 +22,7 @@ package org.apache.cayenne.tx; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; +import java.util.*; import org.apache.cayenne.CayenneRuntimeException; @@ -204,7 +200,10 @@ public abstract class BaseTransaction implements Transaction { Connection c = getExistingConnection(connectionName); if (c == null || c.isClosed()) { - c = dataSource.getConnection(); + if(descriptor.getCustomConnectionSupplier() != null) + c = descriptor.getCustomConnectionSupplier().get(); + else + c = dataSource.getConnection(); addConnection(connectionName, c); } @@ -219,19 +218,18 @@ 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); - } - } + setIsolationLevelFrom(connection); - TransactionConnectionDecorator wrapper = new TransactionConnectionDecorator(connection); + TransactionConnectionDecorator wrapper = null; if (listeners != null) { for (TransactionListener listener : listeners) { + connection = listener.decorateConnection(this, connection); + } + + wrapper = new TransactionConnectionDecorator(connection); + + for (TransactionListener listener : listeners) { listener.willAddConnection(this, connectionName, wrapper); } } @@ -241,6 +239,9 @@ public abstract class BaseTransaction implements Transaction { connections = new HashMap<>(); } + if (wrapper == null) + wrapper = new TransactionConnectionDecorator(connection); + if (connections.put(connectionName, wrapper) != wrapper) { connectionAdded(connection); } @@ -248,6 +249,17 @@ public abstract class BaseTransaction implements Transaction { return wrapper; } + private void setIsolationLevelFrom(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); + } + } + } + protected void connectionAdded(Connection connection) { // implicitly begin transaction @@ -278,7 +290,7 @@ public abstract class BaseTransaction implements Transaction { // ignore for now } finally { // restore connection default isolation level ... - if(defaultIsolationLevel != -1) { + if (defaultIsolationLevel != -1) { try { c.setTransactionIsolation(defaultIsolationLevel); } catch (SQLException ignore) { 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 index 7dd60ce..ecb273a 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionDescriptor.java @@ -19,8 +19,10 @@ package org.apache.cayenne.tx; +import java.sql.Connection; +import java.util.function.Supplier; + /** - * * Descriptor that provide desired transaction isolation level and propagation logic. * * @since 4.1 @@ -32,47 +34,53 @@ public class TransactionDescriptor { */ public static final int ISOLATION_DEFAULT = -1; - private final int isolation; + private int isolation; - private final TransactionPropagation propagation; + private TransactionPropagation propagation; + + private Supplier<Connection> customConnectionSupplier; + + private TransactionDescriptor() { + } /** - * @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 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 + * @deprecated since 4.2.M4. Use builder instead */ + @Deprecated 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> + * <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> */ + @Deprecated public TransactionDescriptor(int isolation) { this(isolation, TransactionPropagation.NESTED); } /** - * * @param propagation transaction propagation behaviour * @see TransactionPropagation + * @deprecated since 4.2.M4. Use builder instead */ + @Deprecated public TransactionDescriptor(TransactionPropagation propagation) { this(ISOLATION_DEFAULT, propagation); } @@ -90,4 +98,66 @@ public class TransactionDescriptor { public TransactionPropagation getPropagation() { return propagation; } + + /** + * @return custom connection supplier, passed by user + */ + public Supplier<Connection> getCustomConnectionSupplier() { + return customConnectionSupplier; + } + + /** + * Builder class for TransactionDescriptor. + * + * @since 4.2.M4 + */ + public static class Builder { + private final TransactionDescriptor transactionDescriptor = new TransactionDescriptor(); + + /** + * @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 Builder isolation(int isolation) { + transactionDescriptor.isolation = isolation; + return this; + } + + /** + * @param connection custom connection + * @see Connection + */ + public Builder connectionSupplier(Connection connection) { + transactionDescriptor.customConnectionSupplier = () -> connection; + return this; + } + + /** + * @param connectionSupplier custom connection supplier + * @see Connection + * @see Supplier + */ + public Builder connectionSupplier(Supplier<Connection> connectionSupplier){ + transactionDescriptor.customConnectionSupplier = connectionSupplier; + return this; + } + + /** + * @param propagation transaction propagation behaviour + * @see TransactionPropagation + */ + public Builder propagation(TransactionPropagation propagation) { + transactionDescriptor.propagation = propagation; + return this; + } + + public TransactionDescriptor build() { + return transactionDescriptor; + } + } + } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionListener.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionListener.java index dc230e1..b891f6b 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionListener.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionListener.java @@ -33,4 +33,8 @@ public interface TransactionListener { void willRollback(Transaction tx); void willAddConnection(Transaction tx, String connectionName, Connection connection); + + default Connection decorateConnection(Transaction tx, Connection connection){ + return connection; + } } 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 deb025a..ef9de67 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 @@ -83,10 +83,12 @@ public class DefaultTransactionManagerIT { try { final Object expectedResult = new Object(); Object result = txManager.performInTransaction(() -> { - assertSame(tx, BaseTransaction.getThreadTransaction()); - return expectedResult; - }, - new TransactionDescriptor(TransactionPropagation.NESTED) + assertSame(tx, BaseTransaction.getThreadTransaction()); + return expectedResult; + }, + new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.NESTED) + .build() ); assertSame(expectedResult, result); } finally { @@ -109,7 +111,9 @@ public class DefaultTransactionManagerIT { assertSame(tx, BaseTransaction.getThreadTransaction()); return expectedResult; }, - new TransactionDescriptor(TransactionPropagation.MANDATORY) + new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.MANDATORY) + .build() ); assertSame(expectedResult, result); } finally { @@ -133,7 +137,9 @@ public class DefaultTransactionManagerIT { assertSame(tx, BaseTransaction.getThreadTransaction()); return expectedResult; }, - new TransactionDescriptor(TransactionPropagation.MANDATORY) + new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.MANDATORY) + .build() ); assertSame(expectedResult, result); } finally { @@ -163,7 +169,9 @@ public class DefaultTransactionManagerIT { assertSame(tx2, BaseTransaction.getThreadTransaction()); return expectedResult; }, - new TransactionDescriptor(TransactionPropagation.REQUIRES_NEW) + new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.REQUIRES_NEW) + .build() ); assertSame(expectedResult, result); assertSame(tx1, BaseTransaction.getThreadTransaction()); 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 index 6f6cfbb..c2d00af 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionIsolationIT.java @@ -74,10 +74,10 @@ public class TransactionIsolationIT extends ServerCase { return; } - TransactionDescriptor descriptor = new TransactionDescriptor( - Connection.TRANSACTION_SERIALIZABLE, - TransactionPropagation.REQUIRES_NEW - ); + TransactionDescriptor descriptor = new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.REQUIRES_NEW) + .isolation(Connection.TRANSACTION_SERIALIZABLE) + .build(); CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch resumeSerializableTransaction = new CountDownLatch(1); 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 index 6c8985e..cd6c937 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionPropagationRollbackIT.java @@ -69,10 +69,10 @@ public class TransactionPropagationRollbackIT extends ServerCase { */ @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 - ); + TransactionDescriptor descriptor = new TransactionDescriptor.Builder() + .propagation(TransactionPropagation.REQUIRES_NEW) + .isolation(Connection.TRANSACTION_SERIALIZABLE) + .build(); performInTransaction(descriptor); @@ -89,10 +89,10 @@ public class TransactionPropagationRollbackIT extends ServerCase { @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 - ); + TransactionDescriptor descriptor = new TransactionDescriptor.Builder() + .isolation(Connection.TRANSACTION_SERIALIZABLE) + .propagation(TransactionPropagation.NESTED) + .build(); performInTransaction(descriptor); @@ -109,10 +109,10 @@ public class TransactionPropagationRollbackIT extends ServerCase { @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 - ); + TransactionDescriptor descriptor = new TransactionDescriptor.Builder() + .isolation(Connection.TRANSACTION_SERIALIZABLE) + .propagation(TransactionPropagation.MANDATORY) + .build(); performInTransaction(descriptor);