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}