Add TypeReference and toType method to querymapper

This commit is contained in:
Travis Burtrum 2017-06-29 00:00:46 -04:00
parent c1b8cfcf3b
commit 195a0c0484
8 changed files with 226 additions and 2 deletions

View File

@ -82,7 +82,7 @@ do
EOF
# handle this specially in QueryMapper because we need it to hold open PreparedStatement until the ResultSetIterable is closed
if [ "$method_name" != 'toResultSetIterable(' -a "$method_name" != 'toStream(' ]; then
if [ "$method_name" != 'toResultSetIterable(' -a "$method_name" != 'toStream(' -a "$method_name" != 'toType(' ]; then
cat >> "$query" <<EOF
$(echo $method | sed -e 's/ResultSet rs/String sql/' -e 's/) {/, final Object... bindObjects) throws SQLException {/')
@ -97,7 +97,7 @@ EOF
EOF
fi # end special case toResultSetIterable/toStream
fi # end special case toResultSetIterable/toStream/toType
# CachingQueryMapper.java
cat >> "$caching_query" <<EOF

View File

@ -180,6 +180,11 @@ public class CachingQueryMapper extends QueryMapper {
return super.toSingleMap(getPreparedStatement(sql), mapValType, bindObjects);
}
@Override
public <T> T toType(String sql, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
return super.toType(getPreparedStatement(sql), typeReference, bindObjects);
}
@Override
public <T extends Collection<E>, E> T toCollection(String sql, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) throws SQLException {
return super.toCollection(getPreparedStatement(sql), collectionType, componentType, bindObjects);

View File

@ -313,6 +313,16 @@ public class ListQueryMapper extends QueryMapper {
return delegate.toSingleMap(prepareSql(sql, bindObjects), mapValType, bindObjects);
}
@Override
public <T> T toType(PreparedStatement ps, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
return delegate.toType(ps, typeReference, bindObjects);
}
@Override
public <T> T toType(String sql, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
return delegate.toType(prepareSql(sql, bindObjects), typeReference, bindObjects);
}
@Override
public <T extends Collection<E>, E> T toCollection(PreparedStatement ps, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) throws SQLException {
return delegate.toCollection(ps, collectionType, componentType, bindObjects);

View File

@ -390,6 +390,26 @@ public class NullQueryMapper extends QueryMapper {
return null;
}
@Override
public <T> T toType(PreparedStatement query, TypeReference<T> typeReference, final Object... bindObjects) {
try {
return delegate.toType(query, typeReference, bindObjects);
} catch (Throwable e) {
if (verbose) e.printStackTrace();
}
return null;
}
@Override
public <T> T toType(String query, TypeReference<T> typeReference, final Object... bindObjects) {
try {
return delegate.toType(query, typeReference, bindObjects);
} catch (Throwable e) {
if (verbose) e.printStackTrace();
}
return null;
}
@Override
public <T extends Collection<E>, E> T toCollection(PreparedStatement query, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) {
try {

View File

@ -345,6 +345,52 @@ public class QueryMapper implements Closeable {
// these are handled specially and not generated because we need it to hold open PreparedStatement until the ResultSetIterable is closed
@SuppressWarnings("unchecked")
public <T> T toType(String sql, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
boolean error = true, closePs = true;
PreparedStatement ps = null;
ResultSet rs = null;
T ret = null;
try {
ps = conn.prepareStatement(sql);
rs = this.toResultSet(ps, bindObjects);
ret = cm.toType(rs, typeReference);
if(ret instanceof ResultSetIterable) {
ret = (T)((ResultSetIterable)ret).setPreparedStatementToClose(ps);
closePs = false;
}
//IFJAVA8_START
else if(ret instanceof Stream) {
final PreparedStatement finalPs = ps;
ret = (T)((Stream)ret).onClose(() -> tryClose(finalPs));
closePs = false;
}
//IFJAVA8_END
else if(ret instanceof ResultSet) {
ret = (T)new StatementClosingResultSet(rs, ps);
closePs = false;
}
error = false;
return ret;
} finally {
if (error) {
if(ret != null) {
if(ret instanceof ResultSet)
tryClose((ResultSet)ret);
else if(ret instanceof Closeable)
tryClose((Closeable)ret);
//IFJAVA8_START
else if(ret instanceof AutoCloseable)
tryClose((AutoCloseable)ret);
//IFJAVA8_END
}
tryClose(rs);
tryClose(ps);
} else if(closePs)
tryClose(ps);
}
}
public <T> ResultSetIterable<T> toResultSetIterable(String sql, Class<T> componentType, final Object... bindObjects) throws SQLException {
boolean error = true;
PreparedStatement ps = null;
@ -507,6 +553,10 @@ public class QueryMapper implements Closeable {
}
}
public <T> T toType(PreparedStatement ps, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
return cm.toType(bindExecute(ps, bindObjects), typeReference);
}
public <T extends Collection<E>, E> T toCollection(PreparedStatement ps, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) throws SQLException {
return cm.toCollection(bindExecute(ps, bindObjects), collectionType, componentType);
}

View File

@ -24,6 +24,8 @@ import com.moparisthebest.jdbc.util.ResultSetIterable;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@ -463,6 +465,45 @@ public class ResultSetMapper implements RowMapperProvider {
// overloaded helper methods
@SuppressWarnings("unchecked")
protected Object toType(final ResultSet rs, final Class returnType, final ParameterizedType type, final int arrayMaxLength, final Calendar cal) {
if (returnType.isArray()) {
return toArray(rs, returnType.getComponentType(), arrayMaxLength, cal);
} else if (Collection.class.isAssignableFrom(returnType)) {
return toCollection(rs, returnType, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal);
} else if (Map.class.isAssignableFrom(returnType)) {
Type[] types = type.getActualTypeArguments();
if (types[1] instanceof ParameterizedType) { // for collectionMaps
ParameterizedType pt = (ParameterizedType) types[1];
Class collectionType = (Class) pt.getRawType();
if (Collection.class.isAssignableFrom(collectionType))
return toMapCollection(rs, returnType, (Class) types[0], collectionType, (Class) pt.getActualTypeArguments()[0], arrayMaxLength, cal);
}
return toMap(rs, com.moparisthebest.jdbc.ResultSetMapper.instantiateClass((Class<Map>)returnType, HashMap.class), (Class) types[0], (Class) types[1], arrayMaxLength, cal);
} else if (Iterator.class.isAssignableFrom(returnType)) {
if(ResultSetIterable.class.isAssignableFrom(returnType))
return toResultSetIterable(rs, (Class) type.getActualTypeArguments()[0], cal);
return ListIterator.class.isAssignableFrom(returnType) ?
toListIterator(rs, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal) :
toIterator(rs, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal);
}
//IFJAVA8_START
else if (Stream.class.isAssignableFrom(returnType)) {
return toStream(rs, (Class) type.getActualTypeArguments()[0], cal);
}
//IFJAVA8_END
else if(ResultSet.class.isAssignableFrom(returnType)) {
return rs; // odd, we didn't do much, but oh well
} else {
return toObject(rs, returnType, cal);
}
}
@SuppressWarnings("unchecked")
public <T> T toType(ResultSet rs, TypeReference<T> typeReference, int arrayMaxLength, Calendar cal) {
return (T)this.toType(rs, typeReference.getRawType(), typeReference.getType(), arrayMaxLength, cal);
}
public <T> T toObject(ResultSet rs, Class<T> componentType, Calendar cal) {
return privToObject(rs, componentType, cal, null);
}
@ -691,6 +732,18 @@ public class ResultSetMapper implements RowMapperProvider {
return this.toSingleMap(rs, mapValType, cal);
}
public <T> T toType(ResultSet rs, TypeReference<T> typeReference) {
return this.toType(rs, typeReference, arrayMaxLength, cal);
}
public <T> T toType(ResultSet rs, TypeReference<T> typeReference, int arrayMaxLength) {
return this.toType(rs, typeReference, arrayMaxLength, cal);
}
public <T> T toType(ResultSet rs, TypeReference<T> typeReference, Calendar cal) {
return this.toType(rs, typeReference, arrayMaxLength, cal);
}
public <T extends Collection<E>, E> T toCollection(ResultSet rs, final Class<T> collectionType, Class<E> componentType) {
return this.toCollection(rs, collectionType, componentType, arrayMaxLength, cal);
}

View File

@ -0,0 +1,79 @@
package com.moparisthebest.jdbc;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
/**
* This generic abstract class is used for obtaining full generics type information
* by sub-classing.
* <p>
* Class is based on ideas from
* <a href="http://gafter.blogspot.com/2006/12/super-type-tokens.html"
* >http://gafter.blogspot.com/2006/12/super-type-tokens.html</a>,
* Additional idea (from a suggestion made in comments of the article)
* is to require bogus implementation of <code>Comparable</code>
* (any such generic interface would do, as long as it forces a method
* with generic type to be implemented).
* to ensure that a Type argument is indeed given.
* <p>
* Usage is by sub-classing: here is one way to instantiate reference
* to generic type <code>List&lt;Integer&gt;</code>:
* <pre>
* TypeReference ref = new TypeReference&lt;List&lt;Integer&gt;&gt;() { };
* </pre>
* which can be passed to methods that accept TypeReference
* <p>
* Pulled from jackson here:
* https://raw.githubusercontent.com/FasterXML/jackson-core/3dcedd2b6838ef29abb179557b6e42479e93834b/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
* Thankfully jackson has an identical Apache 2.0 license so no issues there
*/
public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
private final ParameterizedType type;
private final Class<?> rawType;
protected TypeReference() {
final Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
* it is possible to make it fail?
* But let's deal with specific
* case when we know an actual use case, and thereby suitable
* workarounds for valid case(s) and/or error to throw
* on invalid one(s).
*/
final Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
if (type instanceof Class<?>) {
this.type = null;
this.rawType = (Class<?>) type;
} else if (type instanceof ParameterizedType) {
this.type = (ParameterizedType) type;
this.rawType = (Class<?>) this.type.getRawType();
} else {
throw new IllegalArgumentException("Internal error: TypeReference constructed with unknown type: '" + type + "' class: '" + type.getClass() + "'");
}
}
public final ParameterizedType getType() {
return type;
}
public final Class<?> getRawType() {
return rawType;
}
/**
* The only reason we define this method (and require implementation
* of <code>Comparable</code>) is to prevent constructing a
* reference without type information.
*/
@Override
public final int compareTo(TypeReference<T> o) {
return 0;
}
// just need an implementation, not a good one... hence ^^^
}

View File

@ -402,6 +402,13 @@ public class QueryMapperTest {
assertArrayEquals(people, fromDb.toArray());
}
@Test
public void testListType() throws SQLException {
final List<FieldPerson> fromDb = qm.toType("SELECT * from person WHERE person_no IN (?,?,?) ORDER BY person_no",
new TypeReference<List<FieldPerson>>() {}, people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo());
assertArrayEquals(people, fromDb.toArray());
}
@Test
public void testListQueryMapperList() throws SQLException {
final ListQueryMapper lqm = new ListQueryMapper(qm);