diff --git a/pom.xml b/pom.xml index 45dd4ed..b829864 100644 --- a/pom.xml +++ b/pom.xml @@ -299,6 +299,10 @@ **/module-info.java + + -parameters + -Xlint:unchecked + testCompile diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingRowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingRowToObjectMapper.java index 579036c..b299217 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingRowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingRowToObjectMapper.java @@ -35,20 +35,46 @@ public class CachingRowToObjectMapper extends RowToObjectMapper { } @Override - protected void getFieldMappings() throws SQLException { + protected void lazyLoadConstructor() throws SQLException { final FieldMapping fm = cache.get(keys); if (fm == null) { //System.out.printf("cache miss, keys: %s\n", keys); // generate and put into cache - super.getFieldMappings(); - cache.put(keys, new FieldMapping(_fields, _fieldTypes, resultSetConstructor, constructor)); + super.lazyLoadConstructor(); + cache.put(keys, new FieldMapping(_fields, _fieldTypes, _fieldOrder, _fieldClasses, resultSetConstructor, constructor)); } else { //System.out.printf("cache hit, keys: %s\n", keys); // load from cache _fields = fm._fields; _fieldTypes = fm._fieldTypes; + _fieldOrder = fm._fieldOrder; + _fieldClasses = fm._fieldClasses; resultSetConstructor = fm.resultSetConstructor; constructor = fm.constructor; + _args = _fieldOrder == null ? new Object[1] : new Object[_columnCount]; + constructorLoaded = true; + } + } + + @Override + protected void getFieldMappings() throws SQLException { + final FieldMapping fm = cache.get(keys); + // todo: could cache fm from lazyLoadConstructor and only set _fields if non-final, but race? investigate + if (fm == null || fm._fields == null) { + //System.out.printf("cache miss, keys: %s\n", keys); + // generate and put into cache + super.getFieldMappings(); + cache.put(keys, new FieldMapping(_fields, _fieldTypes, _fieldOrder, _fieldClasses, resultSetConstructor, constructor)); + } else { + //System.out.printf("cache hit, keys: %s\n", keys); + // load from cache + _fields = fm._fields; + _fieldTypes = fm._fieldTypes; + _fieldOrder = fm._fieldOrder; + _fieldClasses = fm._fieldClasses; + resultSetConstructor = fm.resultSetConstructor; + constructor = fm.constructor; + _args = _fieldOrder == null ? new Object[1] : new Object[_columnCount]; constructorLoaded = true; } } @@ -97,13 +123,16 @@ public class CachingRowToObjectMapper extends RowToObjectMapper { static class FieldMapping { public final AccessibleObject[] _fields; - public final int[] _fieldTypes; + public final int[] _fieldTypes, _fieldOrder; + public final Class[] _fieldClasses; public final boolean resultSetConstructor; public final Constructor constructor; - public FieldMapping(final AccessibleObject[] _fields, final int[] _fieldTypes, final boolean resultSetConstructor, final Constructor constructor) { + public FieldMapping(final AccessibleObject[] _fields, final int[] _fieldTypes, final int[] _fieldOrder, final Class[] _fieldClasses, final boolean resultSetConstructor, final Constructor constructor) { this._fields = _fields; this._fieldTypes = _fieldTypes; + this._fieldOrder = _fieldOrder; + this._fieldClasses = _fieldClasses; this.resultSetConstructor = resultSetConstructor; this.constructor = constructor; } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java index 4a08f55..076471f 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java @@ -246,13 +246,29 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { return; } - lazyLoadConstructor(); + try { + lazyLoadConstructor(); + } catch (SQLException e) { + throw new MapperException(e.getMessage(), e); + } if (resultSetConstructor) { java.append("final ").append(tType).append(" ret = new ").append(tType).append("(rs);\n"); return; } + if(_fieldOrder != null) { + java.append("final ").append(tType).append(" ret = new ").append(tType).append("(\n"); + for(int x = 1; x <= _columnCount; ++x) { + extractColumnValueString(java, _fieldOrder[x], _fieldTypes[x], _fieldClasses[x] == null ? null : _fieldClasses[x].getCanonicalName()); + if(x != _columnCount) + java.append(",\n"); + //_args[x-1] = extractColumnValue(_fieldOrder[x], _fieldTypes[x], _fieldClasses[x]); + } + java.append(");\n"); + return; + } + if (returnMap) // we want a map try { java.append("final ").append(tType).append(" ret = new ").append(tType).append("();\n"); @@ -338,17 +354,17 @@ public class CompilingRowToObjectMapper extends RowToObjectMapper { // if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine java.append("ret.").append(((Field) f).getName()).append(" = "); extractColumnValueString(java, i, _fieldTypes[i], - _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((Field) f).getType().getCanonicalName() : null); + _fieldClasses[i] == null ? null : _fieldClasses[i].getCanonicalName()); java.append(";\n"); } else if (f instanceof ReflectionAccessibleObject) { java.append("com.moparisthebest.jdbc.util.ReflectionUtil.setValue(_fields[").append(String.valueOf(((ReflectionAccessibleObject)f).index)).append("], ret, "); extractColumnValueString(java, i, _fieldTypes[i], - _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((ReflectionAccessibleObject) f).field.getType().getCanonicalName() : null); + _fieldClasses[i] == null ? null : _fieldClasses[i].getCanonicalName()); java.append(");\n"); } else { java.append("ret.").append(((Method) f).getName()).append("("); extractColumnValueString(java, i, _fieldTypes[i], - _fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM ? ((Method) f).getParameterTypes()[0].getCanonicalName() : null); + _fieldClasses[i] == null ? null : _fieldClasses[i].getCanonicalName()); java.append(");\n"); } } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java index 99d42fe..4ca5be3 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/RowToObjectMapper.java @@ -26,6 +26,7 @@ import java.lang.reflect.*; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.Map; @@ -67,15 +68,16 @@ public class RowToObjectMapper extends AbstractRowMapper { protected boolean resultSetConstructor, constructorLoaded = false; protected Constructor constructor; protected final Class _returnTypeClass; // over-ride non-generic version of this in super class - + // only non-null when _returnTypeClass is an array, or a map protected final Class componentType; protected final boolean returnMap; protected AccessibleObject[] _fields = null; - protected int[] _fieldTypes; + protected int[] _fieldTypes, _fieldOrder; + protected Class[] _fieldClasses; - protected final Object[] _args = new Object[1]; + protected Object[] _args; public RowToObjectMapper(ResultSet resultSet, Class returnTypeClass) { this(resultSet, returnTypeClass, null, null); @@ -129,7 +131,7 @@ public class RowToObjectMapper extends AbstractRowMapper { } } - protected void lazyLoadConstructor() { + protected void lazyLoadConstructor() throws SQLException { if(constructorLoaded) return; // detect if returnTypeClass has a constructor that takes a ResultSet, if so, our job couldn't be easier... @@ -142,19 +144,69 @@ public class RowToObjectMapper extends AbstractRowMapper { resultSetConstructor = true; } catch (Throwable e) { // if no resultSetConstructor find the constructor - try { - constructor = _returnTypeClass.getDeclaredConstructor(); - if (!constructor.isAccessible()) - constructor.setAccessible(true); - } catch (Throwable e1) { - // if column count is 2 or less, it might map directly to a type like a Long or something, or be a map which does - // or if componentType is non-null, then we want an array like Long[] or String[] - if(_columnCount > 2 && componentType == null) - throw new MapperException("Exception when trying to get constructor for : "+_returnTypeClass.getName() + " Must have default no-arg constructor or one that takes a single ResultSet.", e1); + + // use con.getParameterAnnotations(); for java 6? ugly + //IFJAVA8_START + // look for constructor with matching parameters first + String[] keys = null; + Map strippedKeys = null; + outer: + for(final Constructor con : _returnTypeClass.getConstructors()) { + final Parameter[] params = con.getParameters(); + if(params.length == _columnCount) { + if(!params[0].isNamePresent()) + break; // nothing to do here, compile with -params? + // do this stuff only once + if(keys == null) { + keys = getKeysFromResultSet(); + strippedKeys = new HashMap(keys.length * 2); + for (int x = 1; x <= _columnCount; ++x) { + final String key = keys[x]; + strippedKeys.put(key, x); + strippedKeys.put(key.replaceAll("_", ""), x); + } + _fieldOrder = new int[keys.length]; + _fieldTypes = new int[keys.length]; + @SuppressWarnings("unchecked") + final Class[] noWarnings = (Class[]) new Class[keys.length]; + _fieldClasses = noWarnings; + } + int count = 0; + for(final Parameter param : params) { + final Integer index = strippedKeys.get(param.getName().toUpperCase()); + if(index == null) + continue outer; + _fieldOrder[++count] = index; + _fieldTypes[count] = _tmf.getTypeId(param.getType()); + if(_fieldTypes[count] == TypeMappingsFactory.TYPE_ENUM) { + @SuppressWarnings("unchecked") + final Class noWarnings = (Class) param.getType(); + _fieldClasses[count] = noWarnings; + } + } + @SuppressWarnings("unchecked") + final Constructor noWarnings = (Constructor)con; + this._args = new Object[params.length]; + constructor = noWarnings; + } } + //IFJAVA8_END + if(constructor == null) + try { + constructor = _returnTypeClass.getDeclaredConstructor(); + if (!constructor.isAccessible()) + constructor.setAccessible(true); + } catch (Throwable e1) { + // if column count is 2 or less, it might map directly to a type like a Long or something, or be a map which does + // or if componentType is non-null, then we want an array like Long[] or String[] + if(_columnCount > 2 && componentType == null) + throw new MapperException("Exception when trying to get constructor for : "+_returnTypeClass.getName() + " Must have default no-arg constructor or one that takes a single ResultSet.", e1); + } } this.resultSetConstructor = resultSetConstructor; this.constructor = constructor; + if(this._args == null) + this._args = new Object[1]; this.constructorLoaded = true; } @@ -192,8 +244,19 @@ public class RowToObjectMapper extends AbstractRowMapper { } catch (Throwable e) { throw new MapperException(e.getClass().getName() + " when trying to create instance of : " + _returnTypeClass.getName() + " sending in a ResultSet object as a parameter", e); - } - + } + + if(_fieldOrder != null) + try { + for(int x = 1; x <= _columnCount; ++x) + _args[x-1] = extractColumnValue(_fieldOrder[x], _fieldTypes[x], _fieldClasses[x]); + //System.out.println("creating " + constructor + " sending in a objects: " + Arrays.toString(_args)); + return constructor.newInstance(_args); + } catch (Throwable e) { + throw new MapperException(e.getClass().getName() + " when trying to create instance of : " + + _returnTypeClass.getName() + " sending in a objects: " + Arrays.toString(_args), e); + } + if(returnMap) // we want a map try { final Map ret = getMapImplementation(); @@ -271,7 +334,7 @@ public class RowToObjectMapper extends AbstractRowMapper { AccessibleObject f = _fields[i]; try { - _args[0] = extractColumnValue(i, _fieldTypes[i], null); // be lazy about this + _args[0] = extractColumnValue(i, _fieldTypes[i], _fieldClasses[i]); //System.out.printf("field: '%s' obj: '%s' fieldType: '%s'\n", _fields[i], _args[0], _fieldTypes[i]); if (f instanceof Field) { ((Field) f).set(resultObject, _args[0]); @@ -440,7 +503,13 @@ public class RowToObjectMapper extends AbstractRowMapper { // finally actually init the fields array _fields = new AccessibleObject[_columnCount + 1]; - _fieldTypes = new int[_columnCount + 1]; + if(_fieldTypes == null) + _fieldTypes = new int[_fields.length]; + if(_fieldClasses == null) { + @SuppressWarnings("unchecked") + final Class[] noWarnings = (Class[]) new Class[_fields.length]; + _fieldClasses = noWarnings; + } for (int i = 1; i < _fields.length; i++) { AccessibleObject f = mapFields.get(keys[i]); @@ -457,6 +526,11 @@ public class RowToObjectMapper extends AbstractRowMapper { final Field field = (Field) f; _fields[i] = modField(field, i); _fieldTypes[i] = _tmf.getTypeId(field.getType()); + if(_fieldTypes[i] == TypeMappingsFactory.TYPE_ENUM) { + @SuppressWarnings("unchecked") + final Class noWarnings = (Class) field.getType(); + _fieldClasses[i] = noWarnings; + } } else { _fieldTypes[i] = _tmf.getTypeId(((Method) f).getParameterTypes()[0]); } @@ -503,7 +577,7 @@ public class RowToObjectMapper extends AbstractRowMapper { * @return The extracted value * @throws java.sql.SQLException on error. */ - protected Object extractColumnValue(final int index, final int resultType, Class resultTypeClass) throws SQLException { + protected Object extractColumnValue(final int index, final int resultType, final Class resultTypeClass) throws SQLException { try{ switch (resultType) { case TypeMappingsFactory.TYPE_INT: @@ -577,11 +651,6 @@ public class RowToObjectMapper extends AbstractRowMapper { case TypeMappingsFactory.TYPE_XMLBEAN_ENUM: return _resultSet.getString(index); case TypeMappingsFactory.TYPE_ENUM: - if(resultTypeClass == null) { - // load lazily, todo: could cache? meh - final AccessibleObject f = _fields[index]; - resultTypeClass = f instanceof Field ? ((Field)f).getType() : ((Method)f).getParameterTypes()[0]; - } @SuppressWarnings("unchecked") final Enum ret = Enum.valueOf((Class)resultTypeClass, _resultSet.getString(index)); return ret;