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> <testExcludes>
<exclude>**/module-info.java</exclude> <exclude>**/module-info.java</exclude>
</testExcludes> </testExcludes>
<compilerArgs>
<compilerArg>-parameters</compilerArg>
<compilerArg>-Xlint:unchecked</compilerArg>
</compilerArgs>
</configuration> </configuration>
<goals> <goals>
<goal>testCompile</goal> <goal>testCompile</goal>

View File

@ -35,20 +35,46 @@ public class CachingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
} }
@Override @Override
protected void getFieldMappings() throws SQLException { protected void lazyLoadConstructor() throws SQLException {
final FieldMapping<T> fm = cache.get(keys); final FieldMapping<T> fm = cache.get(keys);
if (fm == null) { if (fm == null) {
//System.out.printf("cache miss, keys: %s\n", keys); //System.out.printf("cache miss, keys: %s\n", keys);
// generate and put into cache // generate and put into cache
super.getFieldMappings(); super.lazyLoadConstructor();
cache.put(keys, new FieldMapping<T>(_fields, _fieldTypes, resultSetConstructor, constructor)); cache.put(keys, new FieldMapping<T>(_fields, _fieldTypes, _fieldOrder, _fieldClasses, resultSetConstructor, constructor));
} else { } else {
//System.out.printf("cache hit, keys: %s\n", keys); //System.out.printf("cache hit, keys: %s\n", keys);
// load from cache // load from cache
_fields = fm._fields; _fields = fm._fields;
_fieldTypes = fm._fieldTypes; _fieldTypes = fm._fieldTypes;
_fieldOrder = fm._fieldOrder;
_fieldClasses = fm._fieldClasses;
resultSetConstructor = fm.resultSetConstructor; resultSetConstructor = fm.resultSetConstructor;
constructor = fm.constructor; 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; constructorLoaded = true;
} }
} }
@ -97,13 +123,16 @@ public class CachingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
static class FieldMapping<T> { static class FieldMapping<T> {
public final AccessibleObject[] _fields; 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 boolean resultSetConstructor;
public final Constructor<? extends T> constructor; 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._fields = _fields;
this._fieldTypes = _fieldTypes; this._fieldTypes = _fieldTypes;
this._fieldOrder = _fieldOrder;
this._fieldClasses = _fieldClasses;
this.resultSetConstructor = resultSetConstructor; this.resultSetConstructor = resultSetConstructor;
this.constructor = constructor; this.constructor = constructor;
} }

View File

@ -246,13 +246,29 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
return; return;
} }
lazyLoadConstructor(); try {
lazyLoadConstructor();
} catch (SQLException e) {
throw new MapperException(e.getMessage(), e);
}
if (resultSetConstructor) { if (resultSetConstructor) {
java.append("final ").append(tType).append(" ret = new ").append(tType).append("(rs);\n"); java.append("final ").append(tType).append(" ret = new ").append(tType).append("(rs);\n");
return; 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 if (returnMap) // we want a map
try { try {
java.append("final ").append(tType).append("<String, Object> ret = new ").append(tType).append("<String, Object>();\n"); 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 // if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine
java.append("ret.").append(((Field) f).getName()).append(" = "); java.append("ret.").append(((Field) f).getName()).append(" = ");
extractColumnValueString(java, i, _fieldTypes[i], 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"); java.append(";\n");
} else if (f instanceof ReflectionAccessibleObject) { } else if (f instanceof ReflectionAccessibleObject) {
java.append("com.moparisthebest.jdbc.util.ReflectionUtil.setValue(_fields[").append(String.valueOf(((ReflectionAccessibleObject)f).index)).append("], ret, "); java.append("com.moparisthebest.jdbc.util.ReflectionUtil.setValue(_fields[").append(String.valueOf(((ReflectionAccessibleObject)f).index)).append("], ret, ");
extractColumnValueString(java, i, _fieldTypes[i], 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"); java.append(");\n");
} else { } else {
java.append("ret.").append(((Method) f).getName()).append("("); java.append("ret.").append(((Method) f).getName()).append("(");
extractColumnValueString(java, i, _fieldTypes[i], 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"); java.append(");\n");
} }
} }

View File

@ -26,6 +26,7 @@ import java.lang.reflect.*;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -73,9 +74,10 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
protected final boolean returnMap; protected final boolean returnMap;
protected AccessibleObject[] _fields = null; 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) { public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass) {
this(resultSet, returnTypeClass, null, null); 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) if(constructorLoaded)
return; return;
// detect if returnTypeClass has a constructor that takes a ResultSet, if so, our job couldn't be easier... // 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; resultSetConstructor = true;
} catch (Throwable e) { } catch (Throwable e) {
// if no resultSetConstructor find the constructor // if no resultSetConstructor find the constructor
try {
constructor = _returnTypeClass.getDeclaredConstructor(); // use con.getParameterAnnotations(); for java 6? ugly
if (!constructor.isAccessible()) //IFJAVA8_START
constructor.setAccessible(true); // look for constructor with matching parameters first
} catch (Throwable e1) { String[] keys = null;
// 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 Map<String, Integer> strippedKeys = null;
// or if componentType is non-null, then we want an array like Long[] or String[] outer:
if(_columnCount > 2 && componentType == null) for(final Constructor<?> con : _returnTypeClass.getConstructors()) {
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); 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.resultSetConstructor = resultSetConstructor;
this.constructor = constructor; this.constructor = constructor;
if(this._args == null)
this._args = new Object[1];
this.constructorLoaded = true; this.constructorLoaded = true;
} }
@ -194,6 +246,17 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
+ _returnTypeClass.getName() + " sending in a ResultSet object as a parameter", e); + _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 if(returnMap) // we want a map
try { try {
final Map<String, Object> ret = getMapImplementation(); final Map<String, Object> ret = getMapImplementation();
@ -271,7 +334,7 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
AccessibleObject f = _fields[i]; AccessibleObject f = _fields[i];
try { 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]); //System.out.printf("field: '%s' obj: '%s' fieldType: '%s'\n", _fields[i], _args[0], _fieldTypes[i]);
if (f instanceof Field) { if (f instanceof Field) {
((Field) f).set(resultObject, _args[0]); ((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 // finally actually init the fields array
_fields = new AccessibleObject[_columnCount + 1]; _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++) { for (int i = 1; i < _fields.length; i++) {
AccessibleObject f = mapFields.get(keys[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; final Field field = (Field) f;
_fields[i] = modField(field, i); _fields[i] = modField(field, i);
_fieldTypes[i] = _tmf.getTypeId(field.getType()); _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 { } else {
_fieldTypes[i] = _tmf.getTypeId(((Method) f).getParameterTypes()[0]); _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 * @return The extracted value
* @throws java.sql.SQLException on error. * @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{ try{
switch (resultType) { switch (resultType) {
case TypeMappingsFactory.TYPE_INT: case TypeMappingsFactory.TYPE_INT:
@ -577,11 +651,6 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
case TypeMappingsFactory.TYPE_XMLBEAN_ENUM: case TypeMappingsFactory.TYPE_XMLBEAN_ENUM:
return _resultSet.getString(index); return _resultSet.getString(index);
case TypeMappingsFactory.TYPE_ENUM: 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") @SuppressWarnings("unchecked")
final Enum ret = Enum.valueOf((Class<? extends Enum>)resultTypeClass, _resultSet.getString(index)); final Enum ret = Enum.valueOf((Class<? extends Enum>)resultTypeClass, _resultSet.getString(index));
return ret; return ret;