001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.dbcp2; 019 020import java.sql.PreparedStatement; 021import java.sql.ResultSet; 022import java.sql.SQLException; 023import java.util.ArrayList; 024import java.util.List; 025 026import org.apache.commons.pool2.KeyedObjectPool; 027 028/** 029 * A {@link DelegatingPreparedStatement} that cooperates with {@link PoolingConnection} to implement a pool of 030 * {@link PreparedStatement}s. 031 * <p> 032 * My {@link #close} method returns me to my containing pool. (See {@link PoolingConnection}.) 033 * </p> 034 * 035 * @param <K> 036 * the key type 037 * 038 * @see PoolingConnection 039 * @since 2.0 040 */ 041public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement { 042 043 /** 044 * The {@link KeyedObjectPool} from which I was obtained. 045 */ 046 private final KeyedObjectPool<K, PoolablePreparedStatement<K>> pool; 047 048 /** 049 * My "key" as used by {@link KeyedObjectPool}. 050 */ 051 private final K key; 052 053 private volatile boolean batchAdded; 054 055 /** 056 * Constructor. 057 * 058 * @param stmt 059 * my underlying {@link PreparedStatement} 060 * @param key 061 * my key" as used by {@link KeyedObjectPool} 062 * @param pool 063 * the {@link KeyedObjectPool} from which I was obtained. 064 * @param conn 065 * the {@link java.sql.Connection Connection} from which I was created 066 */ 067 public PoolablePreparedStatement(final PreparedStatement stmt, final K key, 068 final KeyedObjectPool<K, PoolablePreparedStatement<K>> pool, final DelegatingConnection<?> conn) { 069 super(conn, stmt); 070 this.pool = pool; 071 this.key = key; 072 073 // Remove from trace now because this statement will be 074 // added by the activate method. 075 removeThisTrace(getConnectionInternal()); 076 } 077 078 @Override 079 public void activate() throws SQLException { 080 setClosedInternal(false); 081 if (getConnectionInternal() != null) { 082 getConnectionInternal().addTrace(this); 083 } 084 super.activate(); 085 } 086 087 /** 088 * Add batch. 089 */ 090 @Override 091 public void addBatch() throws SQLException { 092 super.addBatch(); 093 batchAdded = true; 094 } 095 096 /** 097 * Clear Batch. 098 */ 099 @Override 100 public void clearBatch() throws SQLException { 101 batchAdded = false; 102 super.clearBatch(); 103 } 104 105 /** 106 * Return me to my pool. 107 */ 108 @Override 109 public void close() throws SQLException { 110 // calling close twice should have no effect 111 if (!isClosed()) { 112 try { 113 pool.returnObject(key, this); 114 } catch (final SQLException | RuntimeException e) { 115 throw e; 116 } catch (final Exception e) { 117 throw new SQLException("Cannot close preparedstatement (return to pool failed)", e); 118 } 119 } 120 } 121 122 @Override 123 public void passivate() throws SQLException { 124 // DBCP-372. clearBatch with throw an exception if called when the 125 // connection is marked as closed. 126 if (batchAdded) { 127 clearBatch(); 128 } 129 setClosedInternal(true); 130 removeThisTrace(getConnectionInternal()); 131 132 // The JDBC spec requires that a statement closes any open 133 // ResultSet's when it is closed. 134 // FIXME The PreparedStatement we're wrapping should handle this for us. 135 // See bug 17301 for what could happen when ResultSets are closed twice. 136 final List<AbandonedTrace> resultSetList = getTrace(); 137 if (resultSetList != null) { 138 final List<Exception> thrownList = new ArrayList<>(); 139 final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY); 140 for (final ResultSet resultSet : resultSets) { 141 if (resultSet != null) { 142 try { 143 resultSet.close(); 144 } catch (final Exception e) { 145 thrownList.add(e); 146 } 147 } 148 } 149 clearTrace(); 150 if (!thrownList.isEmpty()) { 151 throw new SQLExceptionList(thrownList); 152 } 153 } 154 155 super.passivate(); 156 } 157}