Major refactor, more tests

This commit is contained in:
Travis Burtrum 2017-05-18 14:26:23 -04:00
parent e7639d740c
commit efd1d44808
17 changed files with 633 additions and 382 deletions

View File

@ -0,0 +1,94 @@
/*
* 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.
*
* $Header:$
*/
package com.moparisthebest.jdbc;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Calendar;
/**
* Abstract base class for all row mappers.
*
* RowMappers are used to map the contents of a row in a ResultSet to the return type of an annotated method.
* Supported RowMapper types include: HashMap, Map, Object, XmlObject. When a ResultSetMapper is ready to
* map a ResultSet row to an object, it requests a RowMapper for the return type of the method from the
* RowMapperFactory.
*
*/
public abstract class AbstractRowMapper<K, T> implements RowMapper<K,T> {
/** ResultSet to map. */
protected final ResultSet _resultSet;
/** Calendar instance for date/time mappings. */
protected final Calendar _cal;
/** Class to map ResultSet Rows to. */
protected final Class<T> _returnTypeClass;
protected final Class<K> _mapKeyType;
protected final int _columnCount;
protected final boolean mapOnlySecondColumn;
/**
* Create a new RowMapper for the specified ResultSet and return type Class.
* @param resultSet ResultSet to map
* @param returnTypeClass Class to map ResultSet rows to.
* @param cal Calendar instance for date/time values.
*/
protected AbstractRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
_resultSet = resultSet;
_returnTypeClass = returnTypeClass;
_cal = cal;
_mapKeyType = mapKeyType;
try {
_columnCount = resultSet.getMetaData().getColumnCount();
} catch (SQLException e) {
throw new MapperException("RowToObjectMapper: SQLException: " + e.getMessage(), e);
}
mapOnlySecondColumn = _mapKeyType != null && _columnCount == 2;
}
protected AbstractRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal) {
this(resultSet, returnTypeClass, cal, null);
}
/**
* Build a String array of column names from the ResultSet.
* @return A String array containing the column names contained within the ResultSet.
* @throws java.sql.SQLException on error
*/
protected String[] getKeysFromResultSet() throws SQLException {
String[] keys;
final ResultSetMetaData md = _resultSet.getMetaData();
keys = new String[_columnCount + 1];
for (int i = 1; i <= _columnCount; i++) {
keys[i] = md.getColumnName(i).toUpperCase();
}
return keys;
}
}

View File

@ -132,7 +132,7 @@ public class CachingResultSetMapper extends ResultSetMapper {
}
@Override
protected <K, T> RowToObjectMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
public <K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new CachingRowToObjectMapper<K, T>(cache, resultSet, returnTypeClass, cal, mapValType, mapKeyType);
}
}

View File

@ -15,7 +15,7 @@ public class CaseInsensitiveMapResultSetMapper extends ResultSetMapper {
}
@Override
protected <K, T> RowToObjectMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new CaseInsensitiveMapRowToObjectMapper<K, T>(resultSet, returnTypeClass, cal, mapValType, mapKeyType);
public <K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new RowToObjectMapper<K, T>(resultSet, returnTypeClass, cal, mapValType, mapKeyType, true);
}
}

View File

@ -1,48 +0,0 @@
package com.moparisthebest.jdbc;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
/**
* Created by mopar on 5/15/14.
*/
public class CaseInsensitiveMapRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
public CaseInsensitiveMapRowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass) {
super(resultSet, returnTypeClass);
}
public CaseInsensitiveMapRowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Class<?> mapValType) {
super(resultSet, returnTypeClass, mapValType);
}
public CaseInsensitiveMapRowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal) {
super(resultSet, returnTypeClass, cal);
}
public CaseInsensitiveMapRowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType) {
super(resultSet, returnTypeClass, cal, mapValType);
}
public CaseInsensitiveMapRowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
super(resultSet, returnTypeClass, cal, mapValType, mapKeyType);
}
@Override
protected Map<String, Object> getMapImplementation() throws IllegalAccessException, InstantiationException {
if(HashMap.class.equals(_returnTypeClass))
return new HashMap<String, Object>(){
@Override
public Object get(Object key) {
return super.get(key instanceof String ? ((String)key).toLowerCase() : key);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(key instanceof String ? ((String)key).toLowerCase() : key);
}
};
return super.getMapImplementation();
}
}

View File

@ -0,0 +1,68 @@
package com.moparisthebest.jdbc;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.Map;
/**
* Created by mopar on 5/18/17.
*/
public class CleaningCachingResultSetMapper<E> extends CachingResultSetMapper {
private final Cleaner<E> cleaner;
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final int maxEntries) {
super(cal, arrayMaxLength, maxEntries);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength) {
super(cal, arrayMaxLength);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength) {
super(arrayMaxLength);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner) {
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final Map<CachingRowToObjectMapper.ResultSetKey, CachingRowToObjectMapper.FieldMapping<?>> cache) {
super(cal, arrayMaxLength, cache);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength, final Map<CachingRowToObjectMapper.ResultSetKey, CachingRowToObjectMapper.FieldMapping<?>> cache) {
super(arrayMaxLength, cache);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final Map<CachingRowToObjectMapper.ResultSetKey, CachingRowToObjectMapper.FieldMapping<?>> cache) {
super(cache);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final boolean threadSafe) {
super(cal, arrayMaxLength, threadSafe);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength, final boolean threadSafe) {
super(arrayMaxLength, threadSafe);
this.cleaner = cleaner;
}
public CleaningCachingResultSetMapper(final Cleaner<E> cleaner, final boolean threadSafe) {
super(threadSafe);
this.cleaner = cleaner;
}
@Override
@SuppressWarnings({"unchecked"})
public <K, T> RowMapper<K, T> getRowMapper(final ResultSet resultSet, final Class<T> returnTypeClass, final Calendar cal, final Class<?> mapValType, final Class<K> mapKeyType) {
return new CleaningRowToObjectMapper((Cleaner<T>)cleaner, super.getRowMapper(resultSet, returnTypeClass, cal, mapValType, mapKeyType));
}
}

View File

@ -0,0 +1,68 @@
package com.moparisthebest.jdbc;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.Map;
/**
* Created by mopar on 5/18/17.
*/
public class CleaningCompilingResultSetMapper<E> extends CompilingResultSetMapper {
private final Cleaner<E> cleaner;
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final int maxEntries) {
super(cal, arrayMaxLength, maxEntries);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength) {
super(cal, arrayMaxLength);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength) {
super(arrayMaxLength);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner) {
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final Map<CachingRowToObjectMapper.ResultSetKey, CompilingRowToObjectMapper.ResultSetToObject<?, ?>> cache) {
super(cal, arrayMaxLength, cache);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength, final Map<CachingRowToObjectMapper.ResultSetKey, CompilingRowToObjectMapper.ResultSetToObject<?, ?>> cache) {
super(arrayMaxLength, cache);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final Map<CachingRowToObjectMapper.ResultSetKey, CompilingRowToObjectMapper.ResultSetToObject<?, ?>> cache) {
super(cache);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final Calendar cal, final int arrayMaxLength, final boolean threadSafe) {
super(cal, arrayMaxLength, threadSafe);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final int arrayMaxLength, final boolean threadSafe) {
super(arrayMaxLength, threadSafe);
this.cleaner = cleaner;
}
public CleaningCompilingResultSetMapper(final Cleaner<E> cleaner, final boolean threadSafe) {
super(threadSafe);
this.cleaner = cleaner;
}
@Override
@SuppressWarnings({"unchecked"})
public <K, T> RowMapper<K, T> getRowMapper(final ResultSet resultSet, final Class<T> returnTypeClass, final Calendar cal, final Class<?> mapValType, final Class<K> mapKeyType) {
return new CleaningRowToObjectMapper((Cleaner<T>)cleaner, super.getRowMapper(resultSet, returnTypeClass, cal, mapValType, mapKeyType));
}
}

View File

@ -19,7 +19,7 @@ public class CleaningResultSetMapper<E> extends ResultSetMapper {
@Override
@SuppressWarnings({"unchecked"})
protected <K, T> RowToObjectMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
public <K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new CleaningRowToObjectMapper<K, T>((Cleaner<T>)cleaner, resultSet, returnTypeClass, cal, mapValType, mapKeyType);
}
}

View File

@ -4,19 +4,27 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
public class CleaningRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
public class CleaningRowToObjectMapper<K, T> implements RowMapper<K, T> {
private final Cleaner<T> cleaner;
private final RowMapper<K,T> delegate;
public CleaningRowToObjectMapper(Cleaner<T> cleaner, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
super(resultSet, returnTypeClass, cal, mapValType, mapKeyType);
if (cleaner == null)
throw new NullPointerException("cleaner cannot be null!");
this(cleaner, new RowToObjectMapper<K, T>(resultSet, returnTypeClass, cal, mapValType, mapKeyType));
}
public CleaningRowToObjectMapper(final Cleaner<T> cleaner, final RowMapper<K,T> delegate) {
this.cleaner = cleaner;
this.delegate = delegate;
}
@Override
public T mapRowToReturnType() throws SQLException {
return cleaner.clean(super.mapRowToReturnType());
return cleaner.clean(delegate.mapRowToReturnType());
}
@Override
public K getMapKey() throws SQLException {
return delegate.getMapKey();
}
}

View File

@ -144,7 +144,7 @@ public class CompilingResultSetMapper extends ResultSetMapper {
}
@Override
protected <K, T> RowToObjectMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
public <K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new CompilingRowToObjectMapper<K, T>(compiler, cache, resultSet, returnTypeClass, cal, mapValType, mapKeyType);
}
}

View File

@ -21,13 +21,10 @@ import java.util.Map;
* Usage differences:
* 1. Reflection can set non-public or final fields directly, direct java code cannot, so DTOs like that will result in
* a compilation and therefore mapping error.
* <p>
* Subclass differences:
* 1. Normally a subclass of RowToObjectMapper can overload the getMapImplementation() method to change some behavior,
* @see CaseInsensitiveMapRowToObjectMapper , but that method is never called with this implementation.
*/
*/
public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
// do not remove, used from generated classes
public static final String firstColumnError = "Cannot call getFirstColumn when mapKeyType is null!";
protected final Compiler compiler;
@ -36,7 +33,11 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
protected String[] keys = null; // for caching if we must generate class
public CompilingRowToObjectMapper(final Compiler compiler, final Map<CachingRowToObjectMapper.ResultSetKey, ResultSetToObject<?,?>> cache, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
super(resultSet, returnTypeClass, cal, mapValType, mapKeyType);
this(compiler, cache, resultSet, returnTypeClass, cal, mapValType, mapKeyType, false);
}
public CompilingRowToObjectMapper(final Compiler compiler, final Map<CachingRowToObjectMapper.ResultSetKey, ResultSetToObject<?,?>> cache, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType, final boolean caseInsensitiveMap) {
super(resultSet, returnTypeClass, cal, mapValType, mapKeyType, caseInsensitiveMap);
this.compiler = compiler;
try {
final CachingRowToObjectMapper.ResultSetKey keys = new CachingRowToObjectMapper.ResultSetKey(super.getKeysFromResultSet(), _returnTypeClass, _mapKeyType);
@ -172,7 +173,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
}
java.append(footer);
System.out.println(java);
//System.out.println(java);
return compiler.compile(className, java);
}

View File

@ -53,7 +53,7 @@ import java.util.concurrent.*;
*
* @author Travis Burtrum (modifications from beehive)
*/
public class ResultSetMapper {
public class ResultSetMapper implements RowMapperProvider {
public static final Map<Class, Class> interfaceToConcrete = Collections.unmodifiableMap(new HashMap<Class, Class>() {{
// Collection's
@ -151,7 +151,7 @@ public class ResultSetMapper {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper<?, E> rowMapper = getRowMapper(rs, componentType, cal, mapValType, null);
final RowMapper<?, E> rowMapper = getRowMapper(rs, componentType, cal, mapValType, null);
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
E object = rowMapper.mapRowToReturnType();
@ -230,7 +230,7 @@ public class ResultSetMapper {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper<K, E> rowMapper = getRowMapper(rs, componentType, cal, mapValType, mapKeyType);
final RowMapper<K, E> rowMapper = getRowMapper(rs, componentType, cal, mapValType, mapKeyType);
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
K key = rowMapper.getMapKey();
@ -311,7 +311,7 @@ public class ResultSetMapper {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper<K, C> rowMapper = getRowMapper(rs, componentType, cal, mapValType, mapKeyType);
final RowMapper<K, C> rowMapper = getRowMapper(rs, componentType, cal, mapValType, mapKeyType);
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
K key = rowMapper.getMapKey();
@ -404,7 +404,7 @@ public class ResultSetMapper {
}
// fairly un-interesting methods below here
protected <K, T> RowToObjectMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
public <K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
return new RowToObjectMapper<K, T>(resultSet, returnTypeClass, cal, mapValType, mapKeyType);
}

View File

@ -1,264 +1,21 @@
/*
* 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.
*
* $Header:$
*/
package com.moparisthebest.jdbc;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Abstract base class for all row mappers.
*
* RowMappers are used to map the contents of a row in a ResultSet to the return type of an annotated method.
* Supported RowMapper types include: HashMap, Map, Object, XmlObject. When a ResultSetMapper is ready to
* map a ResultSet row to an object, it requests a RowMapper for the return type of the method from the
* RowMapperFactory.
*
* Created by mopar on 5/18/17.
*/
public abstract class RowMapper<K, T> {
public interface RowMapper<K,T> {
/**
* Map a ResultSet row to the return type class
* @return An instance of class, if _mapKeyType is not null and _columnCount is 2, return only index 2
*/
T mapRowToReturnType() throws SQLException;
private static final String SETTER_NAME_REGEX = "^(set)([A-Z_]\\w*+)";
protected static final TypeMappingsFactory _tmf = TypeMappingsFactory.getInstance();
protected static final Pattern _setterRegex = Pattern.compile(SETTER_NAME_REGEX);
/** ResultSet to map. */
protected final ResultSet _resultSet;
/** Calendar instance for date/time mappings. */
protected final Calendar _cal;
/** Class to map ResultSet Rows to. */
protected final Class<T> _returnTypeClass;
protected final Class<K> _mapKeyType;
protected final int _columnCount;
protected final boolean mapOnlySecondColumn;
/**
* Create a new RowMapper for the specified ResultSet and return type Class.
* @param resultSet ResultSet to map
* @param returnTypeClass Class to map ResultSet rows to.
* @param cal Calendar instance for date/time values.
*/
protected RowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
_resultSet = resultSet;
_returnTypeClass = returnTypeClass;
_cal = cal;
_mapKeyType = mapKeyType;
try {
_columnCount = resultSet.getMetaData().getColumnCount();
} catch (SQLException e) {
throw new MapperException("RowToObjectMapper: SQLException: " + e.getMessage(), e);
}
mapOnlySecondColumn = _mapKeyType != null && _columnCount == 2;
}
protected RowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal) {
this(resultSet, returnTypeClass, cal, null);
}
/**
* Map a ResultSet row to the return type class
* @return An instance of class, if _mapKeyType is not null and _columnCount is 2, return only index 2
*/
public abstract T mapRowToReturnType() throws SQLException;
/**
* key for map
* @return index number 1, with type of _mapKeyType
* @throws MapperException if _mapKeyType is null
*/
public abstract K getMapKey() throws SQLException;
/**
* Build a String array of column names from the ResultSet.
* @return A String array containing the column names contained within the ResultSet.
* @throws java.sql.SQLException on error
*/
protected String[] getKeysFromResultSet() throws SQLException {
String[] keys;
final ResultSetMetaData md = _resultSet.getMetaData();
keys = new String[_columnCount + 1];
for (int i = 1; i <= _columnCount; i++) {
keys[i] = md.getColumnName(i).toUpperCase();
}
return keys;
}
/**
* Determine if the given method is a java bean setter method.
* @param method Method to check
* @return True if the method is a setter method.
*/
protected boolean isSetterMethod(Method method) {
Matcher matcher = _setterRegex.matcher(method.getName());
if (matcher.matches()) {
if (Modifier.isStatic(method.getModifiers())) return false;
if (!Modifier.isPublic(method.getModifiers())) return false;
if (!Void.TYPE.equals(method.getReturnType())) return false;
// method parameter checks
Class[] params = method.getParameterTypes();
if (params.length != 1) return false;
if (TypeMappingsFactory.TYPE_UNKNOWN == _tmf.getTypeId(params[0])) return false;
return true;
}
return false;
}
/**
* Extract a column value from the ResultSet and return it as resultType.
*
* @param index The column index of the value to extract from the ResultSet.
* @param resultType The return type. Defined in TypeMappingsFactory.
* @return The extracted value
* @throws java.sql.SQLException on error.
*/
protected Object extractColumnValue(int index, int resultType) throws SQLException {
switch (resultType) {
case TypeMappingsFactory.TYPE_INT:
return new Integer(_resultSet.getInt(index));
case TypeMappingsFactory.TYPE_LONG:
return new Long(_resultSet.getLong(index));
case TypeMappingsFactory.TYPE_FLOAT:
return new Float(_resultSet.getFloat(index));
case TypeMappingsFactory.TYPE_DOUBLE:
return new Double(_resultSet.getDouble(index));
case TypeMappingsFactory.TYPE_BYTE:
return new Byte(_resultSet.getByte(index));
case TypeMappingsFactory.TYPE_SHORT:
return new Short(_resultSet.getShort(index));
case TypeMappingsFactory.TYPE_BOOLEAN:
return _resultSet.getBoolean(index) ? Boolean.TRUE : Boolean.FALSE;
case TypeMappingsFactory.TYPE_INT_OBJ:
{
int i = _resultSet.getInt(index);
return _resultSet.wasNull() ? null : new Integer(i);
}
case TypeMappingsFactory.TYPE_LONG_OBJ:
{
long i = _resultSet.getLong(index);
return _resultSet.wasNull() ? null : new Long(i);
}
case TypeMappingsFactory.TYPE_FLOAT_OBJ:
{
float i = _resultSet.getFloat(index);
return _resultSet.wasNull() ? null : new Float(i);
}
case TypeMappingsFactory.TYPE_DOUBLE_OBJ:
{
double i = _resultSet.getDouble(index);
return _resultSet.wasNull() ? null : new Double(i);
}
case TypeMappingsFactory.TYPE_BYTE_OBJ:
{
byte i = _resultSet.getByte(index);
return _resultSet.wasNull() ? null : new Byte(i);
}
case TypeMappingsFactory.TYPE_SHORT_OBJ:
{
short i = _resultSet.getShort(index);
return _resultSet.wasNull() ? null : new Short(i);
}
case TypeMappingsFactory.TYPE_BOOLEAN_OBJ:
{
boolean i = _resultSet.getBoolean(index);
return _resultSet.wasNull() ? null : (i ? Boolean.TRUE : Boolean.FALSE);
}
case TypeMappingsFactory.TYPE_STRING:
case TypeMappingsFactory.TYPE_XMLBEAN_ENUM:
return _resultSet.getString(index);
case TypeMappingsFactory.TYPE_BIG_DECIMAL:
return _resultSet.getBigDecimal(index);
case TypeMappingsFactory.TYPE_BYTES:
return _resultSet.getBytes(index);
case TypeMappingsFactory.TYPE_TIMESTAMP:
{
if (null == _cal)
return _resultSet.getTimestamp(index);
else
return _resultSet.getTimestamp(index, _cal);
}
case TypeMappingsFactory.TYPE_TIME:
{
if (null == _cal)
return _resultSet.getTime(index);
else
return _resultSet.getTime(index, _cal);
}
case TypeMappingsFactory.TYPE_SQLDATE:
{
if (null == _cal)
return _resultSet.getDate(index);
else
return _resultSet.getDate(index, _cal);
}
case TypeMappingsFactory.TYPE_DATE:
{
// convert explicity to java.util.Date
// 12918 | knex does not return java.sql.Date properly from web service
java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
if (null == ts)
return null;
return new java.util.Date(ts.getTime());
}
case TypeMappingsFactory.TYPE_CALENDAR:
{
java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
if (null == ts)
return null;
Calendar c = (null == _cal) ? Calendar.getInstance() : (Calendar) _cal.clone();
c.setTimeInMillis(ts.getTime());
return c;
}
case TypeMappingsFactory.TYPE_REF:
return _resultSet.getRef(index);
case TypeMappingsFactory.TYPE_BLOB:
return _resultSet.getBlob(index);
case TypeMappingsFactory.TYPE_CLOB:
return _resultSet.getClob(index);
case TypeMappingsFactory.TYPE_ARRAY:
return _resultSet.getArray(index);
case TypeMappingsFactory.TYPE_READER:
case TypeMappingsFactory.TYPE_STREAM:
throw new MapperException("streaming return types are not supported by the JdbcControl; use ResultSet instead");
case TypeMappingsFactory.TYPE_STRUCT:
case TypeMappingsFactory.TYPE_UNKNOWN:
// JAVA_TYPE (could be any), or REF
return _resultSet.getObject(index);
default:
throw new MapperException("internal error: unknown type ID: " + Integer.toString(resultType));
}
}
/**
* key for map
* @return index number 1, with type of _mapKeyType
* @throws MapperException if _mapKeyType is null
*/
K getMapKey() throws SQLException;
}

View File

@ -0,0 +1,11 @@
package com.moparisthebest.jdbc;
import java.sql.ResultSet;
import java.util.Calendar;
/**
* Created by mopar on 5/18/17.
*/
public interface RowMapperProvider {
<K, T> RowMapper<K, T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType);
}

View File

@ -19,6 +19,8 @@ package com.moparisthebest.jdbc;
* $Header:$
*/
import com.moparisthebest.jdbc.util.CaseInsensitiveHashMap;
import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
@ -26,6 +28,8 @@ import java.sql.SQLException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.moparisthebest.jdbc.UpdateableDTO.YES;
import static com.moparisthebest.jdbc.UpdateableDTO.NO;
@ -47,7 +51,11 @@ import static com.moparisthebest.jdbc.UpdateableDTO.NO;
*
* @author Travis Burtrum (modifications from beehive)
*/
public class RowToObjectMapper<K, T> extends RowMapper<K, T> {
public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
private static final String SETTER_NAME_REGEX = "^(set)([A-Z_]\\w*+)";
protected static final TypeMappingsFactory _tmf = TypeMappingsFactory.getInstance();
protected static final Pattern _setterRegex = Pattern.compile(SETTER_NAME_REGEX);
public static final int TYPE_BOOLEAN = _tmf.getTypeId(Boolean.TYPE);//TypeMappingsFactory.TYPE_BOOLEAN; // not public?
public static final int TYPE_BOOLEAN_OBJ = _tmf.getTypeId(Boolean.class);//TypeMappingsFactory.TYPE_BOOLEAN_OBJ; // not public?
@ -82,6 +90,11 @@ public class RowToObjectMapper<K, T> extends RowMapper<K, T> {
this(resultSet, returnTypeClass, cal, mapValType, null);
}
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
this(resultSet, returnTypeClass, cal, mapValType, mapKeyType, false);
}
/**
* Create a new RowToObjectMapper.
*
@ -89,11 +102,17 @@ public class RowToObjectMapper<K, T> extends RowMapper<K, T> {
* @param returnTypeClass Class to map to.
* @param cal Calendar instance for date/time mappings.
*/
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType, boolean caseInsensitiveMap) {
super(resultSet, returnTypeClass, cal, mapKeyType);
returnMap = Map.class.isAssignableFrom(returnTypeClass);
if(returnMap){
_returnTypeClass = ResultSetMapper.getConcreteClass(returnTypeClass, HashMap.class);
Class<? extends T> rtc = ResultSetMapper.getConcreteClass(returnTypeClass, HashMap.class);
if(caseInsensitiveMap && HashMap.class.equals(rtc)) {
@SuppressWarnings("unchecked")
final Class<? extends T> rtct = (Class<? extends T>) CaseInsensitiveHashMap.class;
rtc = rtct;
}
_returnTypeClass = rtc;
componentType = mapValType;
}else{
_returnTypeClass = returnTypeClass;
@ -137,7 +156,7 @@ public class RowToObjectMapper<K, T> extends RowMapper<K, T> {
* like perhaps to implement the original beehive behavior of case-insensitive strings
*/
@SuppressWarnings({"unchecked"})
protected Map<String, Object> getMapImplementation() throws IllegalAccessException, InstantiationException {
private Map<String, Object> getMapImplementation() throws IllegalAccessException, InstantiationException {
return (Map<String, Object>)_returnTypeClass.newInstance();
}
@ -441,40 +460,178 @@ public class RowToObjectMapper<K, T> extends RowMapper<K, T> {
}
}
@Override
public static <T> T fixNull(Class<T> returnType) {
return returnType.cast(_tmf.fixNull(returnType));
}
/**
* Determine if the given method is a java bean setter method.
* @param method Method to check
* @return True if the method is a setter method.
*/
protected boolean isSetterMethod(Method method) {
Matcher matcher = _setterRegex.matcher(method.getName());
if (matcher.matches()) {
if (Modifier.isStatic(method.getModifiers())) return false;
if (!Modifier.isPublic(method.getModifiers())) return false;
if (!Void.TYPE.equals(method.getReturnType())) return false;
// method parameter checks
Class[] params = method.getParameterTypes();
if (params.length != 1) return false;
if (TypeMappingsFactory.TYPE_UNKNOWN == _tmf.getTypeId(params[0])) return false;
return true;
}
return false;
}
/**
* Extract a column value from the ResultSet and return it as resultType.
*
* @param index The column index of the value to extract from the ResultSet.
* @param resultType The return type. Defined in TypeMappingsFactory.
* @return The extracted value
* @throws java.sql.SQLException on error.
*/
protected Object extractColumnValue(int index, int resultType) throws SQLException {
try{
if (resultType != TYPE_BOOLEAN && resultType != TYPE_BOOLEAN_OBJ)
return super.extractColumnValue(index, resultType);
else {
// do some special handling to convert a database string to a boolean
boolean ret;
try {
// try to get an actual boolean from the database
ret = _resultSet.getBoolean(index);
// null seems to get returned as false above, so String code won't run if its null
if (_resultSet.wasNull())
if (resultType == TYPE_BOOLEAN_OBJ)
return null; // only return null for Boolean object
else
throw new MapperException(String.format("Implicit conversion of database string to boolean failed on column '%d'. Returned string needs to be 'Y' or 'N' and was instead 'null'. If you want to accept null values, make it an object Boolean instead of primitive boolean.", index));
} catch (SQLException e) {
// if we are here, it wasn't a boolean or null, so try to grab a string instead
String bool = _resultSet.getString(index);//.toUpperCase(); // do we want it case-insensitive?
ret = YES.equals(bool);
if (!ret && !NO.equals(bool))
throw new MapperException(String.format("Implicit conversion of database string to boolean failed on column '%d'. Returned string needs to be 'Y' or 'N' and was instead '%s'.", index, bool));
//throw e;
switch (resultType) {
case TypeMappingsFactory.TYPE_INT:
return new Integer(_resultSet.getInt(index));
case TypeMappingsFactory.TYPE_LONG:
return new Long(_resultSet.getLong(index));
case TypeMappingsFactory.TYPE_FLOAT:
return new Float(_resultSet.getFloat(index));
case TypeMappingsFactory.TYPE_DOUBLE:
return new Double(_resultSet.getDouble(index));
case TypeMappingsFactory.TYPE_BYTE:
return new Byte(_resultSet.getByte(index));
case TypeMappingsFactory.TYPE_SHORT:
return new Short(_resultSet.getShort(index));
case TypeMappingsFactory.TYPE_INT_OBJ:
{
int i = _resultSet.getInt(index);
return _resultSet.wasNull() ? null : new Integer(i);
}
return ret ? Boolean.TRUE : Boolean.FALSE;
case TypeMappingsFactory.TYPE_LONG_OBJ:
{
long i = _resultSet.getLong(index);
return _resultSet.wasNull() ? null : new Long(i);
}
case TypeMappingsFactory.TYPE_FLOAT_OBJ:
{
float i = _resultSet.getFloat(index);
return _resultSet.wasNull() ? null : new Float(i);
}
case TypeMappingsFactory.TYPE_DOUBLE_OBJ:
{
double i = _resultSet.getDouble(index);
return _resultSet.wasNull() ? null : new Double(i);
}
case TypeMappingsFactory.TYPE_BYTE_OBJ:
{
byte i = _resultSet.getByte(index);
return _resultSet.wasNull() ? null : new Byte(i);
}
case TypeMappingsFactory.TYPE_SHORT_OBJ:
{
short i = _resultSet.getShort(index);
return _resultSet.wasNull() ? null : new Short(i);
}
case TypeMappingsFactory.TYPE_BOOLEAN:
case TypeMappingsFactory.TYPE_BOOLEAN_OBJ:
{
// do some special handling to convert a database string to a boolean
boolean ret;
try {
// try to get an actual boolean from the database
ret = _resultSet.getBoolean(index);
// null seems to get returned as false above, so String code won't run if its null
if (_resultSet.wasNull())
if (resultType == TYPE_BOOLEAN_OBJ)
return null; // only return null for Boolean object
else
throw new MapperException(String.format("Implicit conversion of database string to boolean failed on column '%d'. Returned string needs to be 'Y' or 'N' and was instead 'null'. If you want to accept null values, make it an object Boolean instead of primitive boolean.", index));
} catch (SQLException e) {
// if we are here, it wasn't a boolean or null, so try to grab a string instead
String bool = _resultSet.getString(index);//.toUpperCase(); // do we want it case-insensitive?
ret = YES.equals(bool);
if (!ret && !NO.equals(bool))
throw new MapperException(String.format("Implicit conversion of database string to boolean failed on column '%d'. Returned string needs to be 'Y' or 'N' and was instead '%s'.", index, bool));
//throw e;
}
return ret ? Boolean.TRUE : Boolean.FALSE;
}
case TypeMappingsFactory.TYPE_STRING:
case TypeMappingsFactory.TYPE_XMLBEAN_ENUM:
return _resultSet.getString(index);
case TypeMappingsFactory.TYPE_BIG_DECIMAL:
return _resultSet.getBigDecimal(index);
case TypeMappingsFactory.TYPE_BYTES:
return _resultSet.getBytes(index);
case TypeMappingsFactory.TYPE_TIMESTAMP:
{
if (null == _cal)
return _resultSet.getTimestamp(index);
else
return _resultSet.getTimestamp(index, _cal);
}
case TypeMappingsFactory.TYPE_TIME:
{
if (null == _cal)
return _resultSet.getTime(index);
else
return _resultSet.getTime(index, _cal);
}
case TypeMappingsFactory.TYPE_SQLDATE:
{
if (null == _cal)
return _resultSet.getDate(index);
else
return _resultSet.getDate(index, _cal);
}
case TypeMappingsFactory.TYPE_DATE:
{
// convert explicity to java.util.Date
// 12918 | knex does not return java.sql.Date properly from web service
java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
if (null == ts)
return null;
return new java.util.Date(ts.getTime());
}
case TypeMappingsFactory.TYPE_CALENDAR:
{
java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
if (null == ts)
return null;
Calendar c = (null == _cal) ? Calendar.getInstance() : (Calendar) _cal.clone();
c.setTimeInMillis(ts.getTime());
return c;
}
case TypeMappingsFactory.TYPE_REF:
return _resultSet.getRef(index);
case TypeMappingsFactory.TYPE_BLOB:
return _resultSet.getBlob(index);
case TypeMappingsFactory.TYPE_CLOB:
return _resultSet.getClob(index);
case TypeMappingsFactory.TYPE_ARRAY:
return _resultSet.getArray(index);
case TypeMappingsFactory.TYPE_READER:
case TypeMappingsFactory.TYPE_STREAM:
throw new MapperException("streaming return types are not supported by the JdbcControl; use ResultSet instead");
case TypeMappingsFactory.TYPE_STRUCT:
case TypeMappingsFactory.TYPE_UNKNOWN:
// JAVA_TYPE (could be any), or REF
return _resultSet.getObject(index);
default:
throw new MapperException("internal error: unknown type ID: " + Integer.toString(resultType));
}
}catch(SQLException e){
throw new SQLExceptionColumnNum(e, index);
}
}
public static <T> T fixNull(Class<T> returnType) {
return returnType.cast(_tmf.fixNull(returnType));
}
}

View File

@ -0,0 +1,18 @@
package com.moparisthebest.jdbc.util;
import java.util.HashMap;
/**
* Created by mopar on 5/18/17.
*/
public class CaseInsensitiveHashMap<String, V> extends HashMap<String, V> {
@Override
public V get(Object key) {
return super.get(key instanceof java.lang.String ? ((java.lang.String) key).toLowerCase() : key);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(key instanceof java.lang.String ? ((java.lang.String) key).toLowerCase() : key);
}
}

View File

@ -0,0 +1,81 @@
package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.dto.*;
import org.junit.*;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.*;
import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1;
import static com.moparisthebest.jdbc.QueryMapperTest.getConnection;
import static com.moparisthebest.jdbc.QueryMapperTest.personRegular;
import static com.moparisthebest.jdbc.TryClose.tryClose;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Created by mopar on 6/10/14.
*/
@RunWith(Parameterized.class)
public class CleaningQueryMapperTest {
private static Connection conn;
@BeforeClass
public static void setUp() throws Throwable {
conn = getConnection();
}
@AfterClass
public static void tearDown() throws Throwable {
tryClose(conn);
}
protected QueryMapper qm;
protected final ResultSetMapper rsm;
public CleaningQueryMapperTest(final ResultSetMapper rsm) {
this.rsm = rsm;
}
@Before
public void open() {
this.qm = new QueryMapper(conn, rsm);
}
@After
public void close() {
tryClose(qm);
}
@Parameterized.Parameters(name="{0}")
public static Collection<Object[]> getParameters()
{
final Cleaner<FieldPerson> personCleaner = new Cleaner<FieldPerson>() {
@Override
public FieldPerson clean(final FieldPerson dto) {
dto.firstName += " " + dto.lastName;
dto.lastName = null;
return dto;
}
};
return Arrays.asList(new Object[][] {
{ new CleaningResultSetMapper<FieldPerson>(personCleaner) },
{ new CleaningCachingResultSetMapper<FieldPerson>(personCleaner) },
{ new CleaningCompilingResultSetMapper<FieldPerson>(personCleaner) },
});
}
// fields
@Test
public void testFieldRegularPerson() throws Throwable {
final Person expected = fieldPerson1;
final Person actual = qm.toObject(personRegular, expected.getClass(), expected.getPersonNo());
assertEquals(expected.getFirstName() + " " + expected.getLastName(), actual.getFirstName());
assertNull(actual.getLastName());
}
}

View File

@ -10,6 +10,8 @@ import java.sql.DriverManager;
import java.util.*;
import static com.moparisthebest.jdbc.TryClose.tryClose;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Created by mopar on 6/10/14.
@ -19,48 +21,47 @@ public class QueryMapperTest {
private static Connection conn;
protected static final Person fieldPerson1 = new FieldPerson(1, new Date(0), "First", "Person");
protected static final Boss fieldBoss1 = new FieldBoss(2, new Date(0), "Second", "Person", "Finance", "Second");
protected static final Boss fieldBoss2 = new FieldBoss(3, new Date(0), "Third", "Person", "Finance", null);
protected static final Boss fieldBoss3 = new FieldBoss(4, new Date(0), null, "Person", "Finance", "Fourth");
public static final Person fieldPerson1 = new FieldPerson(1, new Date(0), "First", "Person");
public static final Boss fieldBoss1 = new FieldBoss(2, new Date(0), "Second", "Person", "Finance", "Second");
public static final Boss fieldBoss2 = new FieldBoss(3, new Date(0), "Third", "Person", "Finance", null);
public static final Boss fieldBoss3 = new FieldBoss(4, new Date(0), null, "Person", "Finance", "Fourth");
protected static final Person setPerson1 = new SetPerson(fieldPerson1);
protected static final Boss setBoss1 = new SetBoss(fieldBoss1);
protected static final Boss setBoss2 = new SetBoss(fieldBoss2);
protected static final Boss setBoss3 = new SetBoss(fieldBoss3);
public static final Person setPerson1 = new SetPerson(fieldPerson1);
public static final Boss setBoss1 = new SetBoss(fieldBoss1);
public static final Boss setBoss2 = new SetBoss(fieldBoss2);
public static final Boss setBoss3 = new SetBoss(fieldBoss3);
protected static final Person reverseFieldPerson1 = new ReverseFieldPerson(fieldPerson1);
protected static final Boss reverseFieldBoss1 = new ReverseFieldBoss(fieldBoss1);
protected static final Boss reverseFieldBoss2 = new ReverseFieldBoss(fieldBoss2);
protected static final Boss reverseFieldBoss3 = new ReverseFieldBoss(fieldBoss3);
public static final Person reverseFieldPerson1 = new ReverseFieldPerson(fieldPerson1);
public static final Boss reverseFieldBoss1 = new ReverseFieldBoss(fieldBoss1);
public static final Boss reverseFieldBoss2 = new ReverseFieldBoss(fieldBoss2);
public static final Boss reverseFieldBoss3