Lines 17-28
Link Here
|
17 |
|
17 |
|
18 |
package org.apache.jmeter.protocol.jdbc.sampler; |
18 |
package org.apache.jmeter.protocol.jdbc.sampler; |
19 |
|
19 |
|
|
|
20 |
import java.lang.reflect.Field; |
20 |
import java.sql.CallableStatement; |
21 |
import java.sql.CallableStatement; |
21 |
import java.sql.Connection; |
22 |
import java.sql.Connection; |
|
|
23 |
import java.sql.PreparedStatement; |
22 |
import java.sql.ResultSet; |
24 |
import java.sql.ResultSet; |
23 |
import java.sql.ResultSetMetaData; |
25 |
import java.sql.ResultSetMetaData; |
24 |
import java.sql.SQLException; |
26 |
import java.sql.SQLException; |
25 |
import java.sql.Statement; |
27 |
import java.sql.Statement; |
|
|
28 |
import java.util.Collection; |
29 |
import java.util.HashMap; |
30 |
import java.util.Iterator; |
31 |
import java.util.LinkedHashMap; |
32 |
import java.util.Map; |
26 |
|
33 |
|
27 |
import org.apache.avalon.excalibur.datasource.DataSourceComponent; |
34 |
import org.apache.avalon.excalibur.datasource.DataSourceComponent; |
28 |
import org.apache.jmeter.samplers.AbstractSampler; |
35 |
import org.apache.jmeter.samplers.AbstractSampler; |
Lines 40-57
Link Here
|
40 |
* @author <a href="mailto:jeremy_a@bigfoot.com">Jeremy Arnold</a> |
47 |
* @author <a href="mailto:jeremy_a@bigfoot.com">Jeremy Arnold</a> |
41 |
*/ |
48 |
*/ |
42 |
public class JDBCSampler extends AbstractSampler implements TestBean { |
49 |
public class JDBCSampler extends AbstractSampler implements TestBean { |
|
|
50 |
private static final int MAX_ENTRIES = 200; |
51 |
|
43 |
private static final Logger log = LoggingManager.getLoggerForClass(); |
52 |
private static final Logger log = LoggingManager.getLoggerForClass(); |
|
|
53 |
static Map mapJdbcNameToInt; |
44 |
|
54 |
|
45 |
// Query types (used to communicate with GUI) |
55 |
// Query types (used to communicate with GUI) |
46 |
static final String SELECT = "Select Statement"; |
56 |
static final String SELECT = "Select Statement"; |
47 |
static final String UPDATE = "Update Statement"; |
57 |
static final String UPDATE = "Update Statement"; |
48 |
static final String CALLABLE = "Callable Statement"; |
58 |
static final String CALLABLE = "Callable Statement"; |
|
|
59 |
static final String PREPARED_SELECT = "Prepared Select Statement"; |
60 |
static final String PREPARED_UPDATE = "Prepared Update Statement"; |
49 |
|
61 |
|
50 |
private String query = ""; |
62 |
private String query = ""; |
51 |
|
63 |
|
52 |
private String dataSource = ""; |
64 |
private String dataSource = ""; |
53 |
|
65 |
|
54 |
private String queryType = SELECT; |
66 |
private String queryType = SELECT; |
|
|
67 |
private String queryArguments = ""; |
68 |
private String queryArgumentsTypes = ""; |
69 |
|
70 |
/** |
71 |
* Cache of PreparedStatements stored in a per-connection basis. Each entry of this |
72 |
* cache is another Map mapping the statemente string to the actual PreparedStatement. |
73 |
* The cache has a fixed size of MAX_ENTRIES and it will throw aways all PreparedStatements |
74 |
* from the least recently used connections. |
75 |
*/ |
76 |
private static Map perConnCache = new LinkedHashMap(MAX_ENTRIES){ |
77 |
protected boolean removeEldestEntry(java.util.Map.Entry arg0) { |
78 |
if (size() > MAX_ENTRIES) { |
79 |
final Object value = arg0.getValue(); |
80 |
if (value instanceof Map) { |
81 |
closeAllStatements(((Map)value).values()); |
82 |
} |
83 |
return true; |
84 |
} |
85 |
return false; |
86 |
} |
87 |
}; |
55 |
|
88 |
|
56 |
/** |
89 |
/** |
57 |
* Creates a JDBCSampler. |
90 |
* Creates a JDBCSampler. |
Lines 78-84
Link Here
|
78 |
log.debug("DataSourceComponent: " + pool); |
111 |
log.debug("DataSourceComponent: " + pool); |
79 |
Connection conn = null; |
112 |
Connection conn = null; |
80 |
Statement stmt = null; |
113 |
Statement stmt = null; |
81 |
CallableStatement cs = null; |
114 |
|
82 |
|
115 |
|
83 |
try { |
116 |
try { |
84 |
|
117 |
|
Lines 88-98
Link Here
|
88 |
// TODO: Consider creating a sub-result with the time to get the |
121 |
// TODO: Consider creating a sub-result with the time to get the |
89 |
// connection. |
122 |
// connection. |
90 |
conn = pool.getConnection(); |
123 |
conn = pool.getConnection(); |
91 |
stmt = conn.createStatement(); |
|
|
92 |
|
124 |
|
93 |
// Based on query return value, get results |
125 |
// Based on query return value, get results |
94 |
String _queryType = getQueryType(); |
126 |
String _queryType = getQueryType(); |
95 |
if (SELECT.equals(_queryType)) { |
127 |
if (SELECT.equals(_queryType)) { |
|
|
128 |
stmt = conn.createStatement(); |
96 |
ResultSet rs = null; |
129 |
ResultSet rs = null; |
97 |
try { |
130 |
try { |
98 |
rs = stmt.executeQuery(getQuery()); |
131 |
rs = stmt.executeQuery(getQuery()); |
Lines 101-125
Link Here
|
101 |
} finally { |
134 |
} finally { |
102 |
close(rs); |
135 |
close(rs); |
103 |
} |
136 |
} |
104 |
} else if (CALLABLE.equals(_queryType)) { |
137 |
} else if (CALLABLE.equals(_queryType)) { |
105 |
cs = conn.prepareCall(getQuery()); |
138 |
CallableStatement cstmt = getCallableStatement(conn); |
106 |
boolean hasResultSet = cs.execute(); |
139 |
setArguments(cstmt); |
107 |
if (hasResultSet){ |
140 |
// A CallableStatement can return more than 1 ResultSets |
108 |
ResultSet rs=cs.getResultSet(); |
141 |
// plus a number of update counts. |
109 |
Data data = getDataFromResultSet(rs); |
142 |
boolean hasResultSet = cstmt.execute(); |
110 |
res.setResponseData(data.toString().getBytes()); |
143 |
String sb = resultSetsToString(cstmt,hasResultSet); |
111 |
} else { |
144 |
res.setResponseData(sb.toString().getBytes()); |
112 |
int updateCount = cs.getUpdateCount(); |
|
|
113 |
String results = updateCount + " updates"; |
114 |
res.setResponseData(results.getBytes()); |
115 |
} |
116 |
//TODO process additional results (if any) using getMoreResults() |
117 |
} else if (UPDATE.equals(_queryType)) { |
145 |
} else if (UPDATE.equals(_queryType)) { |
118 |
stmt.execute(getQuery()); |
146 |
stmt = conn.createStatement(); |
|
|
147 |
stmt.executeUpdate(getQuery()); |
119 |
int updateCount = stmt.getUpdateCount(); |
148 |
int updateCount = stmt.getUpdateCount(); |
120 |
String results = updateCount + " updates"; |
149 |
String results = updateCount + " updates"; |
121 |
res.setResponseData(results.getBytes()); |
150 |
res.setResponseData(results.getBytes()); |
122 |
// TODO add support for PreparedStatments |
151 |
} else if (PREPARED_SELECT.equals(_queryType)) { |
|
|
152 |
PreparedStatement pstmt = getPreparedStatement(conn); |
153 |
setArguments(pstmt); |
154 |
pstmt.executeQuery(); |
155 |
String sb = resultSetsToString(pstmt,true); |
156 |
res.setResponseData(sb.toString().getBytes()); |
157 |
} else if (PREPARED_UPDATE.equals(_queryType)) { |
158 |
PreparedStatement pstmt = getPreparedStatement(conn); |
159 |
setArguments(pstmt); |
160 |
pstmt.executeUpdate(); |
161 |
String sb = resultSetsToString(pstmt,false); |
162 |
res.setResponseData(sb.toString().getBytes()); |
123 |
} else { // User provided incorrect query type |
163 |
} else { // User provided incorrect query type |
124 |
String results="Unexpected query type: "+_queryType; |
164 |
String results="Unexpected query type: "+_queryType; |
125 |
res.setResponseMessage(results); |
165 |
res.setResponseMessage(results); |
Lines 131-137
Link Here
|
131 |
res.setResponseMessage(ex.toString()); |
171 |
res.setResponseMessage(ex.toString()); |
132 |
res.setSuccessful(false); |
172 |
res.setSuccessful(false); |
133 |
} finally { |
173 |
} finally { |
134 |
close(cs); |
|
|
135 |
close(stmt); |
174 |
close(stmt); |
136 |
close(conn); |
175 |
close(conn); |
137 |
} |
176 |
} |
Lines 140-145
Link Here
|
140 |
return res; |
179 |
return res; |
141 |
} |
180 |
} |
142 |
|
181 |
|
|
|
182 |
private String resultSetsToString(PreparedStatement pstmt, boolean result) throws SQLException { |
183 |
StringBuffer sb = new StringBuffer(); |
184 |
sb.append("\n"); |
185 |
int updateCount = 0; |
186 |
if (!result) { |
187 |
updateCount = pstmt.getUpdateCount(); |
188 |
} |
189 |
do { |
190 |
if (result) { |
191 |
ResultSet rs = null; |
192 |
try { |
193 |
rs = pstmt.getResultSet(); |
194 |
Data data = getDataFromResultSet(rs); |
195 |
sb.append(data.toString()).append("\n"); |
196 |
} finally { |
197 |
close(rs); |
198 |
} |
199 |
} else { |
200 |
sb.append(updateCount).append(" updates.\n"); |
201 |
} |
202 |
result = pstmt.getMoreResults(); |
203 |
if (!result) { |
204 |
updateCount = pstmt.getUpdateCount(); |
205 |
} |
206 |
} while (result || (updateCount != -1)); |
207 |
return sb.toString(); |
208 |
} |
209 |
|
210 |
|
211 |
private void setArguments(PreparedStatement pstmt) throws SQLException { |
212 |
if ("".equals(getQueryArguments().trim())) { |
213 |
return; |
214 |
} |
215 |
String[] arguments = getQueryArguments().split(","); |
216 |
String[] argumentsTypes = getQueryArgumentsTypes().split(","); |
217 |
if (arguments.length != argumentsTypes.length) { |
218 |
throw new SQLException("number of arguments ("+arguments.length+") and number of types ("+argumentsTypes.length+") are not equal"); |
219 |
} |
220 |
for (int i = 0; i < arguments.length; i++) { |
221 |
String argument = arguments[i]; |
222 |
String argumentType = argumentsTypes[i]; |
223 |
//TODO should be a more elegant way to do it |
224 |
int targetSqlType = getJdbcType(argumentType); |
225 |
pstmt.setObject(i+1, argument, targetSqlType); |
226 |
} |
227 |
} |
228 |
|
229 |
public static int getJdbcType(String jdbcType) { |
230 |
// based on e291. Getting the Name of a JDBC Type from javaalmanac.com |
231 |
// http://javaalmanac.com/egs/java.sql/JdbcInt2Str.html |
232 |
if (mapJdbcNameToInt == null) { |
233 |
mapJdbcNameToInt = new HashMap(); |
234 |
|
235 |
//Get all field in java.sql.Types |
236 |
Field[] fields = java.sql.Types.class.getFields(); |
237 |
for (int i=0; i<fields.length; i++) { |
238 |
try { |
239 |
// Get field name |
240 |
String name = fields[i].getName(); |
241 |
|
242 |
// Get field value |
243 |
Integer value = (Integer)fields[i].get(null); |
244 |
|
245 |
// Add to map |
246 |
mapJdbcNameToInt.put(name.toLowerCase(),value); |
247 |
} catch (IllegalAccessException e) { |
248 |
throw new RuntimeException(e); |
249 |
} |
250 |
} |
251 |
|
252 |
} |
253 |
return ((Integer)mapJdbcNameToInt.get(jdbcType.toLowerCase())).intValue(); |
254 |
} |
255 |
|
256 |
|
257 |
private CallableStatement getCallableStatement(Connection conn) throws SQLException { |
258 |
return (CallableStatement) getPreparedStatement(conn,true); |
259 |
|
260 |
} |
261 |
private PreparedStatement getPreparedStatement(Connection conn) throws SQLException { |
262 |
return getPreparedStatement(conn,false); |
263 |
} |
264 |
|
265 |
private PreparedStatement getPreparedStatement(Connection conn, boolean callable) throws SQLException { |
266 |
Map preparedStatementMap = (Map) perConnCache.get(conn); |
267 |
if (null == preparedStatementMap ) { |
268 |
// MRU PreparedStatements cache. |
269 |
preparedStatementMap = new LinkedHashMap(MAX_ENTRIES) { |
270 |
protected boolean removeEldestEntry(java.util.Map.Entry arg0) { |
271 |
final int theSize = size(); |
272 |
if (theSize > MAX_ENTRIES) { |
273 |
Object value = arg0.getValue(); |
274 |
if (value instanceof PreparedStatement) { |
275 |
PreparedStatement pstmt = (PreparedStatement) value; |
276 |
try { |
277 |
pstmt.close(); |
278 |
} catch (SQLException e) { |
279 |
// ignore this exception |
280 |
e.printStackTrace(); |
281 |
} |
282 |
} |
283 |
return true; |
284 |
} |
285 |
return false; |
286 |
} |
287 |
}; |
288 |
perConnCache.put(conn, preparedStatementMap); |
289 |
} |
290 |
PreparedStatement pstmt = (PreparedStatement) preparedStatementMap.get(getQuery()); |
291 |
if (null == pstmt) { |
292 |
if (callable) { |
293 |
pstmt = conn.prepareCall(getQuery()); |
294 |
} else { |
295 |
pstmt = conn.prepareStatement(getQuery()); |
296 |
} |
297 |
preparedStatementMap.put(getQuery(), pstmt); |
298 |
} |
299 |
pstmt.clearParameters(); |
300 |
return pstmt; |
301 |
} |
302 |
|
303 |
private static void closeAllStatements(Collection collection) { |
304 |
Iterator iterator = collection.iterator(); |
305 |
while (iterator.hasNext()) { |
306 |
PreparedStatement pstmt = (PreparedStatement) iterator.next(); |
307 |
try { |
308 |
pstmt.close(); |
309 |
} catch (SQLException e) { |
310 |
// ignore this exception |
311 |
e.printStackTrace(); |
312 |
} |
313 |
} |
314 |
|
315 |
} |
316 |
|
143 |
/** |
317 |
/** |
144 |
* Gets a Data object from a ResultSet. |
318 |
* Gets a Data object from a ResultSet. |
145 |
* |
319 |
* |
Lines 171-177
Link Here
|
171 |
} |
345 |
} |
172 |
return data; |
346 |
return data; |
173 |
} |
347 |
} |
174 |
|
348 |
|
175 |
public static void close(Connection c) { |
349 |
public static void close(Connection c) { |
176 |
try { |
350 |
try { |
177 |
if (c != null) c.close(); |
351 |
if (c != null) c.close(); |
Lines 184-201
Link Here
|
184 |
try { |
358 |
try { |
185 |
if (s != null) s.close(); |
359 |
if (s != null) s.close(); |
186 |
} catch (SQLException e) { |
360 |
} catch (SQLException e) { |
187 |
log.warn("Error closing Statement", e); |
361 |
log.warn("Error closing Statement " + s.toString(), e); |
188 |
} |
362 |
} |
189 |
} |
363 |
} |
190 |
|
364 |
|
191 |
public static void close(CallableStatement cs) { |
365 |
|
192 |
try { |
|
|
193 |
if (cs != null) cs.close(); |
194 |
} catch (SQLException e) { |
195 |
log.warn("Error closing CallableStatement", e); |
196 |
} |
197 |
} |
198 |
|
199 |
public static void close(ResultSet rs) { |
366 |
public static void close(ResultSet rs) { |
200 |
try { |
367 |
try { |
201 |
if (rs != null) rs.close(); |
368 |
if (rs != null) rs.close(); |
Lines 253-256
Link Here
|
253 |
public void setQueryType(String queryType) { |
420 |
public void setQueryType(String queryType) { |
254 |
this.queryType = queryType; |
421 |
this.queryType = queryType; |
255 |
} |
422 |
} |
|
|
423 |
|
424 |
public String getQueryArguments() { |
425 |
return queryArguments; |
426 |
} |
427 |
|
428 |
public void setQueryArguments(String queryArguments) { |
429 |
this.queryArguments = queryArguments; |
430 |
} |
431 |
|
432 |
public String getQueryArgumentsTypes() { |
433 |
return queryArgumentsTypes; |
434 |
} |
435 |
|
436 |
public void setQueryArgumentsTypes(String queryArgumentsType) { |
437 |
this.queryArgumentsTypes = queryArgumentsType; |
438 |
} |
256 |
} |
439 |
} |