You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ws...@apache.org on 2020/01/09 02:51:10 UTC

[commons-dbutils] branch 2_0 updated: Merged in changes from master

This is an automated email from the ASF dual-hosted git repository.

wspeirs pushed a commit to branch 2_0
in repository https://gitbox.apache.org/repos/asf/commons-dbutils.git


The following commit(s) were added to refs/heads/2_0 by this push:
     new 31deec7  Merged in changes from master
31deec7 is described below

commit 31deec7a232bacb1204c86788939afaac101b0ef
Author: William Speirs <bi...@gmail.com>
AuthorDate: Wed Jan 8 21:50:24 2020 -0500

    Merged in changes from master
---
 .../commons/dbutils2/BaseResultSetHandler.java     |   4 +
 .../apache/commons/dbutils2/BasicRowProcessor.java |  73 +++---
 .../org/apache/commons/dbutils2/BeanProcessor.java | 270 ++++++++++++---------
 .../java/org/apache/commons/dbutils2/DbUtils.java  |   5 +-
 .../commons/dbutils2/GenerousBeanProcessor.java    |   7 +
 .../org/apache/commons/dbutils2/QueryLoader.java   |  13 +-
 .../dbutils2/wrappers/SqlNullCheckedResultSet.java |  25 +-
 7 files changed, 232 insertions(+), 165 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbutils2/BaseResultSetHandler.java b/src/main/java/org/apache/commons/dbutils2/BaseResultSetHandler.java
index 854e69e..2e9201b 100644
--- a/src/main/java/org/apache/commons/dbutils2/BaseResultSetHandler.java
+++ b/src/main/java/org/apache/commons/dbutils2/BaseResultSetHandler.java
@@ -58,6 +58,9 @@ public abstract class BaseResultSetHandler<T> implements ResultSetHandler<T> {
      */
     private ResultSet rs;
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public final T handle(ResultSet rs) throws SQLException {
         if (this.rs != null) {
@@ -78,6 +81,7 @@ public abstract class BaseResultSetHandler<T> implements ResultSetHandler<T> {
      *
      * @return An Object initialized with <code>ResultSet</code> data
      * @throws SQLException if a database access error occurs
+     * @see ResultSetHandler#handle(ResultSet)
      */
     protected abstract T handle() throws SQLException;
 
diff --git a/src/main/java/org/apache/commons/dbutils2/BasicRowProcessor.java b/src/main/java/org/apache/commons/dbutils2/BasicRowProcessor.java
index b4164c3..cabb494 100644
--- a/src/main/java/org/apache/commons/dbutils2/BasicRowProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils2/BasicRowProcessor.java
@@ -125,26 +125,30 @@ public class BasicRowProcessor implements RowProcessor {
     }
 
     /**
-     * Convert a <code>ResultSet</code> row into a <code>Map</code>.  This
-     * implementation returns a <code>Map</code> with case insensitive column
-     * names as keys.  Calls to <code>map.get("COL")</code> and
-     * <code>map.get("col")</code> return the same value.
-     * 
-     * @see org.apache.commons.dbutils2.RowProcessor#toMap(java.sql.ResultSet)
+     * Convert a {@code ResultSet} row into a {@code Map}.
+     *
+     * <p>
+     * This implementation returns a {@code Map} with case insensitive column names as keys. Calls to
+     * {@code map.get("COL")} and {@code map.get("col")} return the same value. Furthermore this implementation
+     * will return an ordered map, that preserves the ordering of the columns in the ResultSet, so that iterating over
+     * the entry set of the returned map will return the first column of the ResultSet, then the second and so forth.
+     * </p>
+     *
      * @param rs ResultSet that supplies the map data
-     * @throws SQLException if a database access error occurs
      * @return the newly created Map
+     * @throws SQLException if a database access error occurs
+     * @see org.apache.commons.dbutils.RowProcessor#toMap(java.sql.ResultSet)
      */
     @Override
-    public Map<String, Object> toMap(ResultSet rs) throws SQLException {
-        Map<String, Object> result = new CaseInsensitiveHashMap();
-        ResultSetMetaData rsmd = rs.getMetaData();
-        int cols = rsmd.getColumnCount();
+    public Map<String, Object> toMap(final ResultSet rs) throws SQLException {
+        final ResultSetMetaData rsmd = rs.getMetaData();
+        final int cols = rsmd.getColumnCount();
+        final Map<String, Object> result = createCaseInsensitiveHashMap(cols);
 
         for (int i = 1; i <= cols; i++) {
             String columnName = rsmd.getColumnLabel(i);
             if (null == columnName || 0 == columnName.length()) {
-                columnName = rsmd.getColumnName(i);
+              columnName = rsmd.getColumnName(i);
             }
             result.put(columnName, rs.getObject(i));
         }
@@ -152,6 +156,7 @@ public class BasicRowProcessor implements RowProcessor {
         return result;
     }
 
+
     /**
      * A Map that converts all keys to lowercase Strings for case insensitive
      * lookups.  This is needed for the toMap() implementation because
@@ -161,13 +166,18 @@ public class BasicRowProcessor implements RowProcessor {
      * an internal mapping from lowercase keys to the real keys in order to
      * achieve the case insensitive lookup.
      *
-     * <p>Note: This implementation does not allow <tt>null</tt>
-     * for key, whereas {@link HashMap} does, because of the code:
+     * <p>Note: This implementation does not allow {@code null}
+     * for key, whereas {@link LinkedHashMap} does, because of the code:
      * <pre>
      * key.toString().toLowerCase()
      * </pre>
      */
-    private static class CaseInsensitiveHashMap extends HashMap<String, Object> {
+    private static final class CaseInsensitiveHashMap extends LinkedHashMap<String, Object> {
+
+        private CaseInsensitiveHashMap(final int initialCapacity) {
+            super(initialCapacity);
+        }
+
         /**
          * The internal mapping from lowercase keys to the real keys.
          *
@@ -182,7 +192,7 @@ public class BasicRowProcessor implements RowProcessor {
          * </ul>
          * </p>
          */
-        private final Map<String, String> lowerCaseMap = new HashMap<String, String>();
+        private final Map<String, String> lowerCaseMap = new HashMap<>();
 
         /**
          * Required for serialization support.
@@ -191,9 +201,10 @@ public class BasicRowProcessor implements RowProcessor {
          */
         private static final long serialVersionUID = -2848100435296897392L;
 
+        /** {@inheritDoc} */
         @Override
-        public boolean containsKey(Object key) {
-            Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
+        public boolean containsKey(final Object key) {
+            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
             return super.containsKey(realKey);
             // Possible optimisation here:
             // Since the lowerCaseMap contains a mapping for all the keys,
@@ -201,14 +212,16 @@ public class BasicRowProcessor implements RowProcessor {
             // return lowerCaseMap.containsKey(key.toString().toLowerCase());
         }
 
+        /** {@inheritDoc} */
         @Override
-        public Object get(Object key) {
-            Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
+        public Object get(final Object key) {
+            final Object realKey = lowerCaseMap.get(key.toString().toLowerCase(Locale.ENGLISH));
             return super.get(realKey);
         }
 
+        /** {@inheritDoc} */
         @Override
-        public Object put(String key, Object value) {
+        public Object put(final String key, final Object value) {
             /*
              * In order to keep the map and lowerCaseMap synchronized,
              * we have to remove the old mapping before putting the
@@ -216,24 +229,26 @@ public class BasicRowProcessor implements RowProcessor {
              * (That's why we call super.remove(oldKey) and not just
              * super.put(key, value))
              */
-            Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key);
-            Object oldValue = super.remove(oldKey);
+            final Object oldKey = lowerCaseMap.put(key.toLowerCase(Locale.ENGLISH), key);
+            final Object oldValue = super.remove(oldKey);
             super.put(key, value);
             return oldValue;
         }
 
+        /** {@inheritDoc} */
         @Override
-        public void putAll(Map<? extends String, ?> m) {
-            for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
-                String key = entry.getKey();
-                Object value = entry.getValue();
+        public void putAll(final Map<? extends String, ?> m) {
+            for (final Map.Entry<? extends String, ?> entry : m.entrySet()) {
+                final String key = entry.getKey();
+                final Object value = entry.getValue();
                 this.put(key, value);
             }
         }
 
+        /** {@inheritDoc} */
         @Override
-        public Object remove(Object key) {
-            Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH));
+        public Object remove(final Object key) {
+            final Object realKey = lowerCaseMap.remove(key.toString().toLowerCase(Locale.ENGLISH));
             return super.remove(realKey);
         }
     }
diff --git a/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java b/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java
index 4536e78..fe7fc39 100644
--- a/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils2/BeanProcessor.java
@@ -20,6 +20,7 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.sql.ResultSet;
@@ -32,6 +33,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 
 /**
  * <p>
@@ -63,7 +65,11 @@ public class BeanProcessor {
      * 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>();
+    private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<>();
+
+    private static final List<ColumnHandler> columnHandlers = new ArrayList<>();
+
+    private static final List<PropertyHandler> propertyHandlers = new ArrayList<>();
 
     /**
      * ResultSet column to bean property name overrides.
@@ -79,6 +85,16 @@ public class BeanProcessor {
         primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
         primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
         primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
+
+        // Use a ServiceLoader to find implementations
+        for (final ColumnHandler handler : ServiceLoader.load(ColumnHandler.class)) {
+            columnHandlers.add(handler);
+        }
+
+        // Use a ServiceLoader to find implementations
+        for (final PropertyHandler handler : ServiceLoader.load(PropertyHandler.class)) {
+            propertyHandlers.add(handler);
+        }
     }
 
     /**
@@ -136,14 +152,9 @@ public class BeanProcessor {
      * @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);
+    public <T> T toBean(final ResultSet rs, final Class<? extends T> type) throws SQLException {
+        final T bean = this.newInstance(type);
+        return this.populateBean(rs, bean);
     }
 
     /**
@@ -209,8 +220,43 @@ public class BeanProcessor {
      * @return An initialized object.
      * @throws SQLException if a database error occurs.
      */
-    private <T> T createBean(ResultSet rs, Class<T> type,
-            PropertyDescriptor[] props, int[] columnToProperty)
+    private <T> T createBean(final ResultSet rs, final Class<T> type,
+                             final PropertyDescriptor[] props, final int[] columnToProperty)
+    throws SQLException {
+
+        final T bean = this.newInstance(type);
+        return populateBean(rs, bean, props, columnToProperty);
+    }
+
+    /**
+     * Initializes the fields of the provided bean from the ResultSet.
+     * @param <T> The type of bean
+     * @param rs The result set.
+     * @param bean The bean to be populated.
+     * @return An initialized object.
+     * @throws SQLException if a database error occurs.
+     */
+    public <T> T populateBean(final ResultSet rs, final T bean) throws SQLException {
+        final PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
+        final ResultSetMetaData rsmd = rs.getMetaData();
+        final int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
+
+        return populateBean(rs, bean, props, columnToProperty);
+    }
+
+    /**
+     * This method populates a bean from the ResultSet based upon the underlying meta-data.
+     *
+     * @param <T> The type of bean
+     * @param rs The result set.
+     * @param bean The bean to be populated.
+     * @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 populateBean(final ResultSet rs, final T bean,
+            final PropertyDescriptor[] props, final int[] columnToProperty)
             throws SQLException {
 
         T bean = this.newInstance(type);
@@ -221,13 +267,16 @@ public class BeanProcessor {
                 continue;
             }
 
-            PropertyDescriptor prop = props[columnToProperty[i]];
-            Class<?> propType = prop.getPropertyType();
+            final PropertyDescriptor prop = props[columnToProperty[i]];
+            final Class<?> propType = prop.getPropertyType();
 
-            Object value = this.processColumn(rs, i, propType);
+            Object value = null;
+            if (propType != null) {
+                value = this.processColumn(rs, i, propType);
 
-            if (propType != null && value == null && propType.isPrimitive()) {
-                value = primitiveDefaults.get(propType);
+                if (value == null && propType.isPrimitive()) {
+                    value = primitiveDefaults.get(propType);
+                }
             }
 
             this.callSetter(bean, prop, value);
@@ -248,58 +297,40 @@ public class BeanProcessor {
     private void callSetter(Object target, PropertyDescriptor prop, Object value)
             throws SQLException {
 
-        final Method setter = prop.getWriteMethod();
+        final Method setter = getWriteMethod(target, prop, value);
 
-        if (setter == null) {
+        if (setter == null || setter.getParameterTypes().length != 1) {
             return;
         }
 
-        final 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());
+            final Class<?> firstParam = setter.getParameterTypes()[0];
+            for (final PropertyHandler handler : propertyHandlers) {
+                if (handler.match(firstParam, value)) {
+                    value = handler.apply(firstParam, value);
+                    break;
                 }
             }
 
-            // see if we can make it work with an enum
-
-            if(params[0].isEnum() && value != null) {
-                try {
-                    final Class cz = Class.forName(params[0].getName());
-                    setter.invoke(target, Enum.valueOf(cz, (String) value));
-                } catch(final ClassNotFoundException e) {
-                    throw new SQLException("Attempted to set an Enum, but class "
-                            + params[0].getName() + " was not found: " + e.getMessage());
-                }
-            } else if (this.isCompatibleType(value, params[0])) {
-                // Don't call setter if the value object isn't the right type
-                setter.invoke(target, new Object[]{value});
+            // Don't call setter if the value object isn't the right type
+            if (this.isCompatibleType(value, firstParam)) {
+                setter.invoke(target, value);
             } else {
               throw new SQLException(
                   "Cannot set " + prop.getName() + ": incompatible types, cannot convert "
-                  + value.getClass().getName() + " to " + params[0].getName());
+                  + value.getClass().getName() + " to " + firstParam.getName());
                   // value cannot be null here because isCompatibleType allows null
             }
 
-        } catch (IllegalArgumentException e) {
+        } catch (final IllegalArgumentException e) {
             throw new SQLException(
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
 
-        } catch (IllegalAccessException e) {
+        } catch (final IllegalAccessException e) {
             throw new SQLException(
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
 
-        } catch (InvocationTargetException e) {
+        } catch (final InvocationTargetException e) {
             throw new SQLException(
                 "Cannot set " + prop.getName() + ": " + e.getMessage());
         }
@@ -318,36 +349,56 @@ public class BeanProcessor {
      */
     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) && value instanceof Integer) {
-            return true;
-
-        } else if (type.equals(Long.TYPE) && value instanceof Long) {
-            return true;
-
-        } else if (type.equals(Double.TYPE) && value instanceof Double) {
-            return true;
-
-        } else if (type.equals(Float.TYPE) && value instanceof Float) {
+        if (value == null || type.isInstance(value) || matchesPrimitive(type, value.getClass())) {
             return true;
 
-        } else if (type.equals(Short.TYPE) && value instanceof Short) {
-            return true;
+        }
+        return false;
 
-        } else if (type.equals(Byte.TYPE) && value instanceof Byte) {
-            return true;
+    }
 
-        } else if (type.equals(Character.TYPE) && value instanceof Character) {
-            return true;
+    /**
+     * Check whether a value is of the same primitive type as {@code targetType}.
+     *
+     * @param targetType The primitive type to target.
+     * @param valueType The value to match to the primitive type.
+     * @return Whether {@code valueType} can be coerced (e.g. autoboxed) into {@code targetType}.
+     */
+    private boolean matchesPrimitive(final Class<?> targetType, final Class<?> valueType) {
+        if (!targetType.isPrimitive()) {
+            return false;
+        }
 
-        } else if (type.equals(Boolean.TYPE) && value instanceof Boolean) {
-            return true;
+        try {
+            // see if there is a "TYPE" field.  This is present for primitive wrappers.
+            final Field typeField = valueType.getField("TYPE");
+            final Object primitiveValueType = typeField.get(valueType);
 
+            if (targetType == primitiveValueType) {
+                return true;
+            }
+        } catch (final NoSuchFieldException e) {
+            // lacking the TYPE field is a good sign that we're not working with a primitive wrapper.
+            // we can't match for compatibility
+        } catch (final IllegalAccessException e) {
+            // an inaccessible TYPE field is a good sign that we're not working with a primitive wrapper.
+            // nothing to do.  we can't match for compatibility
         }
         return false;
+    }
 
+    /**
+     * Get the write method to use when setting {@code value} to the {@code target}.
+     *
+     * @param target Object where the write method will be called.
+     * @param prop   BeanUtils information.
+     * @param value  The value that will be passed to the write method.
+     * @return The {@link java.lang.reflect.Method} to call on {@code target} to write {@code value} or {@code null} if
+     *         there is no suitable write method.
+     */
+    protected Method getWriteMethod(final Object target, final PropertyDescriptor prop, final Object value) {
+        final Method method = prop.getWriteMethod();
+        return method;
     }
 
     /**
@@ -363,13 +414,11 @@ public class BeanProcessor {
      */
     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());
+            return c.getDeclaredConstructor().newInstance();
 
-        } catch (IllegalAccessException e) {
-            throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage());
+        } catch (final IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
+            throw new SQLException(
+                "Cannot create " + c.getName() + ": " + e.getMessage());
         }
     }
 
@@ -382,12 +431,13 @@ public class BeanProcessor {
      */
     private PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException {
         // Introspector caches BeanInfo classes for better performance
-        BeanInfo beanInfo;
+        BeanInfo beanInfo = null;
         try {
             beanInfo = Introspector.getBeanInfo(c);
 
-        } catch (IntrospectionException e) {
-            throw new SQLException("Bean introspection failed: " + e.getMessage());
+        } catch (final IntrospectionException e) {
+            throw new SQLException(
+                "Bean introspection failed: " + e.getMessage());
         }
 
         return beanInfo.getPropertyDescriptors();
@@ -411,8 +461,8 @@ public class BeanProcessor {
     protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
             PropertyDescriptor[] props) throws SQLException {
 
-        int cols = rsmd.getColumnCount();
-        int[] columnToProperty = new int[cols + 1];
+        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++) {
@@ -426,7 +476,15 @@ public class BeanProcessor {
             }
             for (int i = 0; i < props.length; i++) {
 
-                if (propertyName.equalsIgnoreCase(props[i].getName())) {
+                PropertyDescriptor prop = props[i];
+                Column column = prop.getReadMethod().getAnnotation(Column.class);
+                String propertyColumnName = null;
+                if (column != null) {
+                    propertyColumnName = column.name();
+                } else {
+                    propertyColumnName = prop.getName();
+                }
+                if (propertyName.equalsIgnoreCase(propertyColumnName)) {
                     columnToProperty[col] = i;
                     break;
                 }
@@ -463,52 +521,24 @@ public class BeanProcessor {
      * 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)
+    protected Object processColumn(final ResultSet rs, final int index, final Class<?> propType)
         throws SQLException {
 
-        if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
+        Object retval = rs.getObject(index);
+
+        if ( !propType.isPrimitive() && retval == 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);
+        for (final ColumnHandler handler : columnHandlers) {
+            if (handler.match(propType)) {
+                retval = handler.apply(rs, index);
+                break;
+            }
         }
 
+        return retval;
+
     }
 
 }
diff --git a/src/main/java/org/apache/commons/dbutils2/DbUtils.java b/src/main/java/org/apache/commons/dbutils2/DbUtils.java
index 530ddb9..f0c16d1 100644
--- a/src/main/java/org/apache/commons/dbutils2/DbUtils.java
+++ b/src/main/java/org/apache/commons/dbutils2/DbUtils.java
@@ -211,7 +211,10 @@ public final class DbUtils {
             Constructor<Driver> driverConstructor = driverClass.getConstructor();
 
             // make Constructor accessible if it is private
-            boolean isConstructorAccessible = driverConstructor.isAccessible();
+            @SuppressWarnings("deprecation")
+            // TODO This is deprecated in Java9 and canAccess() should be used. Adding suppression for building on
+            //      later JDKs without a warning.
+            final boolean isConstructorAccessible = driverConstructor.isAccessible();
             if (!isConstructorAccessible) {
                 driverConstructor.setAccessible(true);
             }
diff --git a/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java b/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java
index 3c03765..67e4962 100644
--- a/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java
+++ b/src/main/java/org/apache/commons/dbutils2/GenerousBeanProcessor.java
@@ -28,6 +28,13 @@ import java.util.Arrays;
  */
 public class GenerousBeanProcessor extends BeanProcessor {
 
+    /**
+     * Default constructor.
+     */
+    public GenerousBeanProcessor() {
+        super();
+    }
+
     @Override
     protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,
             final PropertyDescriptor[] props) throws SQLException {
diff --git a/src/main/java/org/apache/commons/dbutils2/QueryLoader.java b/src/main/java/org/apache/commons/dbutils2/QueryLoader.java
index a9a1c74..4d6e3f2 100644
--- a/src/main/java/org/apache/commons/dbutils2/QueryLoader.java
+++ b/src/main/java/org/apache/commons/dbutils2/QueryLoader.java
@@ -112,14 +112,13 @@ public class QueryLoader {
      */
     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);
+        final Properties props;
+        try (InputStream in = getClass().getResourceAsStream(path)) {
 
-        if (in == null) {
-            throw new IllegalArgumentException(path + " not found.");
-        }
-
-        Properties props = new Properties();
-        try {
+            if (in == null) {
+                throw new IllegalArgumentException(path + " not found.");
+            }
+            props = new Properties();
             if (dotXml.matcher(path).matches()) {
                 props.loadFromXML(in);
             } else {
diff --git a/src/main/java/org/apache/commons/dbutils2/wrappers/SqlNullCheckedResultSet.java b/src/main/java/org/apache/commons/dbutils2/wrappers/SqlNullCheckedResultSet.java
index 86a3799..d183e99 100644
--- a/src/main/java/org/apache/commons/dbutils2/wrappers/SqlNullCheckedResultSet.java
+++ b/src/main/java/org/apache/commons/dbutils2/wrappers/SqlNullCheckedResultSet.java
@@ -458,10 +458,14 @@ public class SqlNullCheckedResultSet implements InvocationHandler {
      *
      * @param nullBytes the value
      */
-    public void setNullBytes(byte[] nullBytes) {
-        byte[] copy = new byte[nullBytes.length];
-        System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
-        this.nullBytes = copy;
+    public void setNullBytes(final byte[] nullBytes) {
+        if (nullBytes != null) {
+            final byte[] copy = new byte[nullBytes.length];
+            System.arraycopy(nullBytes, 0, copy, 0, nullBytes.length);
+            this.nullBytes = copy;
+        } else {
+            this.nullBytes = null;
+        }
     }
 
     /**
@@ -580,8 +584,8 @@ public class SqlNullCheckedResultSet implements InvocationHandler {
      *
      * @param nullTime the value
      */
-    public void setNullTime(Time nullTime) {
-        this.nullTime = nullTime;
+    public void setNullTime(final Time nullTime) {
+        this.nullTime = nullTime != null ? new Time(nullTime.getTime()) : null;
     }
 
     /**
@@ -590,8 +594,13 @@ public class SqlNullCheckedResultSet implements InvocationHandler {
      *
      * @param nullTimestamp the value
      */
-    public void setNullTimestamp(Timestamp nullTimestamp) {
-        this.nullTimestamp = nullTimestamp != null ? new Timestamp(nullTimestamp.getTime()) : null;
+    public void setNullTimestamp(final Timestamp nullTimestamp) {
+        if (nullTimestamp != null) {
+            this.nullTimestamp = new Timestamp(nullTimestamp.getTime());
+            this.nullTimestamp.setNanos(nullTimestamp.getNanos());
+        } else {
+            this.nullTimestamp = null;
+        }
     }
 
     /**