Browse Source

Add TypeReference and toType method to querymapper

Travis Burtrum 1 year ago
parent
commit
195a0c0484

+ 2
- 2
querymapper/genQueryMapper.sh View File

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

+ 5
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/CachingQueryMapper.java View File

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

+ 10
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java View File

@@ -313,6 +313,16 @@ public class ListQueryMapper extends QueryMapper {
313 313
 		return delegate.toSingleMap(prepareSql(sql, bindObjects), mapValType, bindObjects);
314 314
 	}
315 315
 
316
+	@Override
317
+	public <T> T toType(PreparedStatement ps, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
318
+		return delegate.toType(ps, typeReference, bindObjects);
319
+	}
320
+
321
+	@Override
322
+	public <T> T toType(String sql, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
323
+		return delegate.toType(prepareSql(sql, bindObjects), typeReference, bindObjects);
324
+	}
325
+
316 326
 	@Override
317 327
 	public <T extends Collection<E>, E> T toCollection(PreparedStatement ps, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) throws SQLException {
318 328
 		return delegate.toCollection(ps, collectionType, componentType, bindObjects);

+ 20
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java View File

@@ -390,6 +390,26 @@ public class NullQueryMapper extends QueryMapper {
390 390
 		return null;
391 391
 	}
392 392
 
393
+	@Override
394
+	public <T> T toType(PreparedStatement query, TypeReference<T> typeReference, final Object... bindObjects) {
395
+		try {
396
+			return delegate.toType(query, typeReference, bindObjects);
397
+		} catch (Throwable e) {
398
+			if (verbose) e.printStackTrace();
399
+		}
400
+		return null;
401
+	}
402
+
403
+	@Override
404
+	public <T> T toType(String query, TypeReference<T> typeReference, final Object... bindObjects) {
405
+		try {
406
+			return delegate.toType(query, typeReference, bindObjects);
407
+		} catch (Throwable e) {
408
+			if (verbose) e.printStackTrace();
409
+		}
410
+		return null;
411
+	}
412
+
393 413
 	@Override
394 414
 	public <T extends Collection<E>, E> T toCollection(PreparedStatement query, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) {
395 415
 		try {

+ 50
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java View File

@@ -345,6 +345,52 @@ public class QueryMapper implements Closeable {
345 345
 
346 346
 	// these are handled specially and not generated because we need it to hold open PreparedStatement until the ResultSetIterable is closed
347 347
 
348
+	@SuppressWarnings("unchecked")
349
+	public <T> T toType(String sql, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
350
+		boolean error = true, closePs = true;
351
+		PreparedStatement ps = null;
352
+		ResultSet rs = null;
353
+		T ret = null;
354
+		try {
355
+			ps = conn.prepareStatement(sql);
356
+			rs = this.toResultSet(ps, bindObjects);
357
+			ret = cm.toType(rs, typeReference);
358
+			if(ret instanceof ResultSetIterable) {
359
+				ret = (T)((ResultSetIterable)ret).setPreparedStatementToClose(ps);
360
+				closePs = false;
361
+			}
362
+			//IFJAVA8_START
363
+			else if(ret instanceof Stream) {
364
+				final PreparedStatement finalPs = ps;
365
+				ret = (T)((Stream)ret).onClose(() -> tryClose(finalPs));
366
+				closePs = false;
367
+			}
368
+			//IFJAVA8_END
369
+			else if(ret instanceof ResultSet) {
370
+				ret = (T)new StatementClosingResultSet(rs, ps);
371
+				closePs = false;
372
+			}
373
+			error = false;
374
+			return ret;
375
+		} finally {
376
+			if (error) {
377
+				if(ret != null) {
378
+					if(ret instanceof ResultSet)
379
+						tryClose((ResultSet)ret);
380
+					else if(ret instanceof Closeable)
381
+						tryClose((Closeable)ret);
382
+					//IFJAVA8_START
383
+					else if(ret instanceof AutoCloseable)
384
+						tryClose((AutoCloseable)ret);
385
+					//IFJAVA8_END
386
+				}
387
+				tryClose(rs);
388
+				tryClose(ps);
389
+			} else if(closePs)
390
+				tryClose(ps);
391
+		}
392
+	}
393
+
348 394
 	public <T> ResultSetIterable<T> toResultSetIterable(String sql, Class<T> componentType, final Object... bindObjects) throws SQLException {
349 395
 		boolean error = true;
350 396
 		PreparedStatement ps = null;
@@ -507,6 +553,10 @@ public class QueryMapper implements Closeable {
507 553
 		}
508 554
 	}
509 555
 
556
+	public <T> T toType(PreparedStatement ps, TypeReference<T> typeReference, final Object... bindObjects) throws SQLException {
557
+		return cm.toType(bindExecute(ps, bindObjects), typeReference);
558
+	}
559
+
510 560
 	public <T extends Collection<E>, E> T toCollection(PreparedStatement ps, final Class<T> collectionType, Class<E> componentType, final Object... bindObjects) throws SQLException {
511 561
 		return cm.toCollection(bindExecute(ps, bindObjects), collectionType, componentType);
512 562
 	}

+ 53
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/ResultSetMapper.java View File

@@ -24,6 +24,8 @@ import com.moparisthebest.jdbc.util.ResultSetIterable;
24 24
 import java.lang.ref.SoftReference;
25 25
 import java.lang.reflect.Array;
26 26
 import java.lang.reflect.Modifier;
27
+import java.lang.reflect.ParameterizedType;
28
+import java.lang.reflect.Type;
27 29
 import java.sql.ResultSet;
28 30
 import java.sql.ResultSetMetaData;
29 31
 import java.sql.SQLException;
@@ -463,6 +465,45 @@ public class ResultSetMapper implements RowMapperProvider {
463 465
 
464 466
 	// overloaded helper methods
465 467
 
468
+	@SuppressWarnings("unchecked")
469
+	protected Object toType(final ResultSet rs, final Class returnType, final ParameterizedType type, final int arrayMaxLength, final Calendar cal) {
470
+		if (returnType.isArray()) {
471
+			return toArray(rs, returnType.getComponentType(), arrayMaxLength, cal);
472
+		} else if (Collection.class.isAssignableFrom(returnType)) {
473
+			return toCollection(rs, returnType, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal);
474
+		} else if (Map.class.isAssignableFrom(returnType)) {
475
+			Type[] types = type.getActualTypeArguments();
476
+			if (types[1] instanceof ParameterizedType) { // for collectionMaps
477
+				ParameterizedType pt = (ParameterizedType) types[1];
478
+				Class collectionType = (Class) pt.getRawType();
479
+				if (Collection.class.isAssignableFrom(collectionType))
480
+					return toMapCollection(rs, returnType, (Class) types[0], collectionType, (Class) pt.getActualTypeArguments()[0], arrayMaxLength, cal);
481
+			}
482
+			return toMap(rs, com.moparisthebest.jdbc.ResultSetMapper.instantiateClass((Class<Map>)returnType, HashMap.class), (Class) types[0], (Class) types[1], arrayMaxLength, cal);
483
+		} else if (Iterator.class.isAssignableFrom(returnType)) {
484
+			if(ResultSetIterable.class.isAssignableFrom(returnType))
485
+				return toResultSetIterable(rs, (Class) type.getActualTypeArguments()[0], cal);
486
+			return ListIterator.class.isAssignableFrom(returnType) ?
487
+					toListIterator(rs, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal) :
488
+					toIterator(rs, (Class) type.getActualTypeArguments()[0], arrayMaxLength, cal);
489
+		}
490
+		//IFJAVA8_START
491
+		else if (Stream.class.isAssignableFrom(returnType)) {
492
+			return toStream(rs, (Class) type.getActualTypeArguments()[0], cal);
493
+		}
494
+		//IFJAVA8_END
495
+		else if(ResultSet.class.isAssignableFrom(returnType)) {
496
+			return rs; // odd, we didn't do much, but oh well
497
+		} else {
498
+			return toObject(rs, returnType, cal);
499
+		}
500
+	}
501
+
502
+	@SuppressWarnings("unchecked")
503
+	public <T> T toType(ResultSet rs, TypeReference<T> typeReference, int arrayMaxLength, Calendar cal) {
504
+		return (T)this.toType(rs, typeReference.getRawType(), typeReference.getType(), arrayMaxLength, cal);
505
+	}
506
+
466 507
 	public <T> T toObject(ResultSet rs, Class<T> componentType, Calendar cal) {
467 508
 		return privToObject(rs, componentType, cal, null);
468 509
 	}
@@ -691,6 +732,18 @@ public class ResultSetMapper implements RowMapperProvider {
691 732
 		return this.toSingleMap(rs, mapValType, cal);
692 733
 	}
693 734
 
735
+	public <T> T toType(ResultSet rs, TypeReference<T> typeReference) {
736
+		return this.toType(rs, typeReference, arrayMaxLength, cal);
737
+	}
738
+
739
+	public <T> T toType(ResultSet rs, TypeReference<T> typeReference, int arrayMaxLength) {
740
+		return this.toType(rs, typeReference, arrayMaxLength, cal);
741
+	}
742
+
743
+	public <T> T toType(ResultSet rs, TypeReference<T> typeReference, Calendar cal) {
744
+		return this.toType(rs, typeReference, arrayMaxLength, cal);
745
+	}
746
+
694 747
 	public <T extends Collection<E>, E> T toCollection(ResultSet rs, final Class<T> collectionType, Class<E> componentType) {
695 748
 		return this.toCollection(rs, collectionType, componentType, arrayMaxLength, cal);
696 749
 	}

+ 79
- 0
querymapper/src/main/java/com/moparisthebest/jdbc/TypeReference.java View File

@@ -0,0 +1,79 @@
1
+package com.moparisthebest.jdbc;
2
+
3
+import java.lang.reflect.ParameterizedType;
4
+import java.lang.reflect.Type;
5
+import java.util.Arrays;
6
+
7
+/**
8
+ * This generic abstract class is used for obtaining full generics type information
9
+ * by sub-classing.
10
+ * <p>
11
+ * Class is based on ideas from
12
+ * <a href="http://gafter.blogspot.com/2006/12/super-type-tokens.html"
13
+ * >http://gafter.blogspot.com/2006/12/super-type-tokens.html</a>,
14
+ * Additional idea (from a suggestion made in comments of the article)
15
+ * is to require bogus implementation of <code>Comparable</code>
16
+ * (any such generic interface would do, as long as it forces a method
17
+ * with generic type to be implemented).
18
+ * to ensure that a Type argument is indeed given.
19
+ * <p>
20
+ * Usage is by sub-classing: here is one way to instantiate reference
21
+ * to generic type <code>List&lt;Integer&gt;</code>:
22
+ * <pre>
23
+ *  TypeReference ref = new TypeReference&lt;List&lt;Integer&gt;&gt;() { };
24
+ * </pre>
25
+ * which can be passed to methods that accept TypeReference
26
+ * <p>
27
+ * Pulled from jackson here:
28
+ * https://raw.githubusercontent.com/FasterXML/jackson-core/3dcedd2b6838ef29abb179557b6e42479e93834b/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
29
+ * Thankfully jackson has an identical Apache 2.0 license so no issues there
30
+ */
31
+public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
32
+
33
+	private final ParameterizedType type;
34
+	private final Class<?> rawType;
35
+
36
+	protected TypeReference() {
37
+		final Type superClass = getClass().getGenericSuperclass();
38
+		if (superClass instanceof Class<?>) { // sanity check, should never happen
39
+			throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
40
+		}
41
+		/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
42
+		 *   it is possible to make it fail?
43
+         *   But let's deal with specific
44
+         *   case when we know an actual use case, and thereby suitable
45
+         *   workarounds for valid case(s) and/or error to throw
46
+         *   on invalid one(s).
47
+         */
48
+		final Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
49
+		if (type instanceof Class<?>) {
50
+			this.type = null;
51
+			this.rawType = (Class<?>) type;
52
+		} else if (type instanceof ParameterizedType) {
53
+			this.type = (ParameterizedType) type;
54
+			this.rawType = (Class<?>) this.type.getRawType();
55
+		} else {
56
+			throw new IllegalArgumentException("Internal error: TypeReference constructed with unknown type: '" + type + "' class: '" + type.getClass() + "'");
57
+		}
58
+	}
59
+
60
+	public final ParameterizedType getType() {
61
+		return type;
62
+	}
63
+
64
+	public final Class<?> getRawType() {
65
+		return rawType;
66
+	}
67
+
68
+	/**
69
+	 * The only reason we define this method (and require implementation
70
+	 * of <code>Comparable</code>) is to prevent constructing a
71
+	 * reference without type information.
72
+	 */
73
+	@Override
74
+	public final int compareTo(TypeReference<T> o) {
75
+		return 0;
76
+	}
77
+	// just need an implementation, not a good one... hence ^^^
78
+}
79
+

+ 7
- 0
querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java View File

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