http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java b/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java new file mode 100644 index 0000000..46c089b --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java @@ -0,0 +1,504 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * <p> + * <code>BeanProcessor</code> matches column names to bean property names + * and converts <code>ResultSet</code> columns into objects for those bean + * properties. Subclasses should override the methods in the processing chain + * to customize behavior. + * </p> + * + * <p> + * This class is thread-safe. + * </p> + * + * @see BasicRowProcessor + * + * @since DbUtils 1.1 + */ +public class BeanProcessor { + + /** + * Special array value used by <code>mapColumnsToProperties</code> that + * indicates there is no bean property that matches a column from a + * <code>ResultSet</code>. + */ + protected static final int PROPERTY_NOT_FOUND = -1; + + /** + * Set a bean's primitive properties to these defaults when SQL NULL + * is returned. These are the same as the defaults that ResultSet get* + * methods return in the event of a NULL column. + */ + private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>(); + + /** + * ResultSet column to bean property name overrides. + */ + private final Map<String, String> columnToPropertyOverrides; + + static { + primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0)); + primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0)); + primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0)); + primitiveDefaults.put(Float.TYPE, Float.valueOf(0f)); + primitiveDefaults.put(Double.TYPE, Double.valueOf(0d)); + primitiveDefaults.put(Long.TYPE, Long.valueOf(0L)); + primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE); + primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0)); + } + + /** + * Constructor for BeanProcessor. + */ + public BeanProcessor() { + this(new HashMap<String, String>()); + } + + /** + * Constructor for BeanProcessor configured with column to property name overrides. + * + * @param columnToPropertyOverrides ResultSet column to bean property name overrides + * @since 1.5 + */ + public BeanProcessor(Map<String, String> columnToPropertyOverrides) { + super(); + if (columnToPropertyOverrides == null) { + throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null"); + } + this.columnToPropertyOverrides = columnToPropertyOverrides; + } + + /** + * Convert a <code>ResultSet</code> row into a JavaBean. This + * implementation uses reflection and <code>BeanInfo</code> classes to + * match column names to bean property names. Properties are matched to + * columns based on several factors: + * <br/> + * <ol> + * <li> + * The class has a writable property with the same name as a column. + * The name comparison is case insensitive. + * </li> + * + * <li> + * The column type can be converted to the property's set method + * parameter type with a ResultSet.get* method. If the conversion fails + * (ie. the property was an int and the column was a Timestamp) an + * SQLException is thrown. + * </li> + * </ol> + * + * <p> + * Primitive bean properties are set to their defaults when SQL NULL is + * returned from the <code>ResultSet</code>. Numeric fields are set to 0 + * and booleans are set to false. Object bean properties are set to + * <code>null</code> when SQL NULL is returned. This is the same behavior + * as the <code>ResultSet</code> get* methods. + * </p> + * @param <T> The type of bean to create + * @param rs ResultSet that supplies the bean data + * @param type Class from which to create the bean instance + * @throws SQLException if a database access error occurs + * @return the newly created bean + */ + public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException { + + PropertyDescriptor[] props = this.propertyDescriptors(type); + + ResultSetMetaData rsmd = rs.getMetaData(); + int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); + + return this.createBean(rs, type, props, columnToProperty); + } + + /** + * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans. + * This implementation uses reflection and <code>BeanInfo</code> classes to + * match column names to bean property names. Properties are matched to + * columns based on several factors: + * <br/> + * <ol> + * <li> + * The class has a writable property with the same name as a column. + * The name comparison is case insensitive. + * </li> + * + * <li> + * The column type can be converted to the property's set method + * parameter type with a ResultSet.get* method. If the conversion fails + * (ie. the property was an int and the column was a Timestamp) an + * SQLException is thrown. + * </li> + * </ol> + * + * <p> + * Primitive bean properties are set to their defaults when SQL NULL is + * returned from the <code>ResultSet</code>. Numeric fields are set to 0 + * and booleans are set to false. Object bean properties are set to + * <code>null</code> when SQL NULL is returned. This is the same behavior + * as the <code>ResultSet</code> get* methods. + * </p> + * @param <T> The type of bean to create + * @param rs ResultSet that supplies the bean data + * @param type Class from which to create the bean instance + * @throws SQLException if a database access error occurs + * @return the newly created List of beans + */ + public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException { + List<T> results = new ArrayList<T>(); + + if (!rs.next()) { + return results; + } + + PropertyDescriptor[] props = this.propertyDescriptors(type); + ResultSetMetaData rsmd = rs.getMetaData(); + int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); + + do { + results.add(this.createBean(rs, type, props, columnToProperty)); + } while (rs.next()); + + return results; + } + + /** + * Creates a new object and initializes its fields from the ResultSet. + * @param <T> The type of bean to create + * @param rs The result set. + * @param type The bean type (the return type of the object). + * @param props The property descriptors. + * @param columnToProperty The column indices in the result set. + * @return An initialized object. + * @throws SQLException if a database error occurs. + */ + private <T> T createBean(ResultSet rs, Class<T> type, + PropertyDescriptor[] props, int[] columnToProperty) + throws SQLException { + + T bean = this.newInstance(type); + + for (int i = 1; i < columnToProperty.length; i++) { + + if (columnToProperty[i] == PROPERTY_NOT_FOUND) { + continue; + } + + PropertyDescriptor prop = props[columnToProperty[i]]; + Class<?> propType = prop.getPropertyType(); + + Object value = this.processColumn(rs, i, propType); + + if (propType != null && value == null && propType.isPrimitive()) { + value = primitiveDefaults.get(propType); + } + + this.callSetter(bean, prop, value); + } + + return bean; + } + + /** + * Calls the setter method on the target object for the given property. + * If no setter method exists for the property, this method does nothing. + * @param target The object to set the property on. + * @param prop The property to set. + * @param value The value to pass into the setter. + * @throws SQLException if an error occurs setting the property. + */ + private void callSetter(Object target, PropertyDescriptor prop, Object value) + throws SQLException { + + Method setter = prop.getWriteMethod(); + + if (setter == null) { + return; + } + + Class<?>[] params = setter.getParameterTypes(); + try { + // convert types for some popular ones + if (value instanceof java.util.Date) { + final String targetType = params[0].getName(); + if ("java.sql.Date".equals(targetType)) { + value = new java.sql.Date(((java.util.Date) value).getTime()); + } else + if ("java.sql.Time".equals(targetType)) { + value = new java.sql.Time(((java.util.Date) value).getTime()); + } else + if ("java.sql.Timestamp".equals(targetType)) { + value = new java.sql.Timestamp(((java.util.Date) value).getTime()); + } + } + + // Don't call setter if the value object isn't the right type + if (this.isCompatibleType(value, params[0])) { + setter.invoke(target, new Object[]{value}); + } else { + throw new SQLException( + "Cannot set " + prop.getName() + ": incompatible types, cannot convert " + + value.getClass().getName() + " to " + params[0].getName()); + // value cannot be null here because isCompatibleType allows null + } + + } catch (IllegalArgumentException e) { + throw new SQLException( + "Cannot set " + prop.getName() + ": " + e.getMessage()); + + } catch (IllegalAccessException e) { + throw new SQLException( + "Cannot set " + prop.getName() + ": " + e.getMessage()); + + } catch (InvocationTargetException e) { + throw new SQLException( + "Cannot set " + prop.getName() + ": " + e.getMessage()); + } + } + + /** + * ResultSet.getObject() returns an Integer object for an INT column. The + * setter method for the property might take an Integer or a primitive int. + * This method returns true if the value can be successfully passed into + * the setter method. Remember, Method.invoke() handles the unwrapping + * of Integer into an int. + * + * @param value The value to be passed into the setter method. + * @param type The setter's parameter type (non-null) + * @return boolean True if the value is compatible (null => true) + */ + private boolean isCompatibleType(Object value, Class<?> type) { + // Do object check first, then primitives + if (value == null || type.isInstance(value)) { + return true; + + } else if (type.equals(Integer.TYPE) && Integer.class.isInstance(value)) { + return true; + + } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) { + return true; + + } else if (type.equals(Double.TYPE) && Double.class.isInstance(value)) { + return true; + + } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) { + return true; + + } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) { + return true; + + } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) { + return true; + + } else if (type.equals(Character.TYPE) && Character.class.isInstance(value)) { + return true; + + } else if (type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) { + return true; + + } + return false; + + } + + /** + * Factory method that returns a new instance of the given Class. This + * is called at the start of the bean creation process and may be + * overridden to provide custom behavior like returning a cached bean + * instance. + * @param <T> The type of object to create + * @param c The Class to create an object from. + * @return A newly created object of the Class. + * @throws SQLException if creation failed. + */ + protected <T> T newInstance(Class<T> c) throws SQLException { + try { + return c.newInstance(); + + } catch (InstantiationException e) { + throw new SQLException( + "Cannot create " + c.getName() + ": " + e.getMessage()); + + } catch (IllegalAccessException e) { + throw new SQLException( + "Cannot create " + c.getName() + ": " + e.getMessage()); + } + } + + /** + * Returns a PropertyDescriptor[] for the given Class. + * + * @param c The Class to retrieve PropertyDescriptors for. + * @return A PropertyDescriptor[] describing the Class. + * @throws SQLException if introspection failed. + */ + private PropertyDescriptor[] propertyDescriptors(Class<?> c) + throws SQLException { + // Introspector caches BeanInfo classes for better performance + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(c); + + } catch (IntrospectionException e) { + throw new SQLException( + "Bean introspection failed: " + e.getMessage()); + } + + return beanInfo.getPropertyDescriptors(); + } + + /** + * The positions in the returned array represent column numbers. The + * values stored at each position represent the index in the + * <code>PropertyDescriptor[]</code> for the bean property that matches + * the column name. If no bean property was found for a column, the + * position is set to <code>PROPERTY_NOT_FOUND</code>. + * + * @param rsmd The <code>ResultSetMetaData</code> containing column + * information. + * + * @param props The bean property descriptors. + * + * @throws SQLException if a database access error occurs + * + * @return An int[] with column index to property index mappings. The 0th + * element is meaningless because JDBC column indexing starts at 1. + */ + protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, + PropertyDescriptor[] props) throws SQLException { + + int cols = rsmd.getColumnCount(); + int[] columnToProperty = new int[cols + 1]; + Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND); + + for (int col = 1; col <= cols; col++) { + String columnName = rsmd.getColumnLabel(col); + if (null == columnName || 0 == columnName.length()) { + columnName = rsmd.getColumnName(col); + } + String propertyName = columnToPropertyOverrides.get(columnName); + if (propertyName == null) { + propertyName = columnName; + } + for (int i = 0; i < props.length; i++) { + + if (propertyName.equalsIgnoreCase(props[i].getName())) { + columnToProperty[col] = i; + break; + } + } + } + + return columnToProperty; + } + + /** + * Convert a <code>ResultSet</code> column into an object. Simple + * implementations could just call <code>rs.getObject(index)</code> while + * more complex implementations could perform type manipulation to match + * the column's type to the bean property type. + * + * <p> + * This implementation calls the appropriate <code>ResultSet</code> getter + * method for the given property type to perform the type conversion. If + * the property type doesn't match one of the supported + * <code>ResultSet</code> types, <code>getObject</code> is called. + * </p> + * + * @param rs The <code>ResultSet</code> currently being processed. It is + * positioned on a valid row before being passed into this method. + * + * @param index The current column index being processed. + * + * @param propType The bean property type that this column needs to be + * converted into. + * + * @throws SQLException if a database access error occurs + * + * @return The object from the <code>ResultSet</code> at the given column + * index after optional type processing or <code>null</code> if the column + * value was SQL NULL. + */ + protected Object processColumn(ResultSet rs, int index, Class<?> propType) + throws SQLException { + + if ( !propType.isPrimitive() && rs.getObject(index) == null ) { + return null; + } + + if (propType.equals(String.class)) { + return rs.getString(index); + + } else if ( + propType.equals(Integer.TYPE) || propType.equals(Integer.class)) { + return Integer.valueOf(rs.getInt(index)); + + } else if ( + propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) { + return Boolean.valueOf(rs.getBoolean(index)); + + } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) { + return Long.valueOf(rs.getLong(index)); + + } else if ( + propType.equals(Double.TYPE) || propType.equals(Double.class)) { + return Double.valueOf(rs.getDouble(index)); + + } else if ( + propType.equals(Float.TYPE) || propType.equals(Float.class)) { + return Float.valueOf(rs.getFloat(index)); + + } else if ( + propType.equals(Short.TYPE) || propType.equals(Short.class)) { + return Short.valueOf(rs.getShort(index)); + + } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) { + return Byte.valueOf(rs.getByte(index)); + + } else if (propType.equals(Timestamp.class)) { + return rs.getTimestamp(index); + + } else if (propType.equals(SQLXML.class)) { + return rs.getSQLXML(index); + + } else { + return rs.getObject(index); + } + + } + +}
http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/DbUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/DbUtils.java b/src/main/java/org/apache/commons/dbutils2/DbUtils.java new file mode 100644 index 0000000..5968d4e --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/DbUtils.java @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import static java.sql.DriverManager.registerDriver; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverPropertyInfo; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.util.Properties; +import java.util.logging.Logger; + +/** + * A collection of JDBC helper methods. This class is thread safe. + */ +public final class DbUtils { + + /** + * Utility classes do not have a public default constructor. + * + * @since 1.4 + */ + private DbUtils() { + // do nothing + } + + /** + * Close a <code>Connection</code>, avoid closing if null. + * + * @param conn Connection to close. + * @throws SQLException if a database access error occurs + */ + public static void close(Connection conn) throws SQLException { + if (conn != null) { + conn.close(); + } + } + + /** + * Close a <code>ResultSet</code>, avoid closing if null. + * + * @param rs ResultSet to close. + * @throws SQLException if a database access error occurs + */ + public static void close(ResultSet rs) throws SQLException { + if (rs != null) { + rs.close(); + } + } + + /** + * Close a <code>Statement</code>, avoid closing if null. + * + * @param stmt Statement to close. + * @throws SQLException if a database access error occurs + */ + public static void close(Statement stmt) throws SQLException { + if (stmt != null) { + stmt.close(); + } + } + + /** + * Close a <code>Connection</code>, avoid closing if null and hide + * any SQLExceptions that occur. + * + * @param conn Connection to close. + */ + public static void closeQuietly(Connection conn) { + try { + close(conn); + } catch (SQLException e) { // NOPMD + // quiet + } + } + + /** + * Close a <code>Connection</code>, <code>Statement</code> and + * <code>ResultSet</code>. Avoid closing if null and hide any + * SQLExceptions that occur. + * + * @param conn Connection to close. + * @param stmt Statement to close. + * @param rs ResultSet to close. + */ + public static void closeQuietly(Connection conn, Statement stmt, + ResultSet rs) { + + try { + closeQuietly(rs); + } finally { + try { + closeQuietly(stmt); + } finally { + closeQuietly(conn); + } + } + + } + + /** + * Close a <code>ResultSet</code>, avoid closing if null and hide any + * SQLExceptions that occur. + * + * @param rs ResultSet to close. + */ + public static void closeQuietly(ResultSet rs) { + try { + close(rs); + } catch (SQLException e) { // NOPMD + // quiet + } + } + + /** + * Close a <code>Statement</code>, avoid closing if null and hide + * any SQLExceptions that occur. + * + * @param stmt Statement to close. + */ + public static void closeQuietly(Statement stmt) { + try { + close(stmt); + } catch (SQLException e) { // NOPMD + // quiet + } + } + + /** + * Commits a <code>Connection</code> then closes it, avoid closing if null. + * + * @param conn Connection to close. + * @throws SQLException if a database access error occurs + */ + public static void commitAndClose(Connection conn) throws SQLException { + if (conn != null) { + try { + conn.commit(); + } finally { + conn.close(); + } + } + } + + /** + * Commits a <code>Connection</code> then closes it, avoid closing if null + * and hide any SQLExceptions that occur. + * + * @param conn Connection to close. + */ + public static void commitAndCloseQuietly(Connection conn) { + try { + commitAndClose(conn); + } catch (SQLException e) { // NOPMD + // quiet + } + } + + /** + * Loads and registers a database driver class. + * If this succeeds, it returns true, else it returns false. + * + * @param driverClassName of driver to load + * @return boolean <code>true</code> if the driver was found, otherwise <code>false</code> + */ + public static boolean loadDriver(String driverClassName) { + return loadDriver(DbUtils.class.getClassLoader(), driverClassName); + } + + /** + * Loads and registers a database driver class. + * If this succeeds, it returns true, else it returns false. + * + * @param classLoader the class loader used to load the driver class + * @param driverClassName of driver to load + * @return boolean <code>true</code> if the driver was found, otherwise <code>false</code> + * @since 1.4 + */ + public static boolean loadDriver(ClassLoader classLoader, String driverClassName) { + try { + Class<?> loadedClass = classLoader.loadClass(driverClassName); + + if (!Driver.class.isAssignableFrom(loadedClass)) { + return false; + } + + @SuppressWarnings("unchecked") // guarded by previous check + Class<Driver> driverClass = (Class<Driver>) loadedClass; + Constructor<Driver> driverConstructor = driverClass.getConstructor(); + + // make Constructor accessible if it is private + boolean isConstructorAccessible = driverConstructor.isAccessible(); + if (!isConstructorAccessible) { + driverConstructor.setAccessible(true); + } + + try { + Driver driver = driverConstructor.newInstance(); + registerDriver(new DriverProxy(driver)); + } finally { + driverConstructor.setAccessible(isConstructorAccessible); + } + + return true; + } catch (Exception e) { + return false; + + } + } + + /** + * Print the stack trace for a SQLException to STDERR. + * + * @param e SQLException to print stack trace of + */ + public static void printStackTrace(SQLException e) { + printStackTrace(e, new PrintWriter(System.err)); + } + + /** + * Print the stack trace for a SQLException to a + * specified PrintWriter. + * + * @param e SQLException to print stack trace of + * @param pw PrintWriter to print to + */ + public static void printStackTrace(SQLException e, PrintWriter pw) { + + SQLException next = e; + while (next != null) { + next.printStackTrace(pw); + next = next.getNextException(); + if (next != null) { + pw.println("Next SQLException:"); + } + } + } + + /** + * Print warnings on a Connection to STDERR. + * + * @param conn Connection to print warnings from + */ + public static void printWarnings(Connection conn) { + printWarnings(conn, new PrintWriter(System.err)); + } + + /** + * Print warnings on a Connection to a specified PrintWriter. + * + * @param conn Connection to print warnings from + * @param pw PrintWriter to print to + */ + public static void printWarnings(Connection conn, PrintWriter pw) { + if (conn != null) { + try { + printStackTrace(conn.getWarnings(), pw); + } catch (SQLException e) { + printStackTrace(e, pw); + } + } + } + + /** + * Rollback any changes made on the given connection. + * @param conn Connection to rollback. A null value is legal. + * @throws SQLException if a database access error occurs + */ + public static void rollback(Connection conn) throws SQLException { + if (conn != null) { + conn.rollback(); + } + } + + /** + * Performs a rollback on the <code>Connection</code> then closes it, + * avoid closing if null. + * + * @param conn Connection to rollback. A null value is legal. + * @throws SQLException if a database access error occurs + * @since DbUtils 1.1 + */ + public static void rollbackAndClose(Connection conn) throws SQLException { + if (conn != null) { + try { + conn.rollback(); + } finally { + conn.close(); + } + } + } + + /** + * Performs a rollback on the <code>Connection</code> then closes it, + * avoid closing if null and hide any SQLExceptions that occur. + * + * @param conn Connection to rollback. A null value is legal. + * @since DbUtils 1.1 + */ + public static void rollbackAndCloseQuietly(Connection conn) { + try { + rollbackAndClose(conn); + } catch (SQLException e) { // NOPMD + // quiet + } + } + + /** + * Simple {@link Driver} proxy class that proxies a JDBC Driver loaded dynamically. + * + * @since 1.6 + */ + private static final class DriverProxy implements Driver { + + /** + * The adapted JDBC Driver loaded dynamically. + */ + private final Driver adapted; + + /** + * Creates a new JDBC Driver that adapts a JDBC Driver loaded dynamically. + * + * @param adapted the adapted JDBC Driver loaded dynamically. + */ + public DriverProxy(Driver adapted) { + this.adapted = adapted; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean acceptsURL(String url) throws SQLException { + return adapted.acceptsURL(url); + } + + /** + * {@inheritDoc} + */ + @Override + public Connection connect(String url, Properties info) throws SQLException { + return adapted.connect(url, info); + } + + /** + * {@inheritDoc} + */ + @Override + public int getMajorVersion() { + return adapted.getMajorVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getMinorVersion() { + return adapted.getMinorVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return adapted.getPropertyInfo(url, info); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean jdbcCompliant() { + return adapted.jdbcCompliant(); + } + + /** + * Java 1.7 method. + */ + @SuppressWarnings("unused") + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java b/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java new file mode 100644 index 0000000..b85b66e --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + + +import java.beans.PropertyDescriptor; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.Arrays; + + +/** + * Provides generous name matching (e.g. underscore-aware) from DB + * columns to Java Bean properties. + */ +public class GenerousBeanProcessor extends BeanProcessor { + + /** + * Default constructor. + */ + public GenerousBeanProcessor() { + super(); + } + + @Override + protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd, + final PropertyDescriptor[] props) throws SQLException { + + final int cols = rsmd.getColumnCount(); + final int[] columnToProperty = new int[cols + 1]; + Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND); + + for (int col = 1; col <= cols; col++) { + String columnName = rsmd.getColumnLabel(col); + + if (null == columnName || 0 == columnName.length()) { + columnName = rsmd.getColumnName(col); + } + + final String generousColumnName = columnName.replace("_",""); + + for (int i = 0; i < props.length; i++) { + final String propName = props[i].getName(); + + // see if either the column name, or the generous one matches + if (columnName.equalsIgnoreCase(propName) || + generousColumnName.equalsIgnoreCase(propName)) { + columnToProperty[col] = i; + break; + } + } + } + + return columnToProperty; + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/InsertExecutor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/InsertExecutor.java b/src/main/java/org/apache/commons/dbutils2/InsertExecutor.java new file mode 100644 index 0000000..39a6db6 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/InsertExecutor.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class InsertExecutor extends AbstractExecutor<InsertExecutor> { + + private final boolean closeConn; + + public InsertExecutor(final Connection conn, final String sql, final boolean closeConnection) throws SQLException { + super(conn, sql); + this.closeConn = closeConnection; + } + + /** + * Executes the given INSERT SQL statement. + * + * @param handler The handler used to create the result object from + * the <code>ResultSet</code> of auto-generated keys. + * + * @return An object generated by the handler. + * @throws SQLException If there are database or parameter errors. + */ + public <T> T execute(ResultSetHandler<T> handler) throws SQLException { + // throw an exception if there are unmapped parameters + this.throwIfUnmappedParams(); + + // make sure our handler is not null + if (handler == null) { + if (closeConn) { + close(getConnection()); + } + throw new SQLException("Null ResultSetHandler"); + } + + try { + // execute the update + getStatement().executeUpdate(); + + // get the result set + final ResultSet resultSet = getStatement().getGeneratedKeys(); + + // run the handler over the results and return them + return handler.handle(resultSet); + } catch (SQLException e) { + this.rethrow(e); + } finally { + close(getStatement()); + if (closeConn) { + close(getConnection()); + } + } + + // we get here only if something is thrown + return null; + } + + /** + * Executes the given INSERT SQL statement. + * @return the number of rows updated. + * @throws SQLException If there are database or parameter errors. + */ + public int execute() throws SQLException { + // throw an exception if there are unmapped parameters + this.throwIfUnmappedParams(); + + try { + // execute the insert + return getStatement().executeUpdate(); + } catch (SQLException e) { + this.rethrow(e); + } finally { + close(getStatement()); + if (closeConn) { + close(getConnection()); + } + } + + return 0; // only get here on an error + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/ProxyFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/ProxyFactory.java b/src/main/java/org/apache/commons/dbutils2/ProxyFactory.java new file mode 100644 index 0000000..9410f27 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/ProxyFactory.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.Statement; + +/** + * Creates proxy implementations of JDBC interfaces. This avoids + * incompatibilities between the JDBC 2 and JDBC 3 interfaces. This class is + * thread safe. + * + * @see java.lang.reflect.Proxy + * @see java.lang.reflect.InvocationHandler + */ +public class ProxyFactory { + + /** + * The Singleton instance of this class. + */ + private static final ProxyFactory instance = new ProxyFactory(); + + /** + * Returns the Singleton instance of this class. + * + * @return singleton instance + */ + public static ProxyFactory instance() { + return instance; + } + + /** + * Protected constructor for ProxyFactory subclasses to use. + */ + protected ProxyFactory() { + super(); + } + + /** + * Convenience method to generate a single-interface proxy using the handler's classloader + * + * @param <T> The type of object to proxy + * @param type The type of object to proxy + * @param handler The handler that intercepts/overrides method calls. + * @return proxied object + */ + public <T> T newProxyInstance(Class<T> type, InvocationHandler handler) { + return type.cast(Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class<?>[] {type}, handler)); + } + + /** + * Creates a new proxy <code>CallableStatement</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied CallableStatement + */ + public CallableStatement createCallableStatement(InvocationHandler handler) { + return newProxyInstance(CallableStatement.class, handler); + } + + /** + * Creates a new proxy <code>Connection</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied Connection + */ + public Connection createConnection(InvocationHandler handler) { + return newProxyInstance(Connection.class, handler); + } + + /** + * Creates a new proxy <code>Driver</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied Driver + */ + public Driver createDriver(InvocationHandler handler) { + return newProxyInstance(Driver.class, handler); + } + + /** + * Creates a new proxy <code>PreparedStatement</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied PreparedStatement + */ + public PreparedStatement createPreparedStatement(InvocationHandler handler) { + return newProxyInstance(PreparedStatement.class, handler); + } + + /** + * Creates a new proxy <code>ResultSet</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied ResultSet + */ + public ResultSet createResultSet(InvocationHandler handler) { + return newProxyInstance(ResultSet.class, handler); + } + + /** + * Creates a new proxy <code>ResultSetMetaData</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied ResultSetMetaData + */ + public ResultSetMetaData createResultSetMetaData(InvocationHandler handler) { + return newProxyInstance(ResultSetMetaData.class, handler); + } + + /** + * Creates a new proxy <code>Statement</code> object. + * @param handler The handler that intercepts/overrides method calls. + * @return proxied Statement + */ + public Statement createStatement(InvocationHandler handler) { + return newProxyInstance(Statement.class, handler); + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/QueryExecutor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/QueryExecutor.java b/src/main/java/org/apache/commons/dbutils2/QueryExecutor.java new file mode 100644 index 0000000..4a7c317 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/QueryExecutor.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Fluent class for executing a query. + * + * @since 2.0 + * @author William Speirs <[email protected]> + */ +class QueryExecutor extends AbstractExecutor<QueryExecutor> { + + private final boolean closeConn; + + public QueryExecutor(final Connection conn, final String sql, final boolean closeConnection) throws SQLException { + super(conn, sql); + this.closeConn = closeConnection; + } + + /** + * Calls query after checking the parameters to ensure nothing is null. + * + * @param rsh The handler that converts the results into an object. + * + * @return The results of the query. + * @throws SQLException If there are database or parameter errors. + */ + public <T> T execute(ResultSetHandler<T> handler) throws SQLException { + // throw an exception if there are unmapped parameters + this.throwIfUnmappedParams(); + + // make sure our handler is not null + if (handler == null) { + if (closeConn) { + close(getConnection()); + } + throw new SQLException("Null ResultSetHandler"); + } + + ResultSet resultSet = null; + + try { + // execute the query, wrapping it + resultSet = this.wrap(getStatement().executeQuery()); + // execute the handler + return handler.handle(resultSet); + } catch (SQLException e) { + // rethrow our exception printing more information + this.rethrow(e); + } finally { + try { + close(resultSet); + } finally { + close(getStatement()); + if (closeConn) { + close(getConnection()); + } + } + } + + // we get here only if something is thrown + return null; + } +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/QueryLoader.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/QueryLoader.java b/src/main/java/org/apache/commons/dbutils2/QueryLoader.java new file mode 100644 index 0000000..df6db6f --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/QueryLoader.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * <code>QueryLoader</code> is a registry for sets of queries so + * that multiple copies of the same queries aren't loaded into memory. + * This implementation loads properties files filled with query name to + * SQL mappings. This class is thread safe. + */ +public class QueryLoader { + + /** + * The Singleton instance of this class. + */ + private static final QueryLoader instance = new QueryLoader(); + + /** + * Return an instance of this class. + * @return The Singleton instance. + */ + public static QueryLoader instance() { + return instance; + } + + /** + * Maps query set names to Maps of their queries. + */ + private final Map<String, Map<String, String>> queries = new HashMap<String, Map<String, String>>(); + + /** + * QueryLoader constructor. + */ + protected QueryLoader() { + super(); + } + + /** + * Loads a Map of query names to SQL values. The Maps are cached so a + * subsequent request to load queries from the same path will return + * the cached Map. + * + * @param path The path that the ClassLoader will use to find the file. + * This is <strong>not</strong> a file system path. If you had a jarred + * Queries.properties file in the com.yourcorp.app.jdbc package you would + * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method. + * @throws IOException if a file access error occurs + * @throws IllegalArgumentException if the ClassLoader can't find a file at + * the given path. + * @return Map of query names to SQL values + */ + public synchronized Map<String, String> load(String path) throws IOException { + + Map<String, String> queryMap = this.queries.get(path); + + if (queryMap == null) { + queryMap = this.loadQueries(path); + this.queries.put(path, queryMap); + } + + return queryMap; + } + + /** + * Loads a set of named queries into a Map object. This implementation + * reads a properties file at the given path. + * @param path The path that the ClassLoader will use to find the file. + * @throws IOException if a file access error occurs + * @throws IllegalArgumentException if the ClassLoader can't find a file at + * the given path. + * @since DbUtils 1.1 + * @return Map of query names to SQL values + */ + protected Map<String, String> loadQueries(String path) throws IOException { + // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API? + InputStream in = getClass().getResourceAsStream(path); + + if (in == null) { + throw new IllegalArgumentException(path + " not found."); + } + + Properties props = new Properties(); + try { + props.load(in); + } finally { + in.close(); + } + + // Copy to HashMap for better performance + + @SuppressWarnings({ "rawtypes", "unchecked" }) // load() always creates <String,String> entries + HashMap<String, String> hashMap = new HashMap(props); + return hashMap; + } + + /** + * Removes the queries for the given path from the cache. + * @param path The path that the queries were loaded from. + */ + public synchronized void unload(String path) { + this.queries.remove(path); + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/QueryRunner.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/QueryRunner.java b/src/main/java/org/apache/commons/dbutils2/QueryRunner.java new file mode 100644 index 0000000..9dbc292 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/QueryRunner.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; + +/** + * Executes SQL queries with pluggable strategies for handling + * <code>ResultSet</code>s. This class is thread safe. + * + * @see ResultSetHandler + */ +public class QueryRunner { + /** + * The DataSource to retrieve connections from. + */ + private final DataSource ds; + + /** + * Constructor for QueryRunner. + */ + public QueryRunner() { + ds = null; + } + + /** + * Constructor for QueryRunner that takes a <code>DataSource</code> to use. + * + * Methods that do not take a <code>Connection</code> parameter will retrieve connections from this + * <code>DataSource</code>. + * + * @param ds The <code>DataSource</code> to retrieve connections from. + */ + public QueryRunner(final DataSource ds) { + this.ds = ds; + } + + /** + * Returns the <code>DataSource</code> this runner is using. + * <code>QueryRunner</code> methods always call this method to get the + * <code>DataSource</code> so subclasses can provide specialized behavior. + * + * @return DataSource the runner is using + */ + public DataSource getDataSource() { + return this.ds; + } + + /** + * Factory method that creates and initializes a <code>Connection</code> + * object. <code>QueryRunner</code> methods always call this method to + * retrieve connections from its DataSource. Subclasses can override this + * method to provide special <code>Connection</code> configuration if + * needed. This implementation simply calls <code>ds.getConnection()</code>. + * + * @return An initialized <code>Connection</code>. + * @throws SQLException if a database access error occurs + */ + protected Connection prepareConnection() throws SQLException { + if (this.getDataSource() == null) { + throw new SQLException( + "QueryRunner requires a DataSource to be " + + "invoked in this way, or a Connection should be passed in"); + } + return this.getDataSource().getConnection(); + } + + /** + * Close a <code>Connection</code>. This implementation avoids closing if + * null and does <strong>not</strong> suppress any exceptions. Subclasses + * can override to provide special handling like logging. + * + * @param conn Connection to close + * @throws SQLException if a database access error occurs + */ + private void close(Connection conn) throws SQLException { + DbUtils.close(conn); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.BatchExecutor} for the given SQL. + * <code>Connection</code> is retrieved from the <code>DataSource</code> + * set in the constructor. This <code>Connection</code> must be in + * auto-commit mode or the insert will not be saved. The <code>Connection</code> is + * closed after the call. + * + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.BatchExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public BatchExecutor batch(String sql) throws SQLException { + return this.batch(this.prepareConnection(), true, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.BatchExecutor} for the given SQL statement and connection. + * The connection is <b>NOT</b> closed after execution. + * + * @param conn The connection to use for the batch call. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.BatchExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public BatchExecutor batch(Connection conn, String sql) throws SQLException { + return this.batch(conn, true, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.BatchExecutor} for the given SQL statement and connection. + * + * @param conn The connection to use for the batch call. + * @param closeConn True if the connection should be closed, false otherwise. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.BatchExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public BatchExecutor batch(Connection conn, boolean closeConn, String sql) throws SQLException { + if (conn == null) { + throw new SQLException("Null connection"); + } + + if (sql == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null SQL statement"); + } + + return new BatchExecutor(conn, sql, closeConn); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.QueryExecutor} for the given SQL. + * <code>Connection</code> is retrieved from the <code>DataSource</code> + * set in the constructor. This <code>Connection</code> must be in + * auto-commit mode or the insert will not be saved. The <code>Connection</code> is + * closed after the call. + * + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.QueryExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public QueryExecutor query(String sql) throws SQLException { + return this.query(this.prepareConnection(), true, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.QueryExecutor} for the given SQL statement and connection. + * The connection is <b>NOT</b> closed after execution. + * + * @param conn The connection to use for the update call. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.QueryExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public QueryExecutor query(Connection conn, String sql) throws SQLException { + return this.query(conn, false, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.QueryExecutor} for the given SQL statement and connection. + * + * @param conn The connection to use for the query call. + * @param closeConn True if the connection should be closed, false otherwise. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.QueryExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public QueryExecutor query(Connection conn, boolean closeConn, String sql) throws SQLException { + if (conn == null) { + throw new SQLException("Null connection"); + } + + if (sql == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null SQL statement"); + } + + return new QueryExecutor(conn, sql, closeConn); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.UpdateExecutor} for the given SQL. + * <code>Connection</code> is retrieved from the <code>DataSource</code> + * set in the constructor. This <code>Connection</code> must be in + * auto-commit mode or the insert will not be saved. The <code>Connection</code> is + * closed after the call. + * + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.UpdateExecutor} for this SQL statement. + * @throws SQLException if a database access error occurs + */ + public UpdateExecutor update(String sql) throws SQLException { + return this.update(this.prepareConnection(), true, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.UpdateExecutor} for the given SQL statement and connection. + * The connection is <b>NOT</b> closed after execution. + * + * @param conn The connection to use for the update call. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.UpdateExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public UpdateExecutor update(Connection conn, String sql) throws SQLException { + return this.update(conn, false, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.UpdateExecutor} for the given SQL statement and connection. + * + * @param conn The connection to use for the update call. + * @param closeConn True if the connection should be closed, false otherwise. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.UpdateExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public UpdateExecutor update(Connection conn, boolean closeConn, String sql) throws SQLException { + if (conn == null) { + throw new SQLException("Null connection"); + } + + if (sql == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null SQL statement"); + } + + return new UpdateExecutor(conn, sql, closeConn); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.InsertExecutor} for the given SQL. + * <code>Connection</code> is retrieved from the <code>DataSource</code> + * set in the constructor. This <code>Connection</code> must be in + * auto-commit mode or the insert will not be saved. The <code>Connection</code> is + * closed after the call. + * + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.InsertExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public InsertExecutor insert(String sql) throws SQLException { + return insert(this.prepareConnection(), true, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.InsertExecutor} for the given SQL and connection + * The connection is <b>NOT</b> closed after execution. + * + * @param conn The connection to use for the query call. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.InsertExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public InsertExecutor insert(Connection conn, String sql) throws SQLException { + return insert(conn, false, sql); + } + + /** + * Creates an {@link org.apache.commons.dbutils2.InsertExecutor} for the given SQL and connection. + * + * @param conn The connection to use for the insert call. + * @param closeConn True if the connection should be closed, false otherwise. + * @param sql The SQL statement to execute. + * + * @return An {@link org.apache.commons.dbutils2.InsertExecutor} for this SQL statement. + * @throws SQLException If there are database or parameter errors. + */ + public InsertExecutor insert(Connection conn, boolean closeConn, String sql) throws SQLException { + if (conn == null) { + throw new SQLException("Null connection"); + } + + if (sql == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null SQL statement"); + } + + return new InsertExecutor(conn, sql, closeConn); + } +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/ResultSetHandler.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/ResultSetHandler.java b/src/main/java/org/apache/commons/dbutils2/ResultSetHandler.java new file mode 100644 index 0000000..f7ba0bd --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/ResultSetHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Implementations of this interface convert ResultSets into other objects. + * + * @param <T> the target type the input ResultSet will be converted to. + */ +public interface ResultSetHandler<T> { + + /** + * Turn the <code>ResultSet</code> into an Object. + * + * @param rs The <code>ResultSet</code> to handle. It has not been touched + * before being passed to this method. + * + * @return An Object initialized with <code>ResultSet</code> data. It is + * legal for implementations to return <code>null</code> if the + * <code>ResultSet</code> contained 0 rows. + * + * @throws SQLException if a database access error occurs + */ + T handle(ResultSet rs) throws SQLException; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/ResultSetIterator.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/ResultSetIterator.java b/src/main/java/org/apache/commons/dbutils2/ResultSetIterator.java new file mode 100644 index 0000000..ef05213 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/ResultSetIterator.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Iterator; + +/** + * <p> + * Wraps a <code>ResultSet</code> in an <code>Iterator<Object[]></code>. This is useful + * when you want to present a non-database application layer with domain + * neutral data. + * </p> + * + * <p> + * This implementation requires the <code>ResultSet.isLast()</code> method + * to be implemented. + * </p> + */ +public class ResultSetIterator implements Iterator<Object[]> { + + /** + * The wrapped <code>ResultSet</code>. + */ + private final ResultSet rs; + + /** + * The processor to use when converting a row into an Object[]. + */ + private final RowProcessor convert; + + /** + * Constructor for ResultSetIterator. + * @param rs Wrap this <code>ResultSet</code> in an <code>Iterator</code>. + */ + public ResultSetIterator(ResultSet rs) { + this(rs, new BasicRowProcessor()); + } + + /** + * Constructor for ResultSetIterator. + * @param rs Wrap this <code>ResultSet</code> in an <code>Iterator</code>. + * @param convert The processor to use when converting a row into an + * <code>Object[]</code>. Defaults to a + * <code>BasicRowProcessor</code>. + */ + public ResultSetIterator(ResultSet rs, RowProcessor convert) { + this.rs = rs; + this.convert = convert; + } + + /** + * Returns true if there are more rows in the ResultSet. + * @return boolean <code>true</code> if there are more rows + * @throws RuntimeException if an SQLException occurs. + */ + @Override + public boolean hasNext() { + try { + return !rs.isLast(); + } catch (SQLException e) { + rethrow(e); + return false; + } + } + + /** + * Returns the next row as an <code>Object[]</code>. + * @return An <code>Object[]</code> with the same number of elements as + * columns in the <code>ResultSet</code>. + * @see java.util.Iterator#next() + * @throws RuntimeException if an SQLException occurs. + */ + @Override + public Object[] next() { + try { + rs.next(); + return this.convert.toArray(rs); + } catch (SQLException e) { + rethrow(e); + return null; + } + } + + /** + * Deletes the current row from the <code>ResultSet</code>. + * @see java.util.Iterator#remove() + * @throws RuntimeException if an SQLException occurs. + */ + @Override + public void remove() { + try { + this.rs.deleteRow(); + } catch (SQLException e) { + rethrow(e); + } + } + + /** + * Rethrow the SQLException as a RuntimeException. This implementation + * creates a new RuntimeException with the SQLException's error message. + * @param e SQLException to rethrow + * @since DbUtils 1.1 + */ + protected void rethrow(SQLException e) { + throw new RuntimeException(e.getMessage()); + } + + /** + * Generates an <code>Iterable</code>, suitable for use in for-each loops. + * + * @param rs Wrap this <code>ResultSet</code> in an <code>Iterator</code>. + * @return an <code>Iterable</code>, suitable for use in for-each loops. + */ + public static Iterable<Object[]> iterable(final ResultSet rs) { + return new Iterable<Object[]>() { + + @Override + public Iterator<Object[]> iterator() { + return new ResultSetIterator(rs); + } + + }; + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/RowProcessor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/RowProcessor.java b/src/main/java/org/apache/commons/dbutils2/RowProcessor.java new file mode 100644 index 0000000..b896b01 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/RowProcessor.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * <code>RowProcessor</code> implementations convert + * <code>ResultSet</code> rows into various other objects. Implementations + * can extend <code>BasicRowProcessor</code> to protect themselves + * from changes to this interface. + * + * @see BasicRowProcessor + */ +public interface RowProcessor { + + /** + * Create an <code>Object[]</code> from the column values in one + * <code>ResultSet</code> row. The <code>ResultSet</code> should be + * positioned on a valid row before passing it to this method. + * Implementations of this method must not alter the row position of + * the <code>ResultSet</code>. + * + * @param rs ResultSet that supplies the array data + * @throws SQLException if a database access error occurs + * @return the newly created array + */ + Object[] toArray(ResultSet rs) throws SQLException; + + /** + * Create a JavaBean from the column values in one <code>ResultSet</code> + * row. The <code>ResultSet</code> should be positioned on a valid row before + * passing it to this method. Implementations of this method must not + * alter the row position of the <code>ResultSet</code>. + * @param <T> The type of bean to create + * @param rs ResultSet that supplies the bean data + * @param type Class from which to create the bean instance + * @throws SQLException if a database access error occurs + * @return the newly created bean + */ + <T> T toBean(ResultSet rs, Class<T> type) throws SQLException; + + /** + * Create a <code>List</code> of JavaBeans from the column values in all + * <code>ResultSet</code> rows. <code>ResultSet.next()</code> should + * <strong>not</strong> be called before passing it to this method. + * @param <T> The type of bean to create + * @param rs ResultSet that supplies the bean data + * @param type Class from which to create the bean instance + * @throws SQLException if a database access error occurs + * @return A <code>List</code> of beans with the given type in the order + * they were returned by the <code>ResultSet</code>. + */ + <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException; + + /** + * Create a <code>Map</code> from the column values in one + * <code>ResultSet</code> row. The <code>ResultSet</code> should be + * positioned on a valid row before + * passing it to this method. Implementations of this method must not + * alter the row position of the <code>ResultSet</code>. + * + * @param rs ResultSet that supplies the map data + * @throws SQLException if a database access error occurs + * @return the newly created Map + */ + Map<String, Object> toMap(ResultSet rs) throws SQLException; + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/UpdateExecutor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/UpdateExecutor.java b/src/main/java/org/apache/commons/dbutils2/UpdateExecutor.java new file mode 100644 index 0000000..590f069 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/UpdateExecutor.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2; + +import java.sql.Connection; +import java.sql.SQLException; + + +public class UpdateExecutor extends AbstractExecutor<UpdateExecutor> { + + private final boolean closeConn; + + public UpdateExecutor(final Connection conn, final String sql, final boolean closeConnection) throws SQLException { + super(conn, sql); + this.closeConn = closeConnection; + } + + /** + * Calls update after checking the parameters to ensure nothing is null. + * @return The number of rows updated. + * @throws SQLException If there are database or parameter errors. + */ + public int execute() throws SQLException { + // throw an exception if there are unmapped parameters + this.throwIfUnmappedParams(); + + try { + return getStatement().executeUpdate(); + } catch (SQLException e) { + this.rethrow(e); + + } finally { + close(getStatement()); + if (closeConn) { + close(getConnection()); + } + } + + // we get here only if something is thrown + return 0; + } + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/handlers/AbstractKeyedHandler.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/handlers/AbstractKeyedHandler.java b/src/main/java/org/apache/commons/dbutils2/handlers/AbstractKeyedHandler.java new file mode 100644 index 0000000..14e0b63 --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/handlers/AbstractKeyedHandler.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2.handlers; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.dbutils2.ResultSetHandler; + +/** + * <p> + * <code>ResultSetHandler</code> implementation that returns a Map. + * <code>ResultSet</code> rows are converted into objects (Vs) which are then stored + * in a Map under the given keys (Ks). + * </p> + * + * @param <K> the type of keys maintained by the returned map + * @param <V> the type of mapped values + * @see org.apache.commons.dbutils2.ResultSetHandler + * @since DbUtils 1.3 + */ +public abstract class AbstractKeyedHandler<K, V> implements ResultSetHandler<Map<K, V>> { + + + /** + * Convert each row's columns into a Map and store then + * in a <code>Map</code> under <code>ResultSet.getObject(key)</code> key. + * @param rs <code>ResultSet</code> to process. + * @return A <code>Map</code>, never <code>null</code>. + * @throws SQLException if a database access error occurs + * @see org.apache.commons.dbutils2.ResultSetHandler#handle(java.sql.ResultSet) + */ + @Override + public Map<K, V> handle(ResultSet rs) throws SQLException { + Map<K, V> result = createMap(); + while (rs.next()) { + result.put(createKey(rs), createRow(rs)); + } + return result; + } + + /** + * This factory method is called by <code>handle()</code> to create the Map + * to store records in. This implementation returns a <code>HashMap</code> + * instance. + * + * @return Map to store records in + */ + protected Map<K, V> createMap() { + return new HashMap<K, V>(); + } + + /** + * This factory method is called by <code>handle()</code> to retrieve the + * key value from the current <code>ResultSet</code> row. + * @param rs ResultSet to create a key from + * @return K from the configured key column name/index + * @throws SQLException if a database access error occurs + */ + protected abstract K createKey(ResultSet rs) throws SQLException; + + /** + * This factory method is called by <code>handle()</code> to store the + * current <code>ResultSet</code> row in some object. + * @param rs ResultSet to create a row from + * @return V object created from the current row + * @throws SQLException if a database access error occurs + */ + protected abstract V createRow(ResultSet rs) throws SQLException; + +} http://git-wip-us.apache.org/repos/asf/commons-dbutils/blob/41d6d58c/src/main/java/org/apache/commons/dbutils2/handlers/AbstractListHandler.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/dbutils2/handlers/AbstractListHandler.java b/src/main/java/org/apache/commons/dbutils2/handlers/AbstractListHandler.java new file mode 100644 index 0000000..8b4b68e --- /dev/null +++ b/src/main/java/org/apache/commons/dbutils2/handlers/AbstractListHandler.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.dbutils2.handlers; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.dbutils2.ResultSetHandler; + +/** + * Abstract class that simplify development of <code>ResultSetHandler</code> + * classes that convert <code>ResultSet</code> into <code>List</code>. + * + * @param <T> the target List generic type + * @see org.apache.commons.dbutils2.ResultSetHandler + */ +public abstract class AbstractListHandler<T> implements ResultSetHandler<List<T>> { + /** + * Whole <code>ResultSet</code> handler. It produce <code>List</code> as + * result. To convert individual rows into Java objects it uses + * <code>handleRow(ResultSet)</code> method. + * + * @see #handleRow(ResultSet) + * @param rs <code>ResultSet</code> to process. + * @return a list of all rows in the result set + * @throws SQLException error occurs + */ + @Override + public List<T> handle(ResultSet rs) throws SQLException { + List<T> rows = new ArrayList<T>(); + while (rs.next()) { + rows.add(this.handleRow(rs)); + } + return rows; + } + + /** + * Row handler. Method converts current row into some Java object. + * + * @param rs <code>ResultSet</code> to process. + * @return row processing result + * @throws SQLException error occurs + */ + protected abstract T handleRow(ResultSet rs) throws SQLException; +}
