001package org.javasimon.jdbc4; 002 003import java.sql.Connection; 004import java.sql.ResultSet; 005import java.sql.SQLException; 006import java.sql.SQLWarning; 007import java.sql.Statement; 008import java.util.LinkedList; 009import java.util.List; 010 011import org.javasimon.Manager; 012import org.javasimon.SimonManager; 013import org.javasimon.Split; 014import org.javasimon.Stopwatch; 015 016/** 017 * Simon JDBC proxy statement implementation class. 018 * 019 * @author Radovan Sninsky 020 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 021 * @see java.sql.Statement 022 * @since 2.4 023 */ 024public class SimonStatement implements Statement { 025 /** 026 * List of batched SQL statements. 027 */ 028 protected final List<String> batchSql = new LinkedList<>(); 029 030 /** 031 * SQL connection. 032 */ 033 protected Connection conn; 034 035 /** 036 * Hierarchy prefix for JDBC Simons. 037 */ 038 protected String prefix; 039 040 /** 041 * SQL statement label containing part up to the SQL command type. 042 */ 043 protected String sqlCmdLabel; 044 045 /** 046 * SQL normalizer helper object. 047 */ 048 protected SqlNormalizer sqlNormalizer; 049 050 /** 051 * Stopwatch split measuring the lifespan of the statement until it is closed across all executes. 052 */ 053 protected Split split; 054 055 private final Statement stmt; 056 057 private final WrapperSupport<Statement> wrapperSupport; 058 059 /** 060 * Class constructor, initializes Simons (lifespan, active) related to statement. 061 * 062 * @param conn database connection (simon impl.) 063 * @param stmt real statement 064 * @param prefix hierarchy preffix for JDBC Simons 065 */ 066 SimonStatement(Connection conn, Statement stmt, String prefix) { 067 this.conn = conn; 068 this.stmt = stmt; 069 this.prefix = prefix; 070 this.wrapperSupport = new WrapperSupport<>(stmt, Statement.class); 071 split = SimonManager.getStopwatch(prefix + ".stmt").start(); 072 } 073 074 /** 075 * Closes real statement, stops lifespan Simon and decrease active Simon. 076 * 077 * @throws java.sql.SQLException if real operation fails 078 */ 079 @Override 080 public final void close() throws SQLException { 081 stmt.close(); 082 083 split.stop(); 084 } 085 086 /** 087 * Returns a connection object (simon impl.). 088 * 089 * @return connection object 090 */ 091 @Override 092 public final Connection getConnection() { 093 return conn; 094 } 095 096 /** 097 * Called before each SQL command execution. Prepares (obtains and starts) {@link org.javasimon.Stopwatch Stopwatch Simon} 098 * for measure SQL operation. 099 * 100 * @param sql sql command for execution 101 * @return Simon stopwatch object or null if sql is null or empty 102 */ 103 protected final Split prepare(String sql) { 104 if (sql != null && !sql.equals("")) { 105 sqlNormalizer = new SqlNormalizer(sql); 106 sqlCmdLabel = prefix + ".sql." + sqlNormalizer.getType(); 107 return startSplit(); 108 } else { 109 return null; 110 } 111 } 112 113 /** 114 * Called before each SQL command execution. Prepares (obtains and starts) {@link org.javasimon.Stopwatch Stopwatch Simon} 115 * for measure bach SQL operations. 116 * 117 * @param sqls list of sql commands 118 * @return Simon stopwatch object or null if sql is null or empty 119 */ 120 protected final Split prepare(List<String> sqls) { 121 if (!sqls.isEmpty()) { 122 sqlNormalizer = sqls.size() == 1 ? new SqlNormalizer(sqls.get(0)) : new SqlNormalizer(sqls); 123 sqlCmdLabel = prefix + ".sql." + sqlNormalizer.getType(); 124 return startSplit(); 125 } else { 126 return null; 127 } 128 } 129 130 /** 131 * Starts the split for the SQL specific stopwatch, sets the note and returns the split. 132 * Used in the statment and prepared statement classes to measure runs of "execute" methods. 133 * 134 * @return split for the execution of the specific SQL command 135 */ 136 protected Split startSplit() { 137 Stopwatch stopwatch = SimonManager.getStopwatch(sqlCmdLabel + Manager.HIERARCHY_DELIMITER + sqlNormalizer.getNormalizedSql().hashCode()); 138 if (stopwatch.getNote() == null) { 139 stopwatch.setNote(sqlNormalizer.getNormalizedSql()); 140 } 141 return stopwatch.start(); 142 } 143 144 /** 145 * Called after each SQL command execution. Stops concrete SQL stopwatch (started in {@link #prepare(String)}), 146 * also adds time to SQL command type Simon and sets human readable SQL cmd as note. 147 * 148 * @param split started Stopwatch split 149 */ 150 protected final void finish(Split split) { 151 if (split != null) { 152 SimonManager.getStopwatch(sqlCmdLabel).addSplit(split.stop()); 153 } 154 } 155 156 /** 157 * Measure and execute SQL operation. 158 * 159 * @param sql sql command 160 * @return database rows and columns 161 * @throws java.sql.SQLException if real calls fails 162 * @see SimonResultSet 163 */ 164 @Override 165 public final ResultSet executeQuery(String sql) throws SQLException { 166 Split s = prepare(sql); 167 try { 168 return new SimonResultSet(stmt.executeQuery(sql), this, prefix, s.getStopwatch().getName()); 169 } finally { 170 finish(s); 171 } 172 } 173 174 /** 175 * Measure and execute SQL operation. 176 * 177 * @param sql sql command 178 * @return count of updated rows 179 * @throws java.sql.SQLException if real calls fails 180 */ 181 @Override 182 public final int executeUpdate(String sql) throws SQLException { 183 Split s = prepare(sql); 184 try { 185 return stmt.executeUpdate(sql); 186 } finally { 187 finish(s); 188 } 189 } 190 191 /** 192 * Measure and execute SQL operation. 193 * 194 * @param sql sql command 195 * @param autoGeneratedKeys autoGeneratedKeys flag 196 * @return count of updated rows 197 * @throws java.sql.SQLException if real calls fails 198 */ 199 @Override 200 public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { 201 Split s = prepare(sql); 202 try { 203 return stmt.executeUpdate(sql, autoGeneratedKeys); 204 } finally { 205 finish(s); 206 } 207 } 208 209 /** 210 * Measure and execute SQL operation. 211 * 212 * @param sql sql command 213 * @param columnIndexes an array of column indexes indicating the columns that should be 214 * returned from the inserted row 215 * @return count of updated rows 216 * @throws java.sql.SQLException if real calls fails 217 */ 218 @Override 219 public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException { 220 Split s = prepare(sql); 221 try { 222 return stmt.executeUpdate(sql, columnIndexes); 223 } finally { 224 finish(s); 225 } 226 } 227 228 /** 229 * Measure and execute SQL operation. 230 * 231 * @param sql sql command 232 * @param columnNames an array of column indexes indicating the columns that should be 233 * returned from the inserted row 234 * @return count of updated rows 235 * @throws java.sql.SQLException if real calls fails 236 */ 237 @Override 238 public final int executeUpdate(String sql, String[] columnNames) throws SQLException { 239 Split s = prepare(sql); 240 try { 241 return stmt.executeUpdate(sql, columnNames); 242 } finally { 243 finish(s); 244 } 245 } 246 247 /** 248 * Measure and execute SQL operation. 249 * 250 * @param sql sql command 251 * @return <code>true</code> if the first result is a <code>ResultSet</code> object; 252 * <code>false</code> if it is an update count or there are no results 253 * @throws java.sql.SQLException if real calls fails 254 */ 255 @Override 256 public final boolean execute(String sql) throws SQLException { 257 Split s = prepare(sql); 258 try { 259 return stmt.execute(sql); 260 } finally { 261 finish(s); 262 } 263 } 264 265 /** 266 * Measure and execute SQL operation. 267 * 268 * @param sql sql command 269 * @param autoGeneratedKeys autoGeneratedKeys flag 270 * @return <code>true</code> if the first result is a <code>ResultSet</code> object; 271 * <code>false</code> if it is an update count or there are no results 272 * @throws java.sql.SQLException if real calls fails 273 */ 274 @Override 275 public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException { 276 Split s = prepare(sql); 277 try { 278 return stmt.execute(sql, autoGeneratedKeys); 279 } finally { 280 finish(s); 281 } 282 } 283 284 /** 285 * Measure and execute SQL operation. 286 * 287 * @param sql sql command 288 * @param columnIndexes an array of column indexes indicating the columns that should be 289 * returned from the inserted row 290 * @return <code>true</code> if the first result is a <code>ResultSet</code> object; 291 * <code>false</code> if it is an update count or there are no results 292 * @throws java.sql.SQLException if real calls fails 293 */ 294 @Override 295 public final boolean execute(String sql, int[] columnIndexes) throws SQLException { 296 Split s = prepare(sql); 297 try { 298 return stmt.execute(sql, columnIndexes); 299 } finally { 300 finish(s); 301 } 302 } 303 304 /** 305 * Measure and execute SQL operation. 306 * 307 * @param sql sql command 308 * @param columnNames an array of column indexes indicating the columns that should be 309 * returned from the inserted row 310 * @return <code>true</code> if the first result is a <code>ResultSet</code> object; 311 * <code>false</code> if it is an update count or there are no results 312 * @throws java.sql.SQLException if real calls fails 313 */ 314 @Override 315 public final boolean execute(String sql, String[] columnNames) throws SQLException { 316 Split s = prepare(sql); 317 try { 318 return stmt.execute(sql, columnNames); 319 } finally { 320 finish(s); 321 } 322 } 323 324 /** 325 * Adds given SQL command into batch list of sql and also into real batch. 326 * 327 * @param s sql command 328 * @throws java.sql.SQLException if real calls fails 329 */ 330 @Override 331 public final void addBatch(String s) throws SQLException { 332 batchSql.add(s); 333 334 stmt.addBatch(s); 335 } 336 337 /** 338 * Measure and execute SQL operation. 339 * 340 * @return an array of update counts containing one element for each 341 * command in the batch. 342 * @throws java.sql.SQLException if real calls fails 343 */ 344 @Override 345 public int[] executeBatch() throws SQLException { 346 Split s = prepare(batchSql); 347 try { 348 return stmt.executeBatch(); 349 } finally { 350 finish(s); 351 } 352 } 353 354 /** 355 * Clears batch sql list and real batch too. 356 * 357 * @throws java.sql.SQLException if real calls fails 358 */ 359 @Override 360 public void clearBatch() throws SQLException { 361 batchSql.clear(); 362 363 stmt.clearBatch(); 364 } 365 366 //// NOT MONITORED 367 368 @Override 369 public final int getMaxFieldSize() throws SQLException { 370 return stmt.getMaxFieldSize(); 371 } 372 373 @Override 374 public final void setMaxFieldSize(int i) throws SQLException { 375 stmt.setMaxFieldSize(i); 376 } 377 378 @Override 379 public final int getMaxRows() throws SQLException { 380 return stmt.getMaxRows(); 381 } 382 383 @Override 384 public final void setMaxRows(int i) throws SQLException { 385 stmt.setMaxRows(i); 386 } 387 388 @Override 389 public final void setEscapeProcessing(boolean b) throws SQLException { 390 stmt.setEscapeProcessing(b); 391 } 392 393 @Override 394 public final int getQueryTimeout() throws SQLException { 395 return stmt.getQueryTimeout(); 396 } 397 398 @Override 399 public final void setQueryTimeout(int i) throws SQLException { 400 stmt.setQueryTimeout(i); 401 } 402 403 @Override 404 public final void cancel() throws SQLException { 405 stmt.cancel(); 406 } 407 408 @Override 409 public final SQLWarning getWarnings() throws SQLException { 410 return stmt.getWarnings(); 411 } 412 413 @Override 414 public final void clearWarnings() throws SQLException { 415 stmt.clearWarnings(); 416 } 417 418 @Override 419 public final void setCursorName(String s) throws SQLException { 420 stmt.setCursorName(s); 421 } 422 423 @Override 424 public final ResultSet getResultSet() throws SQLException { 425 return stmt.getResultSet(); 426 } 427 428 @Override 429 public final int getUpdateCount() throws SQLException { 430 return stmt.getUpdateCount(); 431 } 432 433 @Override 434 public final boolean getMoreResults() throws SQLException { 435 return stmt.getMoreResults(); 436 } 437 438 @Override 439 public final void setFetchDirection(int i) throws SQLException { 440 stmt.setFetchDirection(i); 441 } 442 443 @Override 444 public final int getFetchDirection() throws SQLException { 445 return stmt.getFetchDirection(); 446 } 447 448 @Override 449 public final void setFetchSize(int i) throws SQLException { 450 stmt.setFetchSize(i); 451 } 452 453 @Override 454 public final int getFetchSize() throws SQLException { 455 return stmt.getFetchSize(); 456 } 457 458 @Override 459 public final int getResultSetConcurrency() throws SQLException { 460 return stmt.getResultSetConcurrency(); 461 } 462 463 @Override 464 public final int getResultSetType() throws SQLException { 465 return stmt.getResultSetType(); 466 } 467 468 @Override 469 public final boolean getMoreResults(int i) throws SQLException { 470 return stmt.getMoreResults(i); 471 } 472 473 @Override 474 public final ResultSet getGeneratedKeys() throws SQLException { 475 return stmt.getGeneratedKeys(); 476 } 477 478 @Override 479 public final int getResultSetHoldability() throws SQLException { 480 return stmt.getResultSetHoldability(); 481 } 482 483 @Override 484 public final boolean isClosed() throws SQLException { 485 return stmt.isClosed(); 486 } 487 488 @Override 489 public final void setPoolable(boolean b) throws SQLException { 490 stmt.setPoolable(b); 491 } 492 493 @Override 494 public final boolean isPoolable() throws SQLException { 495 return stmt.isPoolable(); 496 } 497 498 @Override 499 public void closeOnCompletion() throws SQLException { 500 stmt.closeOnCompletion(); 501 } 502 503 @Override 504 public boolean isCloseOnCompletion() throws SQLException { 505 return stmt.isCloseOnCompletion(); 506 } 507 508 @Override 509 public final <T> T unwrap(Class<T> tClass) throws SQLException { 510 return wrapperSupport.unwrap(tClass); 511 } 512 513 @Override 514 public final boolean isWrapperFor(Class<?> aClass) throws SQLException { 515 return wrapperSupport.isWrapperFor(aClass); 516 } 517}