|
1 | 1 | package com.nordstrom.common.jdbc; |
2 | 2 |
|
| 3 | +import java.sql.CallableStatement; |
3 | 4 | import java.sql.Connection; |
4 | 5 | import java.sql.Driver; |
5 | 6 | import java.sql.DriverManager; |
|
8 | 9 | import java.util.Arrays; |
9 | 10 | import java.util.Iterator; |
10 | 11 | import java.util.ServiceLoader; |
| 12 | +import java.util.regex.Matcher; |
| 13 | +import java.util.regex.Pattern; |
11 | 14 |
|
12 | 15 | import com.nordstrom.common.base.UncheckedThrow; |
| 16 | +import com.nordstrom.common.jdbc.Param.Mode; |
13 | 17 |
|
14 | 18 | import java.sql.PreparedStatement; |
15 | 19 |
|
|
171 | 175 | */ |
172 | 176 | public class DatabaseUtils { |
173 | 177 |
|
| 178 | + private static Pattern SPROC_PATTERN = |
| 179 | + Pattern.compile("([\\p{Alpha}_][\\p{Alpha}\\p{Digit}@$#_]*)(?:\\(([<>=](?:,\\s*[<>=])*)?\\))?"); |
| 180 | + |
174 | 181 | private DatabaseUtils() { |
175 | 182 | throw new AssertionError("DatabaseUtils is a static utility class that cannot be instantiated"); |
176 | 183 | } |
@@ -278,27 +285,144 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object.. |
278 | 285 | * @param resultType desired result type (see TYPES above) |
279 | 286 | * @param connectionStr database connection string |
280 | 287 | * @param queryStr a SQL statement that may contain one or more '?' IN parameter placeholders |
281 | | - * @param param an array of objects containing the input parameter values |
| 288 | + * @param params an array of objects containing the input parameter values |
282 | 289 | * @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br> |
283 | 290 | * <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object |
284 | 291 | * when you're done with it to free up database and JDBC resources that were allocated for it. |
285 | 292 | */ |
286 | | - public static Object executeQuery(Class<?> resultType, String connectionStr, String queryStr, Object... param) { |
287 | | - Object result = null; |
288 | | - boolean failed = false; |
| 293 | + public static Object executeQuery(Class<?> resultType, String connectionStr, String queryStr, Object... params) { |
| 294 | + try { |
| 295 | + Connection connection = getConnection(connectionStr); |
| 296 | + PreparedStatement statement = connection.prepareStatement(queryStr); |
| 297 | + |
| 298 | + for (int i = 0; i < params.length; i++) { |
| 299 | + statement.setObject(i + 1, params[i]); |
| 300 | + } |
| 301 | + |
| 302 | + return executeStatement(resultType, connection, statement); |
| 303 | + } catch (SQLException e) { |
| 304 | + throw UncheckedThrow.throwUnchecked(e); |
| 305 | + } |
| 306 | + } |
| 307 | + |
| 308 | + /** |
| 309 | + * |
| 310 | + * @param resultType |
| 311 | + * @param sproc |
| 312 | + * @param parms |
| 313 | + * @return |
| 314 | + */ |
| 315 | + public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc, Object... parms) { |
| 316 | + int typesCount = sproc.getArgCount(); |
| 317 | + int parmsCount = parms.length; |
289 | 318 |
|
290 | | - Connection connection = null; |
291 | | - PreparedStatement statement = null; |
292 | | - ResultSet resultSet = null; |
| 319 | + if (parmsCount != typesCount) { |
| 320 | + String message; |
| 321 | + |
| 322 | + if (typesCount == 0) { |
| 323 | + message = "No arguments expected for " + sproc.getEnum().name(); |
| 324 | + } else { |
| 325 | + message = String.format("Incorrect argument count for %s%s: expect: %d; actual: %d", |
| 326 | + sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()), typesCount, parmsCount); |
| 327 | + } |
| 328 | + |
| 329 | + throw new IllegalArgumentException(message); |
| 330 | + } |
| 331 | + |
| 332 | + String sprocName; |
| 333 | + String[] args = {}; |
| 334 | + String signature = sproc.getSignature(); |
| 335 | + Matcher matcher = SPROC_PATTERN.matcher(signature); |
| 336 | + |
| 337 | + if (matcher.matches()) { |
| 338 | + sprocName = matcher.group(1); |
| 339 | + if (matcher.group(2) != null) { |
| 340 | + args = matcher.group(2).split(",\\s"); |
| 341 | + } |
| 342 | + } else { |
| 343 | + String message = String.format("Unsupported stored procedure signature for %s: %s", |
| 344 | + sproc.getEnum().name(), signature); |
| 345 | + throw new IllegalArgumentException(message); |
| 346 | + } |
| 347 | + |
| 348 | + int argsCount = args.length; |
| 349 | + if (argsCount != typesCount) { |
| 350 | + String message = String.format("Signature argument count differs from declared type count for %s%s: " |
| 351 | + + "signature: %d; declared: %d", |
| 352 | + sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()), argsCount, typesCount); |
| 353 | + throw new IllegalArgumentException(message); |
| 354 | + } |
| 355 | + |
| 356 | + int[] argTypes = sproc.getArgTypes(); |
| 357 | + Param[] parmArray = Param.array(typesCount); |
| 358 | + for (int i = 0; i < typesCount; i++) { |
| 359 | + int type = argTypes[i]; |
| 360 | + Mode mode = Mode.fromChar(args[i].charAt(0)); |
| 361 | + switch (mode) { |
| 362 | + case IN: |
| 363 | + parmArray[i] = Param.in(parms[i], type); |
| 364 | + break; |
| 365 | + |
| 366 | + case OUT: |
| 367 | + parmArray[i] = Param.out(type); |
| 368 | + break; |
| 369 | + |
| 370 | + case INOUT: |
| 371 | + parmArray[i] = Param.inOut(parms[i], type); |
| 372 | + break; |
| 373 | + } |
| 374 | + } |
| 375 | + |
| 376 | + return executeStoredProcedure(resultType, sproc.getConnection(), sprocName, parmArray); |
| 377 | + } |
| 378 | + |
| 379 | + /** |
| 380 | + * |
| 381 | + * @param resultType |
| 382 | + * @param connectionStr |
| 383 | + * @param sprocName |
| 384 | + * @param params |
| 385 | + * @return |
| 386 | + */ |
| 387 | + public static Object executeStoredProcedure(Class<?> resultType, String connectionStr, String sprocName, Param... params) { |
| 388 | + StringBuilder sprocStr = new StringBuilder("{call ").append(sprocName).append("("); |
| 389 | + |
| 390 | + String placeholder = "?"; |
| 391 | + for (int i = 0; i < params.length; i++) { |
| 392 | + sprocStr.append(placeholder); |
| 393 | + placeholder = ",?"; |
| 394 | + } |
| 395 | + |
| 396 | + sprocStr.append(")}"); |
293 | 397 |
|
294 | 398 | try { |
295 | | - connection = getConnection(connectionStr); |
296 | | - statement = connection.prepareStatement(queryStr); //NOSONAR |
| 399 | + Connection connection = getConnection(connectionStr); |
| 400 | + CallableStatement statement = connection.prepareCall(sprocStr.toString()); |
297 | 401 |
|
298 | | - for (int i = 0; i < param.length; i++) { |
299 | | - statement.setObject(i + 1, param[i]); |
| 402 | + for (int i = 0; i < params.length; i++) { |
| 403 | + params[i].set(statement, i); |
300 | 404 | } |
301 | 405 |
|
| 406 | + return executeStatement(resultType, connection, statement); |
| 407 | + } catch (SQLException e) { |
| 408 | + throw UncheckedThrow.throwUnchecked(e); |
| 409 | + } |
| 410 | + } |
| 411 | + |
| 412 | + /** |
| 413 | + * |
| 414 | + * @param resultType |
| 415 | + * @param connection |
| 416 | + * @param statement |
| 417 | + * @return |
| 418 | + */ |
| 419 | + private static Object executeStatement(Class<?> resultType, Connection connection, PreparedStatement statement) { |
| 420 | + Object result = null; |
| 421 | + boolean failed = false; |
| 422 | + |
| 423 | + ResultSet resultSet = null; |
| 424 | + |
| 425 | + try { |
302 | 426 | if (resultType == null) { |
303 | 427 | result = Integer.valueOf(statement.executeUpdate()); |
304 | 428 | } else { |
@@ -375,7 +499,7 @@ public interface QueryAPI { |
375 | 499 | String getQueryStr(); |
376 | 500 |
|
377 | 501 | /** |
378 | | - * Get the argument name for this query object |
| 502 | + * Get the argument names for this query object |
379 | 503 | * |
380 | 504 | * @return query object argument names |
381 | 505 | */ |
@@ -403,6 +527,62 @@ public interface QueryAPI { |
403 | 527 | Enum<? extends QueryAPI> getEnum(); //NOSONAR |
404 | 528 | } |
405 | 529 |
|
| 530 | + /** |
| 531 | + * This interface defines the API supported by database stored procedure collections |
| 532 | + */ |
| 533 | + public interface SProcAPI { |
| 534 | + |
| 535 | + /** |
| 536 | + * Get the signature for this stored procedure object. |
| 537 | + * <p> |
| 538 | + * Each argument place holder in the stored procedure signature indicates the mode of the corresponding |
| 539 | + * parameter: |
| 540 | + * |
| 541 | + * <ul> |
| 542 | + * <li>'>' : This argument is an IN parameter</li> |
| 543 | + * <li>'<' : This argument is an OUT parameter</li> |
| 544 | + * <li>'=' : This argument is an INOUT parameter</li> |
| 545 | + * </ul> |
| 546 | + * |
| 547 | + * For example: |
| 548 | + * |
| 549 | + * <blockquote>RAISE_PRICE(>, >, =)</blockquote> |
| 550 | + * |
| 551 | + * The first and second arguments are IN parameters, and the third argument is an INOUT parameter. |
| 552 | + * |
| 553 | + * @return stored procedure signature |
| 554 | + */ |
| 555 | + String getSignature(); |
| 556 | + |
| 557 | + /** |
| 558 | + * Get the argument types for this stored procedure object |
| 559 | + * |
| 560 | + * @return stored procedure argument types |
| 561 | + */ |
| 562 | + int[] getArgTypes(); |
| 563 | + |
| 564 | + /** |
| 565 | + * Get the count of arguments for this stored procedure object. |
| 566 | + * |
| 567 | + * @return stored procedure argument count |
| 568 | + */ |
| 569 | + int getArgCount(); |
| 570 | + |
| 571 | + /** |
| 572 | + * Get the database connection string for this stored procedure object. |
| 573 | + * |
| 574 | + * @return stored procedure connection string |
| 575 | + */ |
| 576 | + String getConnection(); |
| 577 | + |
| 578 | + /** |
| 579 | + * Get the implementing enumerated constant for this stored procedure object. |
| 580 | + * |
| 581 | + * @return stored procedure enumerated constant |
| 582 | + */ |
| 583 | + Enum<? extends SProcAPI> getEnum(); //NOSONAR |
| 584 | + } |
| 585 | + |
406 | 586 | /** |
407 | 587 | * This class defines a package of database objects associated with a query. These include:<ul> |
408 | 588 | * <li>{@link Connection} object</li> |
|
0 commit comments