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;
+ }
}
/**