diff --git a/jOOQ-test/src/test/java/org/jooq/test/_/testcases/TransactionTests.java b/jOOQ-test/src/test/java/org/jooq/test/_/testcases/TransactionTests.java index 654a4161f8..ba4093fbfd 100644 --- a/jOOQ-test/src/test/java/org/jooq/test/_/testcases/TransactionTests.java +++ b/jOOQ-test/src/test/java/org/jooq/test/_/testcases/TransactionTests.java @@ -47,9 +47,11 @@ import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.Date; import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; import org.jooq.Configuration; import org.jooq.ConnectionProvider; +import org.jooq.DSLContext; import org.jooq.Record1; import org.jooq.Record2; import org.jooq.Record3; @@ -106,8 +108,12 @@ extends BaseTest() { + create.transaction(new Transactional() { @Override public Integer run(Configuration configuration) { assertAutoCommit(configuration.connectionProvider(), false); @@ -125,17 +131,24 @@ extends BaseTest() { + create.transaction(new Transactional() { @Override public Integer run(Configuration configuration) throws MyCheckedException { assertAutoCommit(configuration.connectionProvider(), false); @@ -153,13 +166,16 @@ extends BaseTest() { + create.transaction(new Transactional() { @Override public Integer run(Configuration c1) throws MyCheckedException { assertAutoCommit(c1.connectionProvider(), false); @@ -215,7 +235,9 @@ extends BaseTest() { + create.transaction(new Transactional() { @Override public Integer run(Configuration c1) throws MyCheckedException { assertAutoCommit(c1.connectionProvider(), false); @@ -266,7 +292,10 @@ extends BaseTest + * {@link TransactionProvider} implementations may choose to influence + * {@link ConnectionProvider} behaviour, e.g. by acquiring connections upon + * {@link TransactionProvider#begin(TransactionContext)} and by releasing + * connections only upon {@link TransactionProvider#commit(TransactionContext)}, + * or {@link TransactionProvider#rollback(TransactionContext)}. * * @author Aaron Digulla * @author Lukas Eder diff --git a/jOOQ/src/main/java/org/jooq/TransactionContext.java b/jOOQ/src/main/java/org/jooq/TransactionContext.java new file mode 100644 index 0000000000..5cbd73cc98 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/TransactionContext.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * Licensed 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq; + +/** + * A context object that is used to pass arguments to the various methods of + * {@link TransactionProvider}. + * + * @author Lukas Eder + */ +public interface TransactionContext { + + /** + * The configuration scoped to this transaction and its nested transactions. + */ + Configuration configuration(); + + /** + * A user-defined transaction object, obtained from + * {@link TransactionProvider#begin(TransactionContext)}. + * + * @return The transaction object. May be null. + */ + Transaction transaction(); + + /** + * The exception that has caused the rollback. + * + * @return The exception. May be null. + */ + Exception cause(); +} diff --git a/jOOQ/src/main/java/org/jooq/TransactionProvider.java b/jOOQ/src/main/java/org/jooq/TransactionProvider.java index 0615914b67..f37be678ab 100644 --- a/jOOQ/src/main/java/org/jooq/TransactionProvider.java +++ b/jOOQ/src/main/java/org/jooq/TransactionProvider.java @@ -53,10 +53,9 @@ import org.jooq.impl.DefaultTransactionProvider; * A new {@link Configuration} copy is created from the calling * {@link DSLContext} for the scope of a single transactions. Implementors may * freely add custom data to {@link Configuration#data()}, in order to share - * information between {@link #begin(Configuration)} and - * {@link #commit(Configuration, Transaction)} or - * {@link #rollback(Configuration, Transaction, Exception)}, as well as to share - * information with nested transactions. + * information between {@link #begin(TransactionContext)} and + * {@link #commit(TransactionContext)} or {@link #rollback(TransactionContext)}, + * as well as to share information with nested transactions. *

* Implementors may freely choose whether they support nested transactions. An * example implementation supporting nested transactions is @@ -73,8 +72,8 @@ public interface TransactionProvider { * This method begins a new transaction with a {@link Configuration} scoped * for this transaction. The resulting {@link Transaction} object may be * used by implementors to identify the transaction when - * {@link #commit(Configuration, Transaction)} or - * {@link #rollback(Configuration, Transaction, Exception)} is called. + * {@link #commit(TransactionContext)} or + * {@link #rollback(TransactionContext)} is called. * * @param Configuration the configuration scoped to this transaction and its * nested transactions. @@ -82,27 +81,27 @@ public interface TransactionProvider { * @throws DataAccessException Any exception issued by the underlying * database. */ - Transaction begin(Configuration configuration) throws DataAccessException; + Transaction begin(TransactionContext ctx) throws DataAccessException; /** * @param Configuration the configuration scoped to this transaction and its * nested transactions. * @param transaction The user-defined transaction object returned from - * {@link #begin(Configuration)}. May be null. + * {@link #begin(TransactionContext)}. May be null. * @throws DataAccessException Any exception issued by the underlying * database. */ - void commit(Configuration configuration, Transaction transaction) throws DataAccessException; + void commit(TransactionContext ctx) throws DataAccessException; /** * @param Configuration the configuration scoped to this transaction and its * nested transactions. * @param transaction The user-defined transaction object returned from - * {@link #begin(Configuration)}. May be null. + * {@link #begin(TransactionContext)}. May be null. * @param cause The exception that has caused the rollback. * @throws DataAccessException Any exception issued by the underlying * database. */ - void rollback(Configuration configuration, Transaction transaction, Exception cause) throws DataAccessException; + void rollback(TransactionContext ctx) throws DataAccessException; } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java index 3d195db6a6..8f3b6fb3ba 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java @@ -41,6 +41,7 @@ package org.jooq.impl; import static org.jooq.SQLDialect.SQL99; +import static org.jooq.impl.Utils.DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION; import java.io.IOException; import java.io.ObjectInputStream; @@ -520,7 +521,11 @@ public class DefaultConfiguration implements Configuration { */ @Override public final ConnectionProvider connectionProvider() { - return connectionProvider; + + // [#3229] If we're currently in a transaction, return that transaction's + // local DefaultConnectionProvider, not the one from this configuration + ConnectionProvider transactional = (ConnectionProvider) data(DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION); + return transactional == null ? connectionProvider : transactional; } /** @@ -528,10 +533,11 @@ public class DefaultConfiguration implements Configuration { */ @Override public final TransactionProvider transactionProvider() { - if (transactionProvider instanceof NoTransactionProvider && - connectionProvider instanceof DefaultConnectionProvider) { - return new DefaultTransactionProvider((DefaultConnectionProvider) connectionProvider); + // [#3229] If transactions are used in client code, the default behaviour + // is assumed automatically, for convenience. + if (transactionProvider instanceof NoTransactionProvider) { + return new DefaultTransactionProvider(connectionProvider); } return transactionProvider; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java index 6158052f5f..689b1dc605 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java @@ -176,7 +176,6 @@ import org.jooq.Sequence; import org.jooq.Table; import org.jooq.TableLike; import org.jooq.TableRecord; -import org.jooq.Transaction; import org.jooq.TransactionProvider; import org.jooq.Transactional; import org.jooq.TruncateIdentityStep; @@ -293,19 +292,19 @@ public class DefaultDSLContext implements DSLContext, Serializable { @Override public T transaction(Transactional transactional) { - Configuration local = configuration.derive(); - TransactionProvider provider = local.transactionProvider(); - - Transaction transaction = null; T result = null; + DefaultTransactionContext ctx = new DefaultTransactionContext(configuration.derive()); + TransactionProvider provider = configuration.derive().transactionProvider(); + try { - transaction = provider.begin(local); - result = transactional.run(local); - provider.commit(local, transaction); + ctx.transaction = provider.begin(ctx); + result = transactional.run(configuration.derive()); + provider.commit(ctx); } catch (Exception cause) { - provider.rollback(local, transaction, cause); + ctx.cause = cause; + provider.rollback(ctx); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionContext.java new file mode 100644 index 0000000000..efd500573b --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionContext.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * Licensed 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import org.jooq.Configuration; +import org.jooq.Transaction; +import org.jooq.TransactionContext; + +/** + * @author Lukas Eder + */ +class DefaultTransactionContext implements TransactionContext { + + private final Configuration configuration; + Transaction transaction; + Exception cause; + + DefaultTransactionContext(Configuration configuration) { + this.configuration = configuration; + } + + @Override + public final Configuration configuration() { + return configuration; + } + + @Override + public final Transaction transaction() { + return transaction; + } + + @Override + public final Exception cause() { + return cause; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionProvider.java index ba95290cb5..ee4f169074 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionProvider.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultTransactionProvider.java @@ -41,6 +41,7 @@ package org.jooq.impl; import static org.jooq.impl.Utils.DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT; +import static org.jooq.impl.Utils.DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION; import static org.jooq.impl.Utils.DATA_DEFAULT_TRANSACTION_PROVIDER_SAVEPOINTS; import java.sql.Connection; @@ -48,7 +49,9 @@ import java.sql.Savepoint; import java.util.Stack; import org.jooq.Configuration; +import org.jooq.ConnectionProvider; import org.jooq.Transaction; +import org.jooq.TransactionContext; import org.jooq.TransactionProvider; /** @@ -57,17 +60,18 @@ import org.jooq.TransactionProvider; * This implementation is entirely based on JDBC transactions and is intended to * work with {@link DefaultConnectionProvider} (which is implicitly created when * using {@link DSL#using(Connection)}). It supports nested transactions by - * modelling them implicitly with JDBC {@link Savepoint}s, if supported by the + * modeling them implicitly with JDBC {@link Savepoint}s, if supported by the * underlying JDBC driver. * * @author Lukas Eder */ public class DefaultTransactionProvider implements TransactionProvider { - private final DefaultConnectionProvider connection; + private final ConnectionProvider provider; + private Connection connection; - public DefaultTransactionProvider(DefaultConnectionProvider connection) { - this.connection = connection; + public DefaultTransactionProvider(ConnectionProvider provider) { + this.provider = provider; } @SuppressWarnings("unchecked") @@ -86,35 +90,46 @@ public class DefaultTransactionProvider implements TransactionProvider { Boolean autoCommit = (Boolean) configuration.data(DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT); if (autoCommit == null) { - autoCommit = connection.getAutoCommit(); + autoCommit = connection(configuration).getAutoCommit(); configuration.data(DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT, autoCommit); } return autoCommit; } + private final DefaultConnectionProvider connection(Configuration configuration) { + DefaultConnectionProvider connectionWrapper = (DefaultConnectionProvider) configuration.data(DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION); + + if (connectionWrapper == null) { + connectionWrapper = new DefaultConnectionProvider(connection); + configuration.data(DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION, connectionWrapper); + } + + return connectionWrapper; + } + @Override - public final Transaction begin(Configuration configuration) { - Stack savepoints = savepoints(configuration); + public final Transaction begin(TransactionContext ctx) { + Stack savepoints = savepoints(ctx.configuration()); // This is the top-level transaction if (savepoints.isEmpty()) { - autoCommit(configuration, false); + brace(ctx.configuration(), true); } - savepoints.push(connection.setSavepoint()); + savepoints.push(connection(ctx.configuration()).setSavepoint()); return null; } @Override - public final void commit(Configuration configuration, Transaction transaction) { - Stack savepoints = savepoints(configuration); + public final void commit(TransactionContext ctx) { + Stack savepoints = savepoints(ctx.configuration()); savepoints.pop(); // This is the top-level transaction if (savepoints.isEmpty()) { - connection.commit(); - autoCommit(configuration, true); + connection(ctx.configuration()).commit(); + brace(ctx.configuration(), false); } // Nested commits have no effect @@ -123,17 +138,17 @@ public class DefaultTransactionProvider implements TransactionProvider { } @Override - public final void rollback(Configuration configuration, Transaction transaction, Exception cause) { - Stack savepoints = savepoints(configuration); + public final void rollback(TransactionContext ctx) { + Stack savepoints = savepoints(ctx.configuration()); Savepoint savepoint = savepoints.pop(); try { - connection.rollback(savepoint); + connection(ctx.configuration()).rollback(savepoint); } finally { if (savepoints.isEmpty()) - autoCommit(configuration, true); + brace(ctx.configuration(), false); } } @@ -141,13 +156,24 @@ public class DefaultTransactionProvider implements TransactionProvider { * Ensure an autoCommit value on the connection, if it was set * to true, originally. */ - private void autoCommit(Configuration configuration, boolean newValue) { - boolean oldValue = autoCommit(configuration); + private void brace(Configuration configuration, boolean start) { + if (start) { + connection = provider.acquire(); + } + + boolean autoCommit = autoCommit(configuration); // Transactions cannot run with autoCommit = true. Change the value for // the duration of a transaction - if (oldValue == true) { - connection.setAutoCommit(newValue); + if (autoCommit == true) { + connection(configuration).setAutoCommit(!start); + } + + if (!start) { + provider.release(connection); + + connection = null; + configuration.data().remove(DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION); } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java index 73cdb10bd7..71f37c9d6d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java @@ -69,7 +69,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.jooq.BindContext; import org.jooq.Clause; import org.jooq.Condition; import org.jooq.Configuration; diff --git a/jOOQ/src/main/java/org/jooq/impl/NoTransactionProvider.java b/jOOQ/src/main/java/org/jooq/impl/NoTransactionProvider.java index e6d0194946..50ac67df7a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/NoTransactionProvider.java +++ b/jOOQ/src/main/java/org/jooq/impl/NoTransactionProvider.java @@ -40,8 +40,8 @@ */ package org.jooq.impl; -import org.jooq.Configuration; import org.jooq.Transaction; +import org.jooq.TransactionContext; import org.jooq.TransactionProvider; /** @@ -52,17 +52,17 @@ import org.jooq.TransactionProvider; public class NoTransactionProvider implements TransactionProvider { @Override - public final Transaction begin(Configuration configuration) { + public final Transaction begin(TransactionContext ctx) { throw new UnsupportedOperationException("No transaction provider configured"); } @Override - public final void commit(Configuration configuration, Transaction transaction) { + public final void commit(TransactionContext ctx) { throw new UnsupportedOperationException("No transaction provider configured"); } @Override - public final void rollback(Configuration configuration, Transaction transaction, Exception cause) { + public final void rollback(TransactionContext ctx) { throw new UnsupportedOperationException("No transaction provider configured"); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Utils.java b/jOOQ/src/main/java/org/jooq/impl/Utils.java index dc31ccb7fa..9f99113de4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Utils.java +++ b/jOOQ/src/main/java/org/jooq/impl/Utils.java @@ -261,6 +261,12 @@ final class Utils { */ static final String DATA_DEFAULT_TRANSACTION_PROVIDER_SAVEPOINTS = "org.jooq.configuration.default-transaction-provider-savepoints"; + /** + * [#1629] The {@link DefaultConnectionProvider} instance to be used during + * the transaction. + */ + static final String DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION = "org.jooq.configuration.default-transaction-provider-connection-provider"; + /** * [#2965] These are {@link ConcurrentHashMap}s containing caches for * reflection information.