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.lang.reflect.InvocationHandler; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.sql.Connection; 024import java.sql.SQLException; 025 026import org.springframework.beans.factory.DisposableBean; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ObjectUtils; 030 031/** 032 * Implementation of {@link SmartDataSource} that wraps a single JDBC Connection 033 * which is not closed after use. Obviously, this is not multi-threading capable. 034 * 035 * <p>Note that at shutdown, someone should close the underlying Connection 036 * via the {@code close()} method. Client code will never call close 037 * on the Connection handle if it is SmartDataSource-aware (e.g. uses 038 * {@code DataSourceUtils.releaseConnection}). 039 * 040 * <p>If client code will call {@code close()} in the assumption of a pooled 041 * Connection, like when using persistence tools, set "suppressClose" to "true". 042 * This will return a close-suppressing proxy instead of the physical Connection. 043 * 044 * <p>This is primarily intended for testing. For example, it enables easy testing 045 * outside an application server, for code that expects to work on a DataSource. 046 * In contrast to {@link DriverManagerDataSource}, it reuses the same Connection 047 * all the time, avoiding excessive creation of physical Connections. 048 * 049 * @author Rod Johnson 050 * @author Juergen Hoeller 051 * @see #getConnection() 052 * @see java.sql.Connection#close() 053 * @see DataSourceUtils#releaseConnection 054 */ 055public class SingleConnectionDataSource extends DriverManagerDataSource implements SmartDataSource, DisposableBean { 056 057 /** Create a close-suppressing proxy?. */ 058 private boolean suppressClose; 059 060 /** Override auto-commit state?. */ 061 @Nullable 062 private Boolean autoCommit; 063 064 /** Wrapped Connection. */ 065 @Nullable 066 private Connection target; 067 068 /** Proxy Connection. */ 069 @Nullable 070 private Connection connection; 071 072 /** Synchronization monitor for the shared Connection. */ 073 private final Object connectionMonitor = new Object(); 074 075 076 /** 077 * Constructor for bean-style configuration. 078 */ 079 public SingleConnectionDataSource() { 080 } 081 082 /** 083 * Create a new SingleConnectionDataSource with the given standard 084 * DriverManager parameters. 085 * @param url the JDBC URL to use for accessing the DriverManager 086 * @param username the JDBC username to use for accessing the DriverManager 087 * @param password the JDBC password to use for accessing the DriverManager 088 * @param suppressClose if the returned Connection should be a 089 * close-suppressing proxy or the physical Connection 090 * @see java.sql.DriverManager#getConnection(String, String, String) 091 */ 092 public SingleConnectionDataSource(String url, String username, String password, boolean suppressClose) { 093 super(url, username, password); 094 this.suppressClose = suppressClose; 095 } 096 097 /** 098 * Create a new SingleConnectionDataSource with the given standard 099 * DriverManager parameters. 100 * @param url the JDBC URL to use for accessing the DriverManager 101 * @param suppressClose if the returned Connection should be a 102 * close-suppressing proxy or the physical Connection 103 * @see java.sql.DriverManager#getConnection(String, String, String) 104 */ 105 public SingleConnectionDataSource(String url, boolean suppressClose) { 106 super(url); 107 this.suppressClose = suppressClose; 108 } 109 110 /** 111 * Create a new SingleConnectionDataSource with a given Connection. 112 * @param target underlying target Connection 113 * @param suppressClose if the Connection should be wrapped with a Connection that 114 * suppresses {@code close()} calls (to allow for normal {@code close()} 115 * usage in applications that expect a pooled Connection but do not know our 116 * SmartDataSource interface) 117 */ 118 public SingleConnectionDataSource(Connection target, boolean suppressClose) { 119 Assert.notNull(target, "Connection must not be null"); 120 this.target = target; 121 this.suppressClose = suppressClose; 122 this.connection = (suppressClose ? getCloseSuppressingConnectionProxy(target) : target); 123 } 124 125 126 /** 127 * Set whether the returned Connection should be a close-suppressing proxy 128 * or the physical Connection. 129 */ 130 public void setSuppressClose(boolean suppressClose) { 131 this.suppressClose = suppressClose; 132 } 133 134 /** 135 * Return whether the returned Connection will be a close-suppressing proxy 136 * or the physical Connection. 137 */ 138 protected boolean isSuppressClose() { 139 return this.suppressClose; 140 } 141 142 /** 143 * Set whether the returned Connection's "autoCommit" setting should be overridden. 144 */ 145 public void setAutoCommit(boolean autoCommit) { 146 this.autoCommit = (autoCommit); 147 } 148 149 /** 150 * Return whether the returned Connection's "autoCommit" setting should be overridden. 151 * @return the "autoCommit" value, or {@code null} if none to be applied 152 */ 153 @Nullable 154 protected Boolean getAutoCommitValue() { 155 return this.autoCommit; 156 } 157 158 159 @Override 160 public Connection getConnection() throws SQLException { 161 synchronized (this.connectionMonitor) { 162 if (this.connection == null) { 163 // No underlying Connection -> lazy init via DriverManager. 164 initConnection(); 165 } 166 if (this.connection.isClosed()) { 167 throw new SQLException( 168 "Connection was closed in SingleConnectionDataSource. Check that user code checks " + 169 "shouldClose() before closing Connections, or set 'suppressClose' to 'true'"); 170 } 171 return this.connection; 172 } 173 } 174 175 /** 176 * Specifying a custom username and password doesn't make sense 177 * with a single Connection. Returns the single Connection if given 178 * the same username and password; throws an SQLException else. 179 */ 180 @Override 181 public Connection getConnection(String username, String password) throws SQLException { 182 if (ObjectUtils.nullSafeEquals(username, getUsername()) && 183 ObjectUtils.nullSafeEquals(password, getPassword())) { 184 return getConnection(); 185 } 186 else { 187 throw new SQLException("SingleConnectionDataSource does not support custom username and password"); 188 } 189 } 190 191 /** 192 * This is a single Connection: Do not close it when returning to the "pool". 193 */ 194 @Override 195 public boolean shouldClose(Connection con) { 196 synchronized (this.connectionMonitor) { 197 return (con != this.connection && con != this.target); 198 } 199 } 200 201 /** 202 * Close the underlying Connection. 203 * The provider of this DataSource needs to care for proper shutdown. 204 * <p>As this bean implements DisposableBean, a bean factory will 205 * automatically invoke this on destruction of its cached singletons. 206 */ 207 @Override 208 public void destroy() { 209 synchronized (this.connectionMonitor) { 210 closeConnection(); 211 } 212 } 213 214 215 /** 216 * Initialize the underlying Connection via the DriverManager. 217 */ 218 public void initConnection() throws SQLException { 219 if (getUrl() == null) { 220 throw new IllegalStateException("'url' property is required for lazily initializing a Connection"); 221 } 222 synchronized (this.connectionMonitor) { 223 closeConnection(); 224 this.target = getConnectionFromDriver(getUsername(), getPassword()); 225 prepareConnection(this.target); 226 if (logger.isDebugEnabled()) { 227 logger.debug("Established shared JDBC Connection: " + this.target); 228 } 229 this.connection = (isSuppressClose() ? getCloseSuppressingConnectionProxy(this.target) : this.target); 230 } 231 } 232 233 /** 234 * Reset the underlying shared Connection, to be reinitialized on next access. 235 */ 236 public void resetConnection() { 237 synchronized (this.connectionMonitor) { 238 closeConnection(); 239 this.target = null; 240 this.connection = null; 241 } 242 } 243 244 /** 245 * Prepare the given Connection before it is exposed. 246 * <p>The default implementation applies the auto-commit flag, if necessary. 247 * Can be overridden in subclasses. 248 * @param con the Connection to prepare 249 * @see #setAutoCommit 250 */ 251 protected void prepareConnection(Connection con) throws SQLException { 252 Boolean autoCommit = getAutoCommitValue(); 253 if (autoCommit != null && con.getAutoCommit() != autoCommit) { 254 con.setAutoCommit(autoCommit); 255 } 256 } 257 258 /** 259 * Close the underlying shared Connection. 260 */ 261 private void closeConnection() { 262 if (this.target != null) { 263 try { 264 this.target.close(); 265 } 266 catch (Throwable ex) { 267 logger.info("Could not close shared JDBC Connection", ex); 268 } 269 } 270 } 271 272 /** 273 * Wrap the given Connection with a proxy that delegates every method call to it 274 * but suppresses close calls. 275 * @param target the original Connection to wrap 276 * @return the wrapped Connection 277 */ 278 protected Connection getCloseSuppressingConnectionProxy(Connection target) { 279 return (Connection) Proxy.newProxyInstance( 280 ConnectionProxy.class.getClassLoader(), 281 new Class<?>[] {ConnectionProxy.class}, 282 new CloseSuppressingInvocationHandler(target)); 283 } 284 285 286 /** 287 * Invocation handler that suppresses close calls on JDBC Connections. 288 */ 289 private static class CloseSuppressingInvocationHandler implements InvocationHandler { 290 291 private final Connection target; 292 293 public CloseSuppressingInvocationHandler(Connection target) { 294 this.target = target; 295 } 296 297 @Override 298 @Nullable 299 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 300 // Invocation on ConnectionProxy interface coming in... 301 302 if (method.getName().equals("equals")) { 303 // Only consider equal when proxies are identical. 304 return (proxy == args[0]); 305 } 306 else if (method.getName().equals("hashCode")) { 307 // Use hashCode of Connection proxy. 308 return System.identityHashCode(proxy); 309 } 310 else if (method.getName().equals("unwrap")) { 311 if (((Class<?>) args[0]).isInstance(proxy)) { 312 return proxy; 313 } 314 } 315 else if (method.getName().equals("isWrapperFor")) { 316 if (((Class<?>) args[0]).isInstance(proxy)) { 317 return true; 318 } 319 } 320 else if (method.getName().equals("close")) { 321 // Handle close method: don't pass the call on. 322 return null; 323 } 324 else if (method.getName().equals("isClosed")) { 325 return this.target.isClosed(); 326 } 327 else if (method.getName().equals("getTargetConnection")) { 328 // Handle getTargetConnection method: return underlying Connection. 329 return this.target; 330 } 331 332 // Invoke method on target Connection. 333 try { 334 return method.invoke(this.target, args); 335 } 336 catch (InvocationTargetException ex) { 337 throw ex.getTargetException(); 338 } 339 } 340 } 341 342}