001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.datasource;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021import java.sql.Statement;
022import javax.sql.DataSource;
023
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.transaction.CannotCreateTransactionException;
026import org.springframework.transaction.TransactionDefinition;
027import org.springframework.transaction.TransactionSystemException;
028import org.springframework.transaction.support.AbstractPlatformTransactionManager;
029import org.springframework.transaction.support.DefaultTransactionStatus;
030import org.springframework.transaction.support.ResourceTransactionManager;
031import org.springframework.transaction.support.TransactionSynchronizationManager;
032import org.springframework.transaction.support.TransactionSynchronizationUtils;
033
034/**
035 * {@link org.springframework.transaction.PlatformTransactionManager}
036 * implementation for a single JDBC {@link javax.sql.DataSource}. This class is
037 * capable of working in any environment with any JDBC driver, as long as the setup
038 * uses a {@code javax.sql.DataSource} as its {@code Connection} factory mechanism.
039 * Binds a JDBC Connection from the specified DataSource to the current thread,
040 * potentially allowing for one thread-bound Connection per DataSource.
041 *
042 * <p><b>Note: The DataSource that this transaction manager operates on needs
043 * to return independent Connections.</b> The Connections may come from a pool
044 * (the typical case), but the DataSource must not return thread-scoped /
045 * request-scoped Connections or the like. This transaction manager will
046 * associate Connections with thread-bound transactions itself, according
047 * to the specified propagation behavior. It assumes that a separate,
048 * independent Connection can be obtained even during an ongoing transaction.
049 *
050 * <p>Application code is required to retrieve the JDBC Connection via
051 * {@link DataSourceUtils#getConnection(DataSource)} instead of a standard
052 * Java EE-style {@link DataSource#getConnection()} call. Spring classes such as
053 * {@link org.springframework.jdbc.core.JdbcTemplate} use this strategy implicitly.
054 * If not used in combination with this transaction manager, the
055 * {@link DataSourceUtils} lookup strategy behaves exactly like the native
056 * DataSource lookup; it can thus be used in a portable fashion.
057 *
058 * <p>Alternatively, you can allow application code to work with the standard
059 * Java EE-style lookup pattern {@link DataSource#getConnection()}, for example for
060 * legacy code that is not aware of Spring at all. In that case, define a
061 * {@link TransactionAwareDataSourceProxy} for your target DataSource, and pass
062 * that proxy DataSource to your DAOs, which will automatically participate in
063 * Spring-managed transactions when accessing it.
064 *
065 * <p>Supports custom isolation levels, and timeouts which get applied as
066 * appropriate JDBC statement timeouts. To support the latter, application code
067 * must either use {@link org.springframework.jdbc.core.JdbcTemplate}, call
068 * {@link DataSourceUtils#applyTransactionTimeout} for each created JDBC Statement,
069 * or go through a {@link TransactionAwareDataSourceProxy} which will create
070 * timeout-aware JDBC Connections and Statements automatically.
071 *
072 * <p>Consider defining a {@link LazyConnectionDataSourceProxy} for your target
073 * DataSource, pointing both this transaction manager and your DAOs to it.
074 * This will lead to optimized handling of "empty" transactions, i.e. of transactions
075 * without any JDBC statements executed. A LazyConnectionDataSourceProxy will not fetch
076 * an actual JDBC Connection from the target DataSource until a Statement gets executed,
077 * lazily applying the specified transaction settings to the target Connection.
078 *
079 * <p>This transaction manager supports nested transactions via the JDBC 3.0
080 * {@link java.sql.Savepoint} mechanism. The
081 * {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
082 * to "true", since nested transactions will work without restrictions on JDBC
083 * drivers that support savepoints (such as the Oracle JDBC driver).
084 *
085 * <p>This transaction manager can be used as a replacement for the
086 * {@link org.springframework.transaction.jta.JtaTransactionManager} in the single
087 * resource case, as it does not require a container that supports JTA, typically
088 * in combination with a locally defined JDBC DataSource (e.g. an Apache Commons
089 * DBCP connection pool). Switching between this local strategy and a JTA
090 * environment is just a matter of configuration!
091 *
092 * <p>As of 4.3.4, this transaction manager triggers flush callbacks on registered
093 * transaction synchronizations (if synchronization is generally active), assuming
094 * resources operating on the underlying JDBC {@code Connection}. This allows for
095 * setup analogous to {@code JtaTransactionManager}, in particular with respect to
096 * lazily registered ORM resources (e.g. a Hibernate {@code Session}).
097 *
098 * @author Juergen Hoeller
099 * @since 02.05.2003
100 * @see #setNestedTransactionAllowed
101 * @see java.sql.Savepoint
102 * @see DataSourceUtils#getConnection(javax.sql.DataSource)
103 * @see DataSourceUtils#applyTransactionTimeout
104 * @see DataSourceUtils#releaseConnection
105 * @see TransactionAwareDataSourceProxy
106 * @see LazyConnectionDataSourceProxy
107 * @see org.springframework.jdbc.core.JdbcTemplate
108 */
109@SuppressWarnings("serial")
110public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
111                implements ResourceTransactionManager, InitializingBean {
112
113        private DataSource dataSource;
114
115        private boolean enforceReadOnly = false;
116
117
118        /**
119         * Create a new DataSourceTransactionManager instance.
120         * A DataSource has to be set to be able to use it.
121         * @see #setDataSource
122         */
123        public DataSourceTransactionManager() {
124                setNestedTransactionAllowed(true);
125        }
126
127        /**
128         * Create a new DataSourceTransactionManager instance.
129         * @param dataSource the JDBC DataSource to manage transactions for
130         */
131        public DataSourceTransactionManager(DataSource dataSource) {
132                this();
133                setDataSource(dataSource);
134                afterPropertiesSet();
135        }
136
137
138        /**
139         * Set the JDBC DataSource that this instance should manage transactions for.
140         * <p>This will typically be a locally defined DataSource, for example an
141         * Apache Commons DBCP connection pool. Alternatively, you can also drive
142         * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA
143         * DataSource, use JtaTransactionManager.
144         * <p>The DataSource specified here should be the target DataSource to manage
145         * transactions for, not a TransactionAwareDataSourceProxy. Only data access
146         * code may work with TransactionAwareDataSourceProxy, while the transaction
147         * manager needs to work on the underlying target DataSource. If there's
148         * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
149         * unwrapped to extract its target DataSource.
150         * <p><b>The DataSource passed in here needs to return independent Connections.</b>
151         * The Connections may come from a pool (the typical case), but the DataSource
152         * must not return thread-scoped / request-scoped Connections or the like.
153         * @see TransactionAwareDataSourceProxy
154         * @see org.springframework.transaction.jta.JtaTransactionManager
155         */
156        public void setDataSource(DataSource dataSource) {
157                if (dataSource instanceof TransactionAwareDataSourceProxy) {
158                        // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
159                        // for its underlying target DataSource, else data access code won't see
160                        // properly exposed transactions (i.e. transactions for the target DataSource).
161                        this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
162                }
163                else {
164                        this.dataSource = dataSource;
165                }
166        }
167
168        /**
169         * Return the JDBC DataSource that this instance manages transactions for.
170         */
171        public DataSource getDataSource() {
172                return this.dataSource;
173        }
174
175        /**
176         * Specify whether to enforce the read-only nature of a transaction
177         * (as indicated by {@link TransactionDefinition#isReadOnly()}
178         * through an explicit statement on the transactional connection:
179         * "SET TRANSACTION READ ONLY" as understood by Oracle, MySQL and Postgres.
180         * <p>The exact treatment, including any SQL statement executed on the connection,
181         * can be customized through {@link #prepareTransactionalConnection}.
182         * <p>This mode of read-only handling goes beyond the {@link Connection#setReadOnly}
183         * hint that Spring applies by default. In contrast to that standard JDBC hint,
184         * "SET TRANSACTION READ ONLY" enforces an isolation-level-like connection mode
185         * where data manipulation statements are strictly disallowed. Also, on Oracle,
186         * this read-only mode provides read consistency for the entire transaction.
187         * <p>Note that older Oracle JDBC drivers (9i, 10g) used to enforce this read-only
188         * mode even for {@code Connection.setReadOnly(true}. However, with recent drivers,
189         * this strong enforcement needs to be applied explicitly, e.g. through this flag.
190         * @since 4.3.7
191         * @see #prepareTransactionalConnection
192         */
193        public void setEnforceReadOnly(boolean enforceReadOnly) {
194                this.enforceReadOnly = enforceReadOnly;
195        }
196
197        /**
198         * Return whether to enforce the read-only nature of a transaction
199         * through an explicit statement on the transactional connection.
200         * @since 4.3.7
201         * @see #setEnforceReadOnly
202         */
203        public boolean isEnforceReadOnly() {
204                return this.enforceReadOnly;
205        }
206
207        @Override
208        public void afterPropertiesSet() {
209                if (getDataSource() == null) {
210                        throw new IllegalArgumentException("Property 'dataSource' is required");
211                }
212        }
213
214
215        @Override
216        public Object getResourceFactory() {
217                return getDataSource();
218        }
219
220        @Override
221        protected Object doGetTransaction() {
222                DataSourceTransactionObject txObject = new DataSourceTransactionObject();
223                txObject.setSavepointAllowed(isNestedTransactionAllowed());
224                ConnectionHolder conHolder =
225                                (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
226                txObject.setConnectionHolder(conHolder, false);
227                return txObject;
228        }
229
230        @Override
231        protected boolean isExistingTransaction(Object transaction) {
232                DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
233                return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
234        }
235
236        @Override
237        protected void doBegin(Object transaction, TransactionDefinition definition) {
238                DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
239                Connection con = null;
240
241                try {
242                        if (!txObject.hasConnectionHolder() ||
243                                        txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
244                                Connection newCon = this.dataSource.getConnection();
245                                if (logger.isDebugEnabled()) {
246                                        logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
247                                }
248                                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
249                        }
250
251                        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
252                        con = txObject.getConnectionHolder().getConnection();
253
254                        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
255                        txObject.setPreviousIsolationLevel(previousIsolationLevel);
256
257                        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
258                        // so we don't want to do it unnecessarily (for example if we've explicitly
259                        // configured the connection pool to set it already).
260                        if (con.getAutoCommit()) {
261                                txObject.setMustRestoreAutoCommit(true);
262                                if (logger.isDebugEnabled()) {
263                                        logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
264                                }
265                                con.setAutoCommit(false);
266                        }
267
268                        prepareTransactionalConnection(con, definition);
269                        txObject.getConnectionHolder().setTransactionActive(true);
270
271                        int timeout = determineTimeout(definition);
272                        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
273                                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
274                        }
275
276                        // Bind the connection holder to the thread.
277                        if (txObject.isNewConnectionHolder()) {
278                                TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
279                        }
280                }
281
282                catch (Throwable ex) {
283                        if (txObject.isNewConnectionHolder()) {
284                                DataSourceUtils.releaseConnection(con, this.dataSource);
285                                txObject.setConnectionHolder(null, false);
286                        }
287                        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
288                }
289        }
290
291        @Override
292        protected Object doSuspend(Object transaction) {
293                DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
294                txObject.setConnectionHolder(null);
295                return TransactionSynchronizationManager.unbindResource(this.dataSource);
296        }
297
298        @Override
299        protected void doResume(Object transaction, Object suspendedResources) {
300                TransactionSynchronizationManager.bindResource(this.dataSource, suspendedResources);
301        }
302
303        @Override
304        protected void doCommit(DefaultTransactionStatus status) {
305                DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
306                Connection con = txObject.getConnectionHolder().getConnection();
307                if (status.isDebug()) {
308                        logger.debug("Committing JDBC transaction on Connection [" + con + "]");
309                }
310                try {
311                        con.commit();
312                }
313                catch (SQLException ex) {
314                        throw new TransactionSystemException("Could not commit JDBC transaction", ex);
315                }
316        }
317
318        @Override
319        protected void doRollback(DefaultTransactionStatus status) {
320                DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
321                Connection con = txObject.getConnectionHolder().getConnection();
322                if (status.isDebug()) {
323                        logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
324                }
325                try {
326                        con.rollback();
327                }
328                catch (SQLException ex) {
329                        throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
330                }
331        }
332
333        @Override
334        protected void doSetRollbackOnly(DefaultTransactionStatus status) {
335                DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
336                if (status.isDebug()) {
337                        logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
338                                        "] rollback-only");
339                }
340                txObject.setRollbackOnly();
341        }
342
343        @Override
344        protected void doCleanupAfterCompletion(Object transaction) {
345                DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
346
347                // Remove the connection holder from the thread, if exposed.
348                if (txObject.isNewConnectionHolder()) {
349                        TransactionSynchronizationManager.unbindResource(this.dataSource);
350                }
351
352                // Reset connection.
353                Connection con = txObject.getConnectionHolder().getConnection();
354                try {
355                        if (txObject.isMustRestoreAutoCommit()) {
356                                con.setAutoCommit(true);
357                        }
358                        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
359                }
360                catch (Throwable ex) {
361                        logger.debug("Could not reset JDBC Connection after transaction", ex);
362                }
363
364                if (txObject.isNewConnectionHolder()) {
365                        if (logger.isDebugEnabled()) {
366                                logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
367                        }
368                        DataSourceUtils.releaseConnection(con, this.dataSource);
369                }
370
371                txObject.getConnectionHolder().clear();
372        }
373
374
375        /**
376         * Prepare the transactional {@code Connection} right after transaction begin.
377         * <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement
378         * if the {@link #setEnforceReadOnly "enforceReadOnly"} flag is set to {@code true}
379         * and the transaction definition indicates a read-only transaction.
380         * <p>The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres
381         * and may work with other databases as well. If you'd like to adapt this treatment,
382         * override this method accordingly.
383         * @param con the transactional JDBC Connection
384         * @param definition the current transaction definition
385         * @throws SQLException if thrown by JDBC API
386         * @since 4.3.7
387         * @see #setEnforceReadOnly
388         */
389        protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)
390                        throws SQLException {
391
392                if (isEnforceReadOnly() && definition.isReadOnly()) {
393                        Statement stmt = con.createStatement();
394                        try {
395                                stmt.executeUpdate("SET TRANSACTION READ ONLY");
396                        }
397                        finally {
398                                stmt.close();
399                        }
400                }
401        }
402
403
404        /**
405         * DataSource transaction object, representing a ConnectionHolder.
406         * Used as transaction object by DataSourceTransactionManager.
407         */
408        private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {
409
410                private boolean newConnectionHolder;
411
412                private boolean mustRestoreAutoCommit;
413
414                public void setConnectionHolder(ConnectionHolder connectionHolder, boolean newConnectionHolder) {
415                        super.setConnectionHolder(connectionHolder);
416                        this.newConnectionHolder = newConnectionHolder;
417                }
418
419                public boolean isNewConnectionHolder() {
420                        return this.newConnectionHolder;
421                }
422
423                public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) {
424                        this.mustRestoreAutoCommit = mustRestoreAutoCommit;
425                }
426
427                public boolean isMustRestoreAutoCommit() {
428                        return this.mustRestoreAutoCommit;
429                }
430
431                public void setRollbackOnly() {
432                        getConnectionHolder().setRollbackOnly();
433                }
434
435                @Override
436                public boolean isRollbackOnly() {
437                        return getConnectionHolder().isRollbackOnly();
438                }
439
440                @Override
441                public void flush() {
442                        if (TransactionSynchronizationManager.isSynchronizationActive()) {
443                                TransactionSynchronizationUtils.triggerFlush();
444                        }
445                }
446        }
447
448}