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