001/*
002 * Copyright 2002-2018 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.Savepoint;
022
023import org.springframework.lang.Nullable;
024import org.springframework.transaction.support.ResourceHolderSupport;
025import org.springframework.util.Assert;
026
027/**
028 * Resource holder wrapping a JDBC {@link Connection}.
029 * {@link DataSourceTransactionManager} binds instances of this class
030 * to the thread, for a specific {@link javax.sql.DataSource}.
031 *
032 * <p>Inherits rollback-only support for nested JDBC transactions
033 * and reference count functionality from the base class.
034 *
035 * <p>Note: This is an SPI class, not intended to be used by applications.
036 *
037 * @author Juergen Hoeller
038 * @since 06.05.2003
039 * @see DataSourceTransactionManager
040 * @see DataSourceUtils
041 */
042public class ConnectionHolder extends ResourceHolderSupport {
043
044        /**
045         * Prefix for savepoint names.
046         */
047        public static final String SAVEPOINT_NAME_PREFIX = "SAVEPOINT_";
048
049
050        @Nullable
051        private ConnectionHandle connectionHandle;
052
053        @Nullable
054        private Connection currentConnection;
055
056        private boolean transactionActive = false;
057
058        @Nullable
059        private Boolean savepointsSupported;
060
061        private int savepointCounter = 0;
062
063
064        /**
065         * Create a new ConnectionHolder for the given ConnectionHandle.
066         * @param connectionHandle the ConnectionHandle to hold
067         */
068        public ConnectionHolder(ConnectionHandle connectionHandle) {
069                Assert.notNull(connectionHandle, "ConnectionHandle must not be null");
070                this.connectionHandle = connectionHandle;
071        }
072
073        /**
074         * Create a new ConnectionHolder for the given JDBC Connection,
075         * wrapping it with a {@link SimpleConnectionHandle},
076         * assuming that there is no ongoing transaction.
077         * @param connection the JDBC Connection to hold
078         * @see SimpleConnectionHandle
079         * @see #ConnectionHolder(java.sql.Connection, boolean)
080         */
081        public ConnectionHolder(Connection connection) {
082                this.connectionHandle = new SimpleConnectionHandle(connection);
083        }
084
085        /**
086         * Create a new ConnectionHolder for the given JDBC Connection,
087         * wrapping it with a {@link SimpleConnectionHandle}.
088         * @param connection the JDBC Connection to hold
089         * @param transactionActive whether the given Connection is involved
090         * in an ongoing transaction
091         * @see SimpleConnectionHandle
092         */
093        public ConnectionHolder(Connection connection, boolean transactionActive) {
094                this(connection);
095                this.transactionActive = transactionActive;
096        }
097
098
099        /**
100         * Return the ConnectionHandle held by this ConnectionHolder.
101         */
102        @Nullable
103        public ConnectionHandle getConnectionHandle() {
104                return this.connectionHandle;
105        }
106
107        /**
108         * Return whether this holder currently has a Connection.
109         */
110        protected boolean hasConnection() {
111                return (this.connectionHandle != null);
112        }
113
114        /**
115         * Set whether this holder represents an active, JDBC-managed transaction.
116         * @see DataSourceTransactionManager
117         */
118        protected void setTransactionActive(boolean transactionActive) {
119                this.transactionActive = transactionActive;
120        }
121
122        /**
123         * Return whether this holder represents an active, JDBC-managed transaction.
124         */
125        protected boolean isTransactionActive() {
126                return this.transactionActive;
127        }
128
129
130        /**
131         * Override the existing Connection handle with the given Connection.
132         * Reset the handle if given {@code null}.
133         * <p>Used for releasing the Connection on suspend (with a {@code null}
134         * argument) and setting a fresh Connection on resume.
135         */
136        protected void setConnection(@Nullable Connection connection) {
137                if (this.currentConnection != null) {
138                        if (this.connectionHandle != null) {
139                                this.connectionHandle.releaseConnection(this.currentConnection);
140                        }
141                        this.currentConnection = null;
142                }
143                if (connection != null) {
144                        this.connectionHandle = new SimpleConnectionHandle(connection);
145                }
146                else {
147                        this.connectionHandle = null;
148                }
149        }
150
151        /**
152         * Return the current Connection held by this ConnectionHolder.
153         * <p>This will be the same Connection until {@code released}
154         * gets called on the ConnectionHolder, which will reset the
155         * held Connection, fetching a new Connection on demand.
156         * @see ConnectionHandle#getConnection()
157         * @see #released()
158         */
159        public Connection getConnection() {
160                Assert.notNull(this.connectionHandle, "Active Connection is required");
161                if (this.currentConnection == null) {
162                        this.currentConnection = this.connectionHandle.getConnection();
163                }
164                return this.currentConnection;
165        }
166
167        /**
168         * Return whether JDBC 3.0 Savepoints are supported.
169         * Caches the flag for the lifetime of this ConnectionHolder.
170         * @throws SQLException if thrown by the JDBC driver
171         */
172        public boolean supportsSavepoints() throws SQLException {
173                if (this.savepointsSupported == null) {
174                        this.savepointsSupported = getConnection().getMetaData().supportsSavepoints();
175                }
176                return this.savepointsSupported;
177        }
178
179        /**
180         * Create a new JDBC 3.0 Savepoint for the current Connection,
181         * using generated savepoint names that are unique for the Connection.
182         * @return the new Savepoint
183         * @throws SQLException if thrown by the JDBC driver
184         */
185        public Savepoint createSavepoint() throws SQLException {
186                this.savepointCounter++;
187                return getConnection().setSavepoint(SAVEPOINT_NAME_PREFIX + this.savepointCounter);
188        }
189
190        /**
191         * Releases the current Connection held by this ConnectionHolder.
192         * <p>This is necessary for ConnectionHandles that expect "Connection borrowing",
193         * where each returned Connection is only temporarily leased and needs to be
194         * returned once the data operation is done, to make the Connection available
195         * for other operations within the same transaction.
196         */
197        @Override
198        public void released() {
199                super.released();
200                if (!isOpen() && this.currentConnection != null) {
201                        if (this.connectionHandle != null) {
202                                this.connectionHandle.releaseConnection(this.currentConnection);
203                        }
204                        this.currentConnection = null;
205                }
206        }
207
208
209        @Override
210        public void clear() {
211                super.clear();
212                this.transactionActive = false;
213                this.savepointsSupported = null;
214                this.savepointCounter = 0;
215        }
216
217}