001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.support;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Method;
021: import java.sql.Blob;
022: import java.sql.Clob;
023: import java.sql.Connection;
024: import java.sql.DatabaseMetaData;
025: import java.sql.ResultSet;
026: import java.sql.SQLException;
027: import java.sql.Statement;
028: import java.sql.Types;
029:
030: import javax.sql.DataSource;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034:
035: import org.springframework.jdbc.CannotGetJdbcConnectionException;
036: import org.springframework.jdbc.datasource.DataSourceUtils;
037:
038: /**
039: * Generic utility methods for working with JDBC. Mainly for internal use
040: * within the framework, but also useful for custom JDBC access code.
041: *
042: * @author Thomas Risberg
043: * @author Juergen Hoeller
044: */
045: public abstract class JdbcUtils {
046:
047: /**
048: * Constant that indicates an unknown (or unspecified) SQL type.
049: * @see java.sql.Types
050: */
051: public static final int TYPE_UNKNOWN = Integer.MIN_VALUE;
052:
053: private static final Log logger = LogFactory
054: .getLog(JdbcUtils.class);
055:
056: /**
057: * Close the given JDBC Connection and ignore any thrown exception.
058: * This is useful for typical finally blocks in manual JDBC code.
059: * @param con the JDBC Connection to close (may be <code>null</code>)
060: */
061: public static void closeConnection(Connection con) {
062: if (con != null) {
063: try {
064: con.close();
065: } catch (SQLException ex) {
066: logger.debug("Could not close JDBC Connection", ex);
067: } catch (Throwable ex) {
068: // We don't trust the JDBC driver: It might throw RuntimeException or Error.
069: logger
070: .debug(
071: "Unexpected exception on closing JDBC Connection",
072: ex);
073: }
074: }
075: }
076:
077: /**
078: * Close the given JDBC Statement and ignore any thrown exception.
079: * This is useful for typical finally blocks in manual JDBC code.
080: * @param stmt the JDBC Statement to close (may be <code>null</code>)
081: */
082: public static void closeStatement(Statement stmt) {
083: if (stmt != null) {
084: try {
085: stmt.close();
086: } catch (SQLException ex) {
087: logger.debug("Could not close JDBC Statement", ex);
088: } catch (Throwable ex) {
089: // We don't trust the JDBC driver: It might throw RuntimeException or Error.
090: logger
091: .debug(
092: "Unexpected exception on closing JDBC Statement",
093: ex);
094: }
095: }
096: }
097:
098: /**
099: * Close the given JDBC ResultSet and ignore any thrown exception.
100: * This is useful for typical finally blocks in manual JDBC code.
101: * @param rs the JDBC ResultSet to close (may be <code>null</code>)
102: */
103: public static void closeResultSet(ResultSet rs) {
104: if (rs != null) {
105: try {
106: rs.close();
107: } catch (SQLException ex) {
108: logger.debug("Could not close JDBC ResultSet", ex);
109: } catch (Throwable ex) {
110: // We don't trust the JDBC driver: It might throw RuntimeException or Error.
111: logger
112: .debug(
113: "Unexpected exception on closing JDBC ResultSet",
114: ex);
115: }
116: }
117: }
118:
119: /**
120: * Retrieve a JDBC column value from a ResultSet, using the most appropriate
121: * value type. The returned value should be a detached value object, not having
122: * any ties to the active ResultSet: in particular, it should not be a Blob or
123: * Clob object but rather a byte array respectively String representation.
124: * <p>Uses the <code>getObject(index)</code> method, but includes additional "hacks"
125: * to get around Oracle 10g returning a non-standard object for its TIMESTAMP
126: * datatype and a <code>java.sql.Date</code> for DATE columns leaving out the
127: * time portion: These columns will explicitly be extracted as standard
128: * <code>java.sql.Timestamp</code> object.
129: * @param rs is the ResultSet holding the data
130: * @param index is the column index
131: * @return the value object
132: * @throws SQLException if thrown by the JDBC API
133: * @see java.sql.Blob
134: * @see java.sql.Clob
135: * @see java.sql.Timestamp
136: */
137: public static Object getResultSetValue(ResultSet rs, int index)
138: throws SQLException {
139: Object obj = rs.getObject(index);
140: if (obj instanceof Blob) {
141: obj = rs.getBytes(index);
142: } else if (obj instanceof Clob) {
143: obj = rs.getString(index);
144: } else if (obj != null
145: && obj.getClass().getName().startsWith(
146: "oracle.sql.TIMESTAMP")) {
147: obj = rs.getTimestamp(index);
148: } else if (obj != null
149: && obj.getClass().getName().startsWith(
150: "oracle.sql.DATE")) {
151: String metaDataClassName = rs.getMetaData()
152: .getColumnClassName(index);
153: if ("java.sql.Timestamp".equals(metaDataClassName)
154: || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
155: obj = rs.getTimestamp(index);
156: } else {
157: obj = rs.getDate(index);
158: }
159: } else if (obj != null && obj instanceof java.sql.Date) {
160: if ("java.sql.Timestamp".equals(rs.getMetaData()
161: .getColumnClassName(index))) {
162: obj = rs.getTimestamp(index);
163: }
164: }
165: return obj;
166: }
167:
168: /**
169: * Extract database meta data via the given DatabaseMetaDataCallback.
170: * <p>This method will open a connection to the database and retrieve the database metadata.
171: * Since this method is called before the exception translation feature is configured for
172: * a datasource, this method can not rely on the SQLException translation functionality.
173: * <p>Any exceptions will be wrapped in a MetaDataAccessException. This is a checked exception
174: * and any calling code should catch and handle this exception. You can just log the
175: * error and hope for the best, but there is probably a more serious error that will
176: * reappear when you try to access the database again.
177: * @param dataSource the DataSource to extract metadata for
178: * @param action callback that will do the actual work
179: * @return object containing the extracted information, as returned by
180: * the DatabaseMetaDataCallback's <code>processMetaData</code> method
181: * @throws MetaDataAccessException if meta data access failed
182: */
183: public static Object extractDatabaseMetaData(DataSource dataSource,
184: DatabaseMetaDataCallback action)
185: throws MetaDataAccessException {
186:
187: Connection con = null;
188: try {
189: con = DataSourceUtils.getConnection(dataSource);
190: if (con == null) {
191: // should only happen in test environments
192: throw new MetaDataAccessException(
193: "Connection returned by DataSource ["
194: + dataSource + "] was null");
195: }
196: DatabaseMetaData metaData = con.getMetaData();
197: if (metaData == null) {
198: // should only happen in test environments
199: throw new MetaDataAccessException(
200: "DatabaseMetaData returned by Connection ["
201: + con + "] was null");
202: }
203: return action.processMetaData(metaData);
204: } catch (CannotGetJdbcConnectionException ex) {
205: throw new MetaDataAccessException(
206: "Could not get Connection for extracting meta data",
207: ex);
208: } catch (SQLException ex) {
209: throw new MetaDataAccessException(
210: "Error while extracting DatabaseMetaData", ex);
211: } catch (AbstractMethodError err) {
212: throw new MetaDataAccessException(
213: "JDBC DatabaseMetaData method not implemented by JDBC driver - upgrade your driver",
214: err);
215: } finally {
216: DataSourceUtils.releaseConnection(con, dataSource);
217: }
218: }
219:
220: /**
221: * Call the specified method on DatabaseMetaData for the given DataSource,
222: * and extract the invocation result.
223: * @param dataSource the DataSource to extract meta data for
224: * @param metaDataMethodName the name of the DatabaseMetaData method to call
225: * @return the object returned by the specified DatabaseMetaData method
226: * @throws MetaDataAccessException if we couldn't access the DatabaseMetaData
227: * or failed to invoke the specified method
228: * @see java.sql.DatabaseMetaData
229: */
230: public static Object extractDatabaseMetaData(DataSource dataSource,
231: final String metaDataMethodName)
232: throws MetaDataAccessException {
233:
234: return extractDatabaseMetaData(dataSource,
235: new DatabaseMetaDataCallback() {
236: public Object processMetaData(DatabaseMetaData dbmd)
237: throws SQLException,
238: MetaDataAccessException {
239: try {
240: Method method = dbmd.getClass().getMethod(
241: metaDataMethodName, (Class[]) null);
242: return method.invoke(dbmd, (Object[]) null);
243: } catch (NoSuchMethodException ex) {
244: throw new MetaDataAccessException(
245: "No method named '"
246: + metaDataMethodName
247: + "' found on DatabaseMetaData instance ["
248: + dbmd + "]", ex);
249: } catch (IllegalAccessException ex) {
250: throw new MetaDataAccessException(
251: "Could not access DatabaseMetaData method '"
252: + metaDataMethodName + "'",
253: ex);
254: } catch (InvocationTargetException ex) {
255: if (ex.getTargetException() instanceof SQLException) {
256: throw (SQLException) ex
257: .getTargetException();
258: }
259: throw new MetaDataAccessException(
260: "Invocation of DatabaseMetaData method '"
261: + metaDataMethodName
262: + "' failed", ex);
263: }
264: }
265: });
266: }
267:
268: /**
269: * Return whether the given JDBC driver supports JDBC 2.0 batch updates.
270: * <p>Typically invoked right before execution of a given set of statements:
271: * to decide whether the set of SQL statements should be executed through
272: * the JDBC 2.0 batch mechanism or simply in a traditional one-by-one fashion.
273: * <p>Logs a warning if the "supportsBatchUpdates" methods throws an exception
274: * and simply returns <code>false</code> in that case.
275: * @param con the Connection to check
276: * @return whether JDBC 2.0 batch updates are supported
277: * @see java.sql.DatabaseMetaData#supportsBatchUpdates()
278: */
279: public static boolean supportsBatchUpdates(Connection con) {
280: try {
281: DatabaseMetaData dbmd = con.getMetaData();
282: if (dbmd != null) {
283: if (dbmd.supportsBatchUpdates()) {
284: logger.debug("JDBC driver supports batch updates");
285: return true;
286: } else {
287: logger
288: .debug("JDBC driver does not support batch updates");
289: }
290: }
291: } catch (SQLException ex) {
292: logger
293: .debug(
294: "JDBC driver 'supportsBatchUpdates' method threw exception",
295: ex);
296: } catch (AbstractMethodError err) {
297: logger
298: .debug(
299: "JDBC driver does not support JDBC 2.0 'supportsBatchUpdates' method",
300: err);
301: }
302: return false;
303: }
304:
305: /**
306: * Check whether the given SQL type is numeric.
307: * @param sqlType the SQL type to be checked
308: * @return whether the type is numeric
309: */
310: public static boolean isNumeric(int sqlType) {
311: return Types.BIT == sqlType || Types.BIGINT == sqlType
312: || Types.DECIMAL == sqlType || Types.DOUBLE == sqlType
313: || Types.FLOAT == sqlType || Types.INTEGER == sqlType
314: || Types.NUMERIC == sqlType || Types.REAL == sqlType
315: || Types.SMALLINT == sqlType
316: || Types.TINYINT == sqlType;
317: }
318:
319: }
|