Skip to content

Commit 3e61ca8

Browse files
committed
Initial commit for stored procedure support
1 parent d531de3 commit 3e61ca8

File tree

2 files changed

+491
-12
lines changed

2 files changed

+491
-12
lines changed

src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java

Lines changed: 192 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.nordstrom.common.jdbc;
22

3+
import java.sql.CallableStatement;
34
import java.sql.Connection;
45
import java.sql.Driver;
56
import java.sql.DriverManager;
@@ -8,8 +9,11 @@
89
import java.util.Arrays;
910
import java.util.Iterator;
1011
import java.util.ServiceLoader;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
1114

1215
import com.nordstrom.common.base.UncheckedThrow;
16+
import com.nordstrom.common.jdbc.Param.Mode;
1317

1418
import java.sql.PreparedStatement;
1519

@@ -171,6 +175,9 @@
171175
*/
172176
public class DatabaseUtils {
173177

178+
private static Pattern SPROC_PATTERN =
179+
Pattern.compile("([\\p{Alpha}_][\\p{Alpha}\\p{Digit}@$#_]*)(?:\\(([<>=](?:,\\s*[<>=])*)?\\))?");
180+
174181
private DatabaseUtils() {
175182
throw new AssertionError("DatabaseUtils is a static utility class that cannot be instantiated");
176183
}
@@ -278,27 +285,144 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
278285
* @param resultType desired result type (see TYPES above)
279286
* @param connectionStr database connection string
280287
* @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
282289
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
283290
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
284291
* when you're done with it to free up database and JDBC resources that were allocated for it.
285292
*/
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;
289318

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(")}");
293397

294398
try {
295-
connection = getConnection(connectionStr);
296-
statement = connection.prepareStatement(queryStr); //NOSONAR
399+
Connection connection = getConnection(connectionStr);
400+
CallableStatement statement = connection.prepareCall(sprocStr.toString());
297401

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);
300404
}
301405

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 {
302426
if (resultType == null) {
303427
result = Integer.valueOf(statement.executeUpdate());
304428
} else {
@@ -375,7 +499,7 @@ public interface QueryAPI {
375499
String getQueryStr();
376500

377501
/**
378-
* Get the argument name for this query object
502+
* Get the argument names for this query object
379503
*
380504
* @return query object argument names
381505
*/
@@ -403,6 +527,62 @@ public interface QueryAPI {
403527
Enum<? extends QueryAPI> getEnum(); //NOSONAR
404528
}
405529

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>'&gt;' : This argument is an IN parameter</li>
543+
* <li>'&lt;' : 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+
406586
/**
407587
* This class defines a package of database objects associated with a query. These include:<ul>
408588
* <li>{@link Connection} object</li>

0 commit comments

Comments
 (0)