Add constructor calling for querymapper in java8+ only for now

This commit is contained in:
Travis Burtrum 2017-06-19 21:08:22 -04:00
parent 46686e5b05
commit d2df6dabaf
4 changed files with 150 additions and 32 deletions

View File

@ -299,6 +299,10 @@
<testExcludes>
<exclude>**/module-info.java</exclude>
</testExcludes>
<compilerArgs>
<compilerArg>-parameters</compilerArg>
<compilerArg>-Xlint:unchecked</compilerArg>
</compilerArgs>
</configuration>
<goals>
<goal>testCompile</goal>

View File

@ -35,20 +35,46 @@ public class CachingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
}
@Override
protected void getFieldMappings() throws SQLException {
protected void lazyLoadConstructor() throws SQLException {
final FieldMapping<T> 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<T>(_fields, _fieldTypes, resultSetConstructor, constructor));
super.lazyLoadConstructor();
cache.put(keys, new FieldMapping<T>(_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<T> 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<T>(_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<K, T> extends RowToObjectMapper<K, T> {
static class FieldMapping<T> {
public final AccessibleObject[] _fields;
public final int[] _fieldTypes;
public final int[] _fieldTypes, _fieldOrder;
public final Class<? extends Enum>[] _fieldClasses;
public final boolean resultSetConstructor;
public final Constructor<? extends T> constructor;
public FieldMapping(final AccessibleObject[] _fields, final int[] _fieldTypes, final boolean resultSetConstructor, final Constructor<? extends T> constructor) {
public FieldMapping(final AccessibleObject[] _fields, final int[] _fieldTypes, final int[] _fieldOrder, final Class<? extends Enum>[] _fieldClasses, final boolean resultSetConstructor, final Constructor<? extends T> constructor) {
this._fields = _fields;
this._fieldTypes = _fieldTypes;
this._fieldOrder = _fieldOrder;
this._fieldClasses = _fieldClasses;
this.resultSetConstructor = resultSetConstructor;
this.constructor = constructor;
}

View File

@ -246,13 +246,29 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
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("<String, Object> ret = new ").append(tType).append("<String, Object>();\n");
@ -338,17 +354,17 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
// 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");
}
}

View File

@ -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<K, T> extends AbstractRowMapper<K, T> {
protected boolean resultSetConstructor, constructorLoaded = false;
protected Constructor<? extends T> constructor;
protected final Class<? extends T> _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<? extends Enum>[] _fieldClasses;
protected final Object[] _args = new Object[1];
protected Object[] _args;
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass) {
this(resultSet, returnTypeClass, null, null);
@ -129,7 +131,7 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
}
}
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<K, T> extends AbstractRowMapper<K, T> {
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<String, Integer> 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<String, Integer>(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<? extends Enum>[] noWarnings = (Class<? extends Enum>[]) 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<? extends Enum> noWarnings = (Class<? extends Enum>) param.getType();
_fieldClasses[count] = noWarnings;
}
}
@SuppressWarnings("unchecked")
final Constructor<? extends T> noWarnings = (Constructor<? extends T>)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<K, T> extends AbstractRowMapper<K, T> {
} 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<String, Object> ret = getMapImplementation();
@ -271,7 +334,7 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
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<K, T> extends AbstractRowMapper<K, T> {
// 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<? extends Enum>[] noWarnings = (Class<? extends Enum>[]) 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<K, T> extends AbstractRowMapper<K, T> {
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<? extends Enum> noWarnings = (Class<? extends Enum>) field.getType();
_fieldClasses[i] = noWarnings;
}
} else {
_fieldTypes[i] = _tmf.getTypeId(((Method) f).getParameterTypes()[0]);
}
@ -503,7 +577,7 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
* @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<K, T> extends AbstractRowMapper<K, T> {
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<? extends Enum>)resultTypeClass, _resultSet.getString(index));
return ret;