1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.dbcp2.cpdsadapter;
18
19 import java.sql.CallableStatement;
20 import java.sql.Connection;
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.sql.Statement;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28
29 import javax.sql.ConnectionEvent;
30 import javax.sql.ConnectionEventListener;
31 import javax.sql.PooledConnection;
32 import javax.sql.StatementEventListener;
33
34 import org.apache.commons.dbcp2.DelegatingConnection;
35 import org.apache.commons.dbcp2.DelegatingPreparedStatement;
36 import org.apache.commons.dbcp2.Jdbc41Bridge;
37 import org.apache.commons.dbcp2.PStmtKey;
38 import org.apache.commons.dbcp2.PoolableCallableStatement;
39 import org.apache.commons.dbcp2.PoolablePreparedStatement;
40 import org.apache.commons.dbcp2.PoolingConnection.StatementType;
41 import org.apache.commons.dbcp2.Utils;
42 import org.apache.commons.pool2.KeyedObjectPool;
43 import org.apache.commons.pool2.KeyedPooledObjectFactory;
44 import org.apache.commons.pool2.PooledObject;
45 import org.apache.commons.pool2.impl.DefaultPooledObject;
46
47 /**
48 * Implements {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}.
49 *
50 * @since 2.0
51 */
52 final class PooledConnectionImpl
53 implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
54
55 private static final String CLOSED = "Attempted to use PooledConnection after closed() was called.";
56
57 /**
58 * The JDBC database connection that represents the physical db connection.
59 */
60 private Connection connection;
61
62 /**
63 * A DelegatingConnection used to create a PoolablePreparedStatementStub.
64 */
65 private final DelegatingConnection<?> delegatingConnection;
66
67 /**
68 * The JDBC database logical connection.
69 */
70 private Connection logicalConnection;
71
72 /**
73 * ConnectionEventListeners.
74 */
75 private final List<ConnectionEventListener> eventListeners;
76
77 /**
78 * StatementEventListeners.
79 */
80 private final List<StatementEventListener> statementEventListeners = Collections.synchronizedList(new ArrayList<>());
81
82 /**
83 * Flag set to true, once {@link #close()} is called.
84 */
85 private volatile boolean closed;
86
87 /** My pool of {@link PreparedStatement}s. */
88 private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool;
89
90 /**
91 * Controls access to the underlying connection.
92 */
93 private boolean accessToUnderlyingConnectionAllowed;
94
95 /**
96 * Wraps a real connection.
97 *
98 * @param connection
99 * the connection to be wrapped.
100 */
101 PooledConnectionImpl(final Connection connection) {
102 this.connection = connection;
103 if (connection instanceof DelegatingConnection) {
104 this.delegatingConnection = (DelegatingConnection<?>) connection;
105 } else {
106 this.delegatingConnection = new DelegatingConnection<>(connection);
107 }
108 eventListeners = Collections.synchronizedList(new ArrayList<>());
109 closed = false;
110 }
111
112 /**
113 * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s.
114 *
115 * @param pooledObject Activates the underlying object.
116 */
117 @Override
118 public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
119 throws SQLException {
120 pooledObject.getObject().activate();
121 }
122
123 /**
124 * {@inheritDoc}
125 */
126 @Override
127 public void addConnectionEventListener(final ConnectionEventListener listener) {
128 if (!eventListeners.contains(listener)) {
129 eventListeners.add(listener);
130 }
131 }
132
133 @Override
134 public void addStatementEventListener(final StatementEventListener listener) {
135 if (!statementEventListeners.contains(listener)) {
136 statementEventListeners.add(listener);
137 }
138 }
139
140 /**
141 * Throws an SQLException, if isClosed is true
142 */
143 private void assertOpen() throws SQLException {
144 if (closed || connection == null) {
145 throw new SQLException(CLOSED);
146 }
147 }
148
149 /**
150 * Closes the physical connection and marks this {@link PooledConnection} so that it may not be used to
151 * generate any more logical {@link Connection}s.
152 *
153 * @throws SQLException
154 * Thrown when an error occurs or the connection is already closed.
155 */
156 @Override
157 public void close() throws SQLException {
158 assertOpen();
159 closed = true;
160 try {
161 if (stmtPool != null) {
162 try {
163 stmtPool.close();
164 } finally {
165 stmtPool = null;
166 }
167 }
168 } catch (final RuntimeException e) {
169 throw e;
170 } catch (final Exception e) {
171 throw new SQLException("Cannot close connection (return to pool failed)", e);
172 } finally {
173 try {
174 connection.close();
175 } finally {
176 connection = null;
177 }
178 }
179 }
180
181 /**
182 * Creates a {@link PStmtKey} for the given arguments.
183 *
184 * @param sql
185 * The SQL statement.
186 * @return a {@link PStmtKey} for the given arguments.
187 */
188 protected PStmtKey createKey(final String sql) {
189 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull());
190 }
191
192 /**
193 * Creates a {@link PStmtKey} for the given arguments.
194 *
195 * @param sql
196 * The SQL statement.
197 * @param autoGeneratedKeys
198 * A flag indicating whether auto-generated keys should be returned; one of
199 * {@link Statement#RETURN_GENERATED_KEYS} or {@link Statement#NO_GENERATED_KEYS}.
200 * @return a key to uniquely identify a prepared statement.
201 */
202 protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
203 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
204 }
205
206 /**
207 * Creates a {@link PStmtKey} for the given arguments.
208 *
209 * @param sql
210 * The SQL statement.
211 * @param resultSetType
212 * A result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
213 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
214 * @param resultSetConcurrency
215 * A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
216 * {@link ResultSet#CONCUR_UPDATABLE}.
217 * @return a key to uniquely identify a prepared statement.
218 */
219 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
220 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
221 }
222
223 /**
224 * Creates a {@link PStmtKey} for the given arguments.
225 *
226 * @param sql
227 * The SQL statement.
228 * @param resultSetType
229 * a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
230 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
231 * @param resultSetConcurrency
232 * A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
233 * {@link ResultSet#CONCUR_UPDATABLE}
234 * @param resultSetHoldability
235 * One of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
236 * or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
237 * @return a key to uniquely identify a prepared statement.
238 */
239 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
240 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability);
241 }
242
243 /**
244 * Creates a {@link PStmtKey} for the given arguments.
245 *
246 * @param sql
247 * The SQL statement.
248 * @param resultSetType
249 * a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
250 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}
251 * @param resultSetConcurrency
252 * A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
253 * {@link ResultSet#CONCUR_UPDATABLE}.
254 * @param resultSetHoldability
255 * One of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
256 * or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
257 * @param statementType
258 * The SQL statement type, prepared or callable.
259 * @return a key to uniquely identify a prepared statement.
260 * @since 2.4.0
261 */
262 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability,
263 final StatementType statementType) {
264 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, statementType);
265 }
266
267 /**
268 * Creates a {@link PStmtKey} for the given arguments.
269 *
270 * @param sql
271 * The SQL statement.
272 * @param resultSetType
273 * A result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
274 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
275 * @param resultSetConcurrency
276 * A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
277 * {@link ResultSet#CONCUR_UPDATABLE}.
278 * @param statementType
279 * The SQL statement type, prepared or callable.
280 * @return a key to uniquely identify a prepared statement.
281 * @since 2.4.0
282 */
283 protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) {
284 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType);
285 }
286
287 /**
288 * Creates a {@link PStmtKey} for the given arguments.
289 *
290 * @param sql
291 * The SQL statement.
292 * @param columnIndexes
293 * An array of column indexes indicating the columns that should be returned from the inserted row or
294 * rows.
295 * @return a key to uniquely identify a prepared statement.
296 */
297 protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
298 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
299 }
300
301 /**
302 * Creates a {@link PStmtKey} for the given arguments.
303 *
304 * @param sql
305 * The SQL statement.
306 * @param statementType
307 * The SQL statement type, prepared or callable.
308 * @return a key to uniquely identify a prepared statement.
309 */
310 protected PStmtKey createKey(final String sql, final StatementType statementType) {
311 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), statementType);
312 }
313
314 /**
315 * Creates a {@link PStmtKey} for the given arguments.
316 *
317 * @param sql
318 * The SQL statement.
319 * @param columnNames
320 * An array of column names indicating the columns that should be returned from the inserted row or rows.
321 * @return a key to uniquely identify a prepared statement.
322 */
323 protected PStmtKey createKey(final String sql, final String[] columnNames) {
324 return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnNames);
325 }
326
327 /**
328 * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s.
329 *
330 * @param key
331 * ignored
332 * @param pooledObject
333 * the wrapped {@link PreparedStatement} to be destroyed.
334 */
335 @Override
336 public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) throws SQLException {
337 if (pooledObject != null) {
338 @SuppressWarnings("resource")
339 final DelegatingPreparedStatement object = pooledObject.getObject();
340 if (object != null) {
341 @SuppressWarnings("resource")
342 final Statement innermostDelegate = object.getInnermostDelegate();
343 if (innermostDelegate != null) {
344 innermostDelegate.close();
345 }
346 }
347 }
348 }
349
350 /**
351 * Closes the physical connection and checks that the logical connection was closed as well.
352 */
353 @Override
354 protected void finalize() throws Throwable {
355 // Closing the Connection ensures that if anyone tries to use it,
356 // an error will occur.
357 Utils.close(connection, null);
358 // make sure the last connection is marked as closed
359 if (logicalConnection != null && !logicalConnection.isClosed()) {
360 throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed.");
361 }
362 }
363
364 private String getCatalogOrNull() {
365 try {
366 return connection == null ? null : connection.getCatalog();
367 } catch (final SQLException e) {
368 return null;
369 }
370 }
371
372 /**
373 * Returns a JDBC connection.
374 *
375 * @return The database connection.
376 * @throws SQLException
377 * if the connection is not open or the previous logical connection is still open
378 */
379 @Override
380 public Connection getConnection() throws SQLException {
381 assertOpen();
382 // make sure the last connection is marked as closed
383 if (logicalConnection != null && !logicalConnection.isClosed()) {
384 // should notify pool of error so the pooled connection can
385 // be removed !FIXME!
386 throw new SQLException("PooledConnection was reused, without its previous Connection being closed.");
387 }
388
389 // the spec requires that this return a new Connection instance.
390 logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed());
391 return logicalConnection;
392 }
393
394 private Connection getRawConnection() throws SQLException {
395 assertOpen();
396 return connection;
397 }
398
399 private String getSchemaOrNull() {
400 try {
401 return connection == null ? null : Jdbc41Bridge.getSchema(connection);
402 } catch (final SQLException e) {
403 return null;
404 }
405 }
406
407 /**
408 * Returns the value of the accessToUnderlyingConnectionAllowed property.
409 *
410 * @return true if access to the underlying is allowed, false otherwise.
411 */
412 public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
413 return this.accessToUnderlyingConnectionAllowed;
414 }
415
416 /**
417 * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s.
418 *
419 * @param key
420 * The key for the {@link PreparedStatement} to be created.
421 */
422 @SuppressWarnings("resource")
423 @Override
424 public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
425 if (null == key) {
426 throw new IllegalArgumentException("Prepared statement key is null or invalid.");
427 }
428 if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
429 final PreparedStatement statement = (PreparedStatement) key.createStatement(connection);
430 @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
431 final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, stmtPool,
432 delegatingConnection);
433 return new DefaultPooledObject<>(pps);
434 }
435 final CallableStatement statement = (CallableStatement) key.createStatement(connection);
436 @SuppressWarnings("unchecked")
437 final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, stmtPool,
438 (DelegatingConnection<Connection>) delegatingConnection);
439 return new DefaultPooledObject<>(pcs);
440 }
441
442 /**
443 * Sends a connectionClosed event.
444 */
445 void notifyListeners() {
446 final ConnectionEvent event = new ConnectionEvent(this);
447 new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event));
448 }
449
450 /**
451 * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently, invokes
452 * {@link PreparedStatement#clearParameters}.
453 *
454 * @param key
455 * ignored
456 * @param pooledObject
457 * a wrapped {@link PreparedStatement}
458 */
459 @Override
460 public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
461 throws SQLException {
462 @SuppressWarnings("resource")
463 final DelegatingPreparedStatement dps = pooledObject.getObject();
464 dps.clearParameters();
465 dps.passivate();
466 }
467
468 /**
469 * Creates or obtains a {@link CallableStatement} from my pool.
470 *
471 * @param sql
472 * an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is
473 * specified using JDBC call escape syntax.
474 * @return a default {@link CallableStatement} object containing the pre-compiled SQL statement.
475 * @throws SQLException
476 * Thrown if a database access error occurs or this method is called on a closed connection.
477 * @since 2.4.0
478 */
479 @SuppressWarnings("resource") // getRawConnection() does not allocate
480 CallableStatement prepareCall(final String sql) throws SQLException {
481 if (stmtPool == null) {
482 return getRawConnection().prepareCall(sql);
483 }
484 try {
485 return (CallableStatement) stmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT));
486 } catch (final RuntimeException e) {
487 throw e;
488 } catch (final Exception e) {
489 throw new SQLException("Borrow prepareCall from pool failed", e);
490 }
491 }
492
493 /**
494 * Creates or obtains a {@link CallableStatement} from my pool.
495 *
496 * @param sql
497 * a {@link String} object that is the SQL statement to be sent to the database; may contain on or
498 * more '?' parameters.
499 * @param resultSetType
500 * a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
501 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
502 * @param resultSetConcurrency
503 * a concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
504 * {@link ResultSet#CONCUR_UPDATABLE}.
505 * @return a {@link CallableStatement} object containing the pre-compiled SQL statement that will produce
506 * {@link ResultSet} objects with the given type and concurrency.
507 * @throws SQLException
508 * Thrown if a database access error occurs, this method is called on a closed connection or the given
509 * parameters are not {@link ResultSet} constants indicating type and concurrency.
510 * @since 2.4.0
511 */
512 @SuppressWarnings("resource") // getRawConnection() does not allocate
513 CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
514 throws SQLException {
515 if (stmtPool == null) {
516 return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
517 }
518 try {
519 return (CallableStatement) stmtPool.borrowObject(
520 createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
521 } catch (final RuntimeException e) {
522 throw e;
523 } catch (final Exception e) {
524 throw new SQLException("Borrow prepareCall from pool failed", e);
525 }
526 }
527
528 /**
529 * Creates or obtains a {@link CallableStatement} from my pool.
530 *
531 * @param sql
532 * a {@link String} object that is the SQL statement to be sent to the database; may contain on or
533 * more '?' parameters.
534 * @param resultSetType
535 * one of the following {@link ResultSet} constants: {@link ResultSet#TYPE_FORWARD_ONLY},
536 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
537 * @param resultSetConcurrency
538 * one of the following {@link ResultSet} constants: {@link ResultSet#CONCUR_READ_ONLY} or
539 * {@link ResultSet#CONCUR_UPDATABLE}.
540 * @param resultSetHoldability
541 * one of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
542 * or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
543 * @return a new {@link CallableStatement} object, containing the pre-compiled SQL statement, that will
544 * generate {@link ResultSet} objects with the given type, concurrency, and holdability.
545 * @throws SQLException
546 * Thrown if a database access error occurs, this method is called on a closed connection or the given
547 * parameters are not {@link ResultSet} constants indicating type, concurrency, and holdability.
548 * @since 2.4.0
549 */
550 @SuppressWarnings("resource") // getRawConnection() does not allocate
551 CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
552 final int resultSetHoldability) throws SQLException {
553 if (stmtPool == null) {
554 return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
555 }
556 try {
557 return (CallableStatement) stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency,
558 resultSetHoldability, StatementType.CALLABLE_STATEMENT));
559 } catch (final RuntimeException e) {
560 throw e;
561 } catch (final Exception e) {
562 throw new SQLException("Borrow prepareCall from pool failed", e);
563 }
564 }
565
566 /**
567 * Creates or obtains a {@link PreparedStatement} from my pool.
568 *
569 * @param sql the SQL statement.
570 * @return a {@link PoolablePreparedStatement}
571 * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
572 * the borrow failed.
573 */
574 @SuppressWarnings("resource") // getRawConnection() does not allocate
575 PreparedStatement prepareStatement(final String sql) throws SQLException {
576 if (stmtPool == null) {
577 return getRawConnection().prepareStatement(sql);
578 }
579 try {
580 return stmtPool.borrowObject(createKey(sql));
581 } catch (final RuntimeException e) {
582 throw e;
583 } catch (final Exception e) {
584 throw new SQLException("Borrow prepareStatement from pool failed", e);
585 }
586 }
587
588 /**
589 * Creates or obtains a {@link PreparedStatement} from my pool.
590 *
591 * @param sql
592 * an SQL statement that may contain one or more '?' IN parameter placeholders.
593 * @param autoGeneratedKeys
594 * a flag indicating whether auto-generated keys should be returned; one of
595 * {@link Statement#RETURN_GENERATED_KEYS} or {@link Statement#NO_GENERATED_KEYS}.
596 * @return a {@link PoolablePreparedStatement}
597 * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
598 * the borrow failed.
599 * @see Connection#prepareStatement(String, int)
600 */
601 @SuppressWarnings("resource") // getRawConnection() does not allocate
602 PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
603 if (stmtPool == null) {
604 return getRawConnection().prepareStatement(sql, autoGeneratedKeys);
605 }
606 try {
607 return stmtPool.borrowObject(createKey(sql, autoGeneratedKeys));
608 } catch (final RuntimeException e) {
609 throw e;
610 } catch (final Exception e) {
611 throw new SQLException("Borrow prepareStatement from pool failed", e);
612 }
613 }
614
615 /**
616 * Creates or obtains a {@link PreparedStatement} from my pool.
617 *
618 * @param sql
619 * a {@link String} object that is the SQL statement to be sent to the database; may contain one or
620 * more '?' IN parameters.
621 * @param resultSetType
622 * a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
623 * {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
624 * @param resultSetConcurrency
625 * a concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
626 * {@link ResultSet#CONCUR_UPDATABLE}.
627 *
628 * @return a {@link PoolablePreparedStatement}.
629 * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
630 * the borrow failed.
631 * @see Connection#prepareStatement(String, int, int)
632 */
633 @SuppressWarnings("resource") // getRawConnection() does not allocate
634 PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
635 throws SQLException {
636 if (stmtPool == null) {
637 return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency);
638 }
639 try {
640 return stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency));
641 } catch (final RuntimeException e) {
642 throw e;
643 } catch (final Exception e) {
644 throw new SQLException("Borrow prepareStatement from pool failed", e);
645 }
646 }
647
648 @SuppressWarnings("resource") // getRawConnection() does not allocate
649 PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
650 final int resultSetHoldability) throws SQLException {
651 if (stmtPool == null) {
652 return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
653 }
654 try {
655 return stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
656 } catch (final RuntimeException e) {
657 throw e;
658 } catch (final Exception e) {
659 throw new SQLException("Borrow prepareStatement from pool failed", e);
660 }
661 }
662
663 @SuppressWarnings("resource") // getRawConnection() does not allocate
664 PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
665 if (stmtPool == null) {
666 return getRawConnection().prepareStatement(sql, columnIndexes);
667 }
668 try {
669 return stmtPool.borrowObject(createKey(sql, columnIndexes));
670 } catch (final RuntimeException e) {
671 throw e;
672 } catch (final Exception e) {
673 throw new SQLException("Borrow prepareStatement from pool failed", e);
674 }
675 }
676
677 @SuppressWarnings("resource") // getRawConnection() does not allocate
678 PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
679 if (stmtPool == null) {
680 return getRawConnection().prepareStatement(sql, columnNames);
681 }
682 try {
683 return stmtPool.borrowObject(createKey(sql, columnNames));
684 } catch (final RuntimeException e) {
685 throw e;
686 } catch (final Exception e) {
687 throw new SQLException("Borrow prepareStatement from pool failed", e);
688 }
689 }
690
691 /**
692 * {@inheritDoc}
693 */
694 @Override
695 public void removeConnectionEventListener(final ConnectionEventListener listener) {
696 eventListeners.remove(listener);
697 }
698
699 @Override
700 public void removeStatementEventListener(final StatementEventListener listener) {
701 statementEventListeners.remove(listener);
702 }
703
704 /**
705 * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
706 * the underlying connection. (Default: false.)
707 *
708 * @param allow
709 * Access to the underlying connection is granted when true.
710 */
711 public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
712 this.accessToUnderlyingConnectionAllowed = allow;
713 }
714
715 public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> statementPool) {
716 stmtPool = statementPool;
717 }
718
719 /**
720 * @since 2.6.0
721 */
722 @Override
723 public synchronized String toString() {
724 final StringBuilder builder = new StringBuilder(super.toString());
725 builder.append("[connection=");
726 builder.append(connection);
727 builder.append(", delegatingConnection=");
728 builder.append(delegatingConnection);
729 builder.append(", logicalConnection=");
730 builder.append(logicalConnection);
731 builder.append(", eventListeners=");
732 builder.append(eventListeners);
733 builder.append(", statementEventListeners=");
734 builder.append(statementEventListeners);
735 builder.append(", closed=");
736 builder.append(closed);
737 builder.append(", stmtPool=");
738 builder.append(stmtPool);
739 builder.append(", accessToUnderlyingConnectionAllowed=");
740 builder.append(accessToUnderlyingConnectionAllowed);
741 builder.append("]");
742 return builder.toString();
743 }
744
745 /**
746 * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
747 *
748 * @param key
749 * Ignored.
750 * @param pooledObject
751 * Ignored.
752 * @return {@code true}
753 */
754 @Override
755 public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
756 return true;
757 }
758 }