diff --git a/querymapper/genQueryMapper.sh b/querymapper/genQueryMapper.sh index 39de0ee..ffc086f 100755 --- a/querymapper/genQueryMapper.sh +++ b/querymapper/genQueryMapper.sh @@ -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" <> "$caching_query" < T toType(String sql, TypeReference typeReference, final Object... bindObjects) throws SQLException { + return super.toType(getPreparedStatement(sql), typeReference, bindObjects); + } + @Override public , E> T toCollection(String sql, final Class collectionType, Class componentType, final Object... bindObjects) throws SQLException { return super.toCollection(getPreparedStatement(sql), collectionType, componentType, bindObjects); diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java index 3c490a8..bb45f71 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java @@ -313,6 +313,16 @@ public class ListQueryMapper extends QueryMapper { return delegate.toSingleMap(prepareSql(sql, bindObjects), mapValType, bindObjects); } + @Override + public T toType(PreparedStatement ps, TypeReference typeReference, final Object... bindObjects) throws SQLException { + return delegate.toType(ps, typeReference, bindObjects); + } + + @Override + public T toType(String sql, TypeReference typeReference, final Object... bindObjects) throws SQLException { + return delegate.toType(prepareSql(sql, bindObjects), typeReference, bindObjects); + } + @Override public , E> T toCollection(PreparedStatement ps, final Class collectionType, Class componentType, final Object... bindObjects) throws SQLException { return delegate.toCollection(ps, collectionType, componentType, bindObjects); diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java index 87472de..317a55a 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java @@ -390,6 +390,26 @@ public class NullQueryMapper extends QueryMapper { return null; } + @Override + public T toType(PreparedStatement query, TypeReference typeReference, final Object... bindObjects) { + try { + return delegate.toType(query, typeReference, bindObjects); + } catch (Throwable e) { + if (verbose) e.printStackTrace(); + } + return null; + } + + @Override + public T toType(String query, TypeReference typeReference, final Object... bindObjects) { + try { + return delegate.toType(query, typeReference, bindObjects); + } catch (Throwable e) { + if (verbose) e.printStackTrace(); + } + return null; + } + @Override public , E> T toCollection(PreparedStatement query, final Class collectionType, Class componentType, final Object... bindObjects) { try { diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java index 413e23a..2c6e713 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java @@ -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 toType(String sql, TypeReference 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 ResultSetIterable toResultSetIterable(String sql, Class componentType, final Object... bindObjects) throws SQLException { boolean error = true; PreparedStatement ps = null; @@ -507,6 +553,10 @@ public class QueryMapper implements Closeable { } } + public T toType(PreparedStatement ps, TypeReference typeReference, final Object... bindObjects) throws SQLException { + return cm.toType(bindExecute(ps, bindObjects), typeReference); + } + public , E> T toCollection(PreparedStatement ps, final Class collectionType, Class componentType, final Object... bindObjects) throws SQLException { return cm.toCollection(bindExecute(ps, bindObjects), collectionType, componentType); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ResultSetMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ResultSetMapper.java index d31122e..e0de913 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/ResultSetMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ResultSetMapper.java @@ -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)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 toType(ResultSet rs, TypeReference typeReference, int arrayMaxLength, Calendar cal) { + return (T)this.toType(rs, typeReference.getRawType(), typeReference.getType(), arrayMaxLength, cal); + } + public T toObject(ResultSet rs, Class 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 toType(ResultSet rs, TypeReference typeReference) { + return this.toType(rs, typeReference, arrayMaxLength, cal); + } + + public T toType(ResultSet rs, TypeReference typeReference, int arrayMaxLength) { + return this.toType(rs, typeReference, arrayMaxLength, cal); + } + + public T toType(ResultSet rs, TypeReference typeReference, Calendar cal) { + return this.toType(rs, typeReference, arrayMaxLength, cal); + } + public , E> T toCollection(ResultSet rs, final Class collectionType, Class componentType) { return this.toCollection(rs, collectionType, componentType, arrayMaxLength, cal); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/TypeReference.java b/querymapper/src/main/java/com/moparisthebest/jdbc/TypeReference.java new file mode 100644 index 0000000..08e5abb --- /dev/null +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/TypeReference.java @@ -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. + *

+ * Class is based on ideas from + * http://gafter.blogspot.com/2006/12/super-type-tokens.html, + * Additional idea (from a suggestion made in comments of the article) + * is to require bogus implementation of Comparable + * (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. + *

+ * Usage is by sub-classing: here is one way to instantiate reference + * to generic type List<Integer>: + *

+ *  TypeReference ref = new TypeReference<List<Integer>>() { };
+ * 
+ * which can be passed to methods that accept TypeReference + *

+ * 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 implements Comparable> { + + 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 Comparable) is to prevent constructing a + * reference without type information. + */ + @Override + public final int compareTo(TypeReference o) { + return 0; + } + // just need an implementation, not a good one... hence ^^^ +} + diff --git a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java index 088bf52..0879f3d 100644 --- a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java +++ b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java @@ -402,6 +402,13 @@ public class QueryMapperTest { assertArrayEquals(people, fromDb.toArray()); } + @Test + public void testListType() throws SQLException { + final List fromDb = qm.toType("SELECT * from person WHERE person_no IN (?,?,?) ORDER BY person_no", + new TypeReference>() {}, 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);