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