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 = false; 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 /** 079 * Add batch. 080 */ 081 @Override 082 public void addBatch() throws SQLException { 083 super.addBatch(); 084 batchAdded = true; 085 } 086 087 /** 088 * Clear Batch. 089 */ 090 @Override 091 public void clearBatch() throws SQLException { 092 batchAdded = false; 093 super.clearBatch(); 094 } 095 096 /** 097 * Return me to my pool. 098 */ 099 @Override 100 public void close() throws SQLException { 101 // calling close twice should have no effect 102 if (!isClosed()) { 103 try { 104 pool.returnObject(key, this); 105 } catch (final SQLException e) { 106 throw e; 107 } catch (final RuntimeException e) { 108 throw e; 109 } catch (final Exception e) { 110 throw new SQLException("Cannot close preparedstatement (return to pool failed)", e); 111 } 112 } 113 } 114 115 @Override 116 public void activate() throws SQLException { 117 setClosedInternal(false); 118 if (getConnectionInternal() != null) { 119 getConnectionInternal().addTrace(this); 120 } 121 super.activate(); 122 } 123 124 @Override 125 public void passivate() throws SQLException { 126 // DBCP-372. clearBatch with throw an exception if called when the 127 // connection is marked as closed. 128 if (batchAdded) { 129 clearBatch(); 130 } 131 setClosedInternal(true); 132 removeThisTrace(getConnectionInternal()); 133 134 // The JDBC spec requires that a statement closes any open 135 // ResultSet's when it is closed. 136 // FIXME The PreparedStatement we're wrapping should handle this for us. 137 // See bug 17301 for what could happen when ResultSets are closed twice. 138 final List<AbandonedTrace> resultSetList = getTrace(); 139 if (resultSetList != null) { 140 final List<Exception> thrown = new ArrayList<>(); 141 final ResultSet[] resultSets = resultSetList.toArray(new ResultSet[resultSetList.size()]); 142 for (final ResultSet resultSet : resultSets) { 143 if (resultSet != null) { 144 try { 145 resultSet.close(); 146 } catch (Exception e) { 147 thrown.add(e); 148 } 149 } 150 } 151 clearTrace(); 152 if (!thrown.isEmpty()) { 153 throw new SQLExceptionList(thrown); 154 } 155 } 156 157 super.passivate(); 158 } 159}