Skip to content

Commit 4773aa7

Browse files
Babcock, Scott (Contractor)Babcock, Scott (Contractor)
authored andcommitted
Merge pull request #1 in MFATT/common from pr/add-database-utils to master
* commit '97289c61cb6e5e23796ecba976e4f1d6723de57f': Add DatabaseUtils class
2 parents 5928cb2 + 97289c6 commit 4773aa7

File tree

1 file changed

+313
-0
lines changed

1 file changed

+313
-0
lines changed
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
package com.nordstrom.common.jdbc;
2+
3+
import java.sql.Connection;
4+
import java.sql.DriverManager;
5+
import java.sql.ResultSet;
6+
import java.sql.SQLException;
7+
import java.util.Arrays;
8+
9+
import com.nordstrom.common.base.UncheckedThrow;
10+
11+
import java.sql.PreparedStatement;
12+
13+
public class DatabaseUtils {
14+
15+
private DatabaseUtils() {
16+
throw new AssertionError("DatabaseUtils is a static utility class that cannot be instantiated");
17+
}
18+
19+
static {
20+
try {
21+
Class.forName("oracle.jdbc.driver.OracleDriver");
22+
} catch (ClassNotFoundException e) {
23+
throw new RuntimeException("Unable to load the Oracle JDBC driver", e);
24+
}
25+
}
26+
27+
/**
28+
* Execute the specified query object with supplied arguments as an 'update' operation
29+
*
30+
* @param query query object to execute
31+
* @param queryArgs replacement values for query place-holders
32+
* @return count of records updated
33+
*/
34+
public static int update(QueryAPI query, Object... queryArgs) {
35+
Integer result = (Integer) executeOracleQuery(null, query, queryArgs);
36+
return result.intValue();
37+
}
38+
39+
/**
40+
* Execute the specified query object with supplied arguments as a 'query' operation
41+
*
42+
* @param query query object to execute
43+
* @param queryArgs replacement values for query place-holders
44+
* @return row 1 / column 1 as integer; -1 if no rows were returned
45+
*/
46+
public static int getInt(QueryAPI query, Object... queryArgs) {
47+
Integer result = (Integer) executeOracleQuery(Integer.class, query, queryArgs);
48+
return result.intValue();
49+
}
50+
51+
/**
52+
* Execute the specified query object with supplied arguments as a 'query' operation
53+
*
54+
* @param query query object to execute
55+
* @param queryArgs replacement values for query place-holders
56+
* @return row 1 / column 1 as string; 'null' if no rows were returned
57+
*/
58+
public static String getString(QueryAPI query, Object... queryArgs) {
59+
return (String) executeOracleQuery(String.class, query, queryArgs);
60+
}
61+
62+
/**
63+
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
64+
* <p>
65+
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
66+
* <li>'null' - The query is executed as an update operation.</li>
67+
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
68+
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
69+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
70+
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
71+
*
72+
* @param resultType desired result type (see TYPES above)
73+
* @param query query object to execute
74+
* @param queryArgs replacement values for query place-holders
75+
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
76+
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
77+
* when you're done with it to free up database and JDBC resources that were allocated for it.
78+
*/
79+
private static Object executeOracleQuery(Class<?> resultType, QueryAPI query, Object... queryArgs) {
80+
int expectCount = query.getArgCount();
81+
int actualCount = queryArgs.length;
82+
83+
if (actualCount != expectCount) {
84+
String message;
85+
86+
if (expectCount == 0) {
87+
message = "No arguments expected for " + query.getEnum().name();
88+
} else {
89+
message = String.format("Incorrect argument count for %s%s: expect: %d; actual: %d",
90+
query.getEnum().name(), Arrays.toString(query.getArgNames()), expectCount, actualCount);
91+
}
92+
93+
throw new IllegalArgumentException(message);
94+
}
95+
96+
return executeOracleQuery(resultType, query.getConnection(), query.getQueryStr(), queryArgs);
97+
}
98+
99+
/**
100+
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
101+
* <p>
102+
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
103+
* <li>'null' - The query is executed as an update operation.</li>
104+
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
105+
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
106+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
107+
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
108+
*
109+
* @param resultType desired result type (see TYPES above)
110+
* @param connectionStr Oracle database connection string
111+
* @param queryStr a SQL statement that may contain one or more '?' IN parameter placeholders
112+
* @param param an array of objects containing the input parameter values
113+
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
114+
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
115+
* when you're done with it to free up database and JDBC resources that were allocated for it.
116+
*/
117+
public static Object executeOracleQuery(Class<?> resultType, String connectionStr, String queryStr, Object... param) {
118+
Object result = null;
119+
boolean failed = false;
120+
121+
Connection connection = null;
122+
PreparedStatement statement = null;
123+
ResultSet resultSet = null;
124+
125+
try {
126+
connection = getOracleConnection(connectionStr);
127+
statement = connection.prepareStatement(queryStr);
128+
129+
for (int i = 0; i < param.length; i++) {
130+
statement.setObject(i + 1, param[i]);
131+
}
132+
133+
if (resultType == null) {
134+
result = Integer.valueOf(statement.executeUpdate());
135+
} else {
136+
resultSet = statement.executeQuery();
137+
138+
if (resultType == ResultPackage.class) {
139+
result = new ResultPackage(connection, statement, resultSet);
140+
} else if (resultType == Integer.class) {
141+
result = Integer.valueOf((resultSet.next()) ? resultSet.getInt(1) : -1);
142+
} else if (resultType == String.class) {
143+
result = (resultSet.next()) ? resultSet.getString(1) : null;
144+
} else {
145+
result = (resultSet.next()) ? resultSet.getObject(1, resultType) : null;
146+
}
147+
}
148+
149+
} catch (SQLException e) {
150+
failed = true;
151+
throw UncheckedThrow.throwUnchecked(e);
152+
} finally {
153+
if (failed || (resultType != ResultPackage.class)) {
154+
if (resultSet != null) {
155+
try {
156+
resultSet.close();
157+
} catch (SQLException e) { }
158+
}
159+
if (statement != null) {
160+
try {
161+
statement.close();
162+
} catch (SQLException e) { }
163+
}
164+
if (connection != null) {
165+
try {
166+
connection.commit();
167+
connection.close();
168+
} catch (SQLException e) { }
169+
}
170+
}
171+
}
172+
173+
return result;
174+
}
175+
176+
/**
177+
* Get a connection to the Oracle database associated with the specified connection string.
178+
*
179+
* @param connectionString Oracle database connection string
180+
* @return Oracle database connection object
181+
*/
182+
private static Connection getOracleConnection(String connectionString) {
183+
try {
184+
QueryCreds creds = new QueryCreds(connectionString);
185+
return DriverManager.getConnection(creds.url, creds.userId, creds.password);
186+
} catch (SQLException e) {
187+
throw UncheckedThrow.throwUnchecked(e);
188+
}
189+
}
190+
191+
/**
192+
* This class encapsulated database query credentials.
193+
*/
194+
private static class QueryCreds {
195+
196+
private String url;
197+
private String userId;
198+
private String password;
199+
200+
/**
201+
* Constructor for database query credentials.
202+
*
203+
* @param connectionString database connection string
204+
*/
205+
private QueryCreds(String connectionString) {
206+
String[] bits = connectionString.split(";");
207+
208+
url = bits[0].trim();
209+
userId = bits[1].split("=")[1].trim();
210+
password = bits[2].split("=")[1].trim();
211+
}
212+
}
213+
214+
/**
215+
* This interface defines the API supported by database query collections
216+
*/
217+
public interface QueryAPI {
218+
219+
/**
220+
* Get the query string for this query object.
221+
*
222+
* @return query object query string
223+
*/
224+
String getQueryStr();
225+
226+
/**
227+
* Get the argument name for this query object
228+
*
229+
* @return query object argument names
230+
*/
231+
String[] getArgNames();
232+
233+
/**
234+
* Get the count of arguments for this query object.
235+
*
236+
* @return query object argument count
237+
*/
238+
int getArgCount();
239+
240+
/**
241+
* Get the database connection string for this query object.
242+
*
243+
* @return query object connection string
244+
*/
245+
String getConnection();
246+
247+
/**
248+
* Get the implementing enumerated constant for this query object.
249+
*
250+
* @return query object enumerated constant
251+
*/
252+
Enum<?> getEnum();
253+
}
254+
255+
/**
256+
* This class defines a package of database objects associated with a query. These include:<ul>
257+
* <li>{@link Connection} object</li>
258+
* <li>{@link PreparedStatement} object</li>
259+
* <li>{@link ResultSet} object</li></ul>
260+
*/
261+
public static class ResultPackage implements AutoCloseable {
262+
263+
private Connection connection;
264+
private PreparedStatement statement;
265+
private ResultSet resultSet;
266+
267+
/**
268+
* Constructor for a result package object
269+
*
270+
* @param connection {@link Connection} object
271+
* @param statement {@link PreparedStatement} object
272+
* @param resultSet {@link ResultSet} object
273+
*/
274+
private ResultPackage(Connection connection, PreparedStatement statement, ResultSet resultSet) {
275+
this.connection = connection;
276+
this.statement = statement;
277+
this.resultSet = resultSet;
278+
}
279+
280+
/**
281+
* Get the result set object of this package.
282+
*
283+
* @return {@link ResultSet} object
284+
*/
285+
public ResultSet getResultSet() {
286+
if (resultSet != null) return resultSet;
287+
throw new IllegalStateException("The result set in this package has been closed");
288+
}
289+
290+
@Override
291+
public void close() {
292+
if (resultSet != null) {
293+
try {
294+
resultSet.close();
295+
resultSet = null;
296+
} catch (SQLException e) { }
297+
}
298+
if (statement != null) {
299+
try {
300+
statement.close();
301+
statement = null;
302+
} catch (SQLException e) { }
303+
}
304+
if (connection != null) {
305+
try {
306+
connection.commit();
307+
connection.close();
308+
connection = null;
309+
} catch (SQLException e) { }
310+
}
311+
}
312+
}
313+
}

0 commit comments

Comments
 (0)