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