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 <bill.spe...@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; + } } /**