001/*
002 * Copyright 2002-2019 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.SQLException;
020import java.sql.Savepoint;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.lang.Nullable;
026import org.springframework.transaction.CannotCreateTransactionException;
027import org.springframework.transaction.NestedTransactionNotSupportedException;
028import org.springframework.transaction.SavepointManager;
029import org.springframework.transaction.TransactionException;
030import org.springframework.transaction.TransactionSystemException;
031import org.springframework.transaction.TransactionUsageException;
032import org.springframework.transaction.support.SmartTransactionObject;
033import org.springframework.util.Assert;
034
035/**
036 * Convenient base class for JDBC-aware transaction objects. Can contain a
037 * {@link ConnectionHolder} with a JDBC {@code Connection}, and implements the
038 * {@link SavepointManager} interface based on that {@code ConnectionHolder}.
039 *
040 * <p>Allows for programmatic management of JDBC {@link java.sql.Savepoint Savepoints}.
041 * Spring's {@link org.springframework.transaction.support.DefaultTransactionStatus}
042 * automatically delegates to this, as it autodetects transaction objects which
043 * implement the {@link SavepointManager} interface.
044 *
045 * @author Juergen Hoeller
046 * @since 1.1
047 * @see DataSourceTransactionManager
048 */
049public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject {
050
051        private static final Log logger = LogFactory.getLog(JdbcTransactionObjectSupport.class);
052
053
054        @Nullable
055        private ConnectionHolder connectionHolder;
056
057        @Nullable
058        private Integer previousIsolationLevel;
059
060        private boolean readOnly = false;
061
062        private boolean savepointAllowed = false;
063
064
065        /**
066         * Set the ConnectionHolder for this transaction object.
067         */
068        public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) {
069                this.connectionHolder = connectionHolder;
070        }
071
072        /**
073         * Return the ConnectionHolder for this transaction object.
074         */
075        public ConnectionHolder getConnectionHolder() {
076                Assert.state(this.connectionHolder != null, "No ConnectionHolder available");
077                return this.connectionHolder;
078        }
079
080        /**
081         * Check whether this transaction object has a ConnectionHolder.
082         */
083        public boolean hasConnectionHolder() {
084                return (this.connectionHolder != null);
085        }
086
087        /**
088         * Set the previous isolation level to retain, if any.
089         */
090        public void setPreviousIsolationLevel(@Nullable Integer previousIsolationLevel) {
091                this.previousIsolationLevel = previousIsolationLevel;
092        }
093
094        /**
095         * Return the retained previous isolation level, if any.
096         */
097        @Nullable
098        public Integer getPreviousIsolationLevel() {
099                return this.previousIsolationLevel;
100        }
101
102        /**
103         * Set the read-only status of this transaction.
104         * The default is {@code false}.
105         * @since 5.2.1
106         */
107        public void setReadOnly(boolean readOnly) {
108                this.readOnly = readOnly;
109        }
110
111        /**
112         * Return the read-only status of this transaction.
113         * @since 5.2.1
114         */
115        public boolean isReadOnly() {
116                return this.readOnly;
117        }
118
119        /**
120         * Set whether savepoints are allowed within this transaction.
121         * The default is {@code false}.
122         */
123        public void setSavepointAllowed(boolean savepointAllowed) {
124                this.savepointAllowed = savepointAllowed;
125        }
126
127        /**
128         * Return whether savepoints are allowed within this transaction.
129         */
130        public boolean isSavepointAllowed() {
131                return this.savepointAllowed;
132        }
133
134        @Override
135        public void flush() {
136                // no-op
137        }
138
139
140        //---------------------------------------------------------------------
141        // Implementation of SavepointManager
142        //---------------------------------------------------------------------
143
144        /**
145         * This implementation creates a JDBC 3.0 Savepoint and returns it.
146         * @see java.sql.Connection#setSavepoint
147         */
148        @Override
149        public Object createSavepoint() throws TransactionException {
150                ConnectionHolder conHolder = getConnectionHolderForSavepoint();
151                try {
152                        if (!conHolder.supportsSavepoints()) {
153                                throw new NestedTransactionNotSupportedException(
154                                                "Cannot create a nested transaction because savepoints are not supported by your JDBC driver");
155                        }
156                        if (conHolder.isRollbackOnly()) {
157                                throw new CannotCreateTransactionException(
158                                                "Cannot create savepoint for transaction which is already marked as rollback-only");
159                        }
160                        return conHolder.createSavepoint();
161                }
162                catch (SQLException ex) {
163                        throw new CannotCreateTransactionException("Could not create JDBC savepoint", ex);
164                }
165        }
166
167        /**
168         * This implementation rolls back to the given JDBC 3.0 Savepoint.
169         * @see java.sql.Connection#rollback(java.sql.Savepoint)
170         */
171        @Override
172        public void rollbackToSavepoint(Object savepoint) throws TransactionException {
173                ConnectionHolder conHolder = getConnectionHolderForSavepoint();
174                try {
175                        conHolder.getConnection().rollback((Savepoint) savepoint);
176                        conHolder.resetRollbackOnly();
177                }
178                catch (Throwable ex) {
179                        throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
180                }
181        }
182
183        /**
184         * This implementation releases the given JDBC 3.0 Savepoint.
185         * @see java.sql.Connection#releaseSavepoint
186         */
187        @Override
188        public void releaseSavepoint(Object savepoint) throws TransactionException {
189                ConnectionHolder conHolder = getConnectionHolderForSavepoint();
190                try {
191                        conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);
192                }
193                catch (Throwable ex) {
194                        logger.debug("Could not explicitly release JDBC savepoint", ex);
195                }
196        }
197
198        protected ConnectionHolder getConnectionHolderForSavepoint() throws TransactionException {
199                if (!isSavepointAllowed()) {
200                        throw new NestedTransactionNotSupportedException(
201                                        "Transaction manager does not allow nested transactions");
202                }
203                if (!hasConnectionHolder()) {
204                        throw new TransactionUsageException(
205                                        "Cannot create nested transaction when not exposing a JDBC transaction");
206                }
207                return getConnectionHolder();
208        }
209
210}