From 881de93799a22aa468c1e82f2a1b530ebde416ed Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Fri, 9 Jun 2017 00:36:41 -0400 Subject: [PATCH] Add ResultSetIterable support to jdbcmapper --- .../codegen/CompileTimeResultSetMapper.java | 49 +++++++++++++++++-- .../jdbc/codegen/JdbcMapperProcessor.java | 35 ++++++++----- .../jdbc/codegen/JdbcMapperTest.java | 36 +++++++++++++- .../jdbc/codegen/PersonDAO.java | 16 +++++- 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java index d8484e9..93a8e0b 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/CompileTimeResultSetMapper.java @@ -2,6 +2,7 @@ package com.moparisthebest.jdbc.codegen; import com.moparisthebest.jdbc.Finishable; import com.moparisthebest.jdbc.ResultSetMapper; +import com.moparisthebest.jdbc.util.ResultSetIterable; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; @@ -26,7 +27,8 @@ import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.typeMirrorToCl public class CompileTimeResultSetMapper { public final Types types; - public final TypeMirror collectionType, mapType, mapCollectionType, iteratorType, listIteratorType, finishableType, resultSetType; + public final TypeMirror collectionType, mapType, mapCollectionType, iteratorType, listIteratorType, finishableType, resultSetType, resultSetIterableType; + private final boolean java8 = false; public CompileTimeResultSetMapper(final ProcessingEnvironment processingEnv) { types = processingEnv.getTypeUtils(); @@ -41,6 +43,8 @@ public class CompileTimeResultSetMapper { finishableType = elements.getTypeElement(Finishable.class.getCanonicalName()).asType(); resultSetType = elements.getTypeElement(ResultSet.class.getCanonicalName()).asType(); + + resultSetIterableType = types.getDeclaredType(elements.getTypeElement(ResultSetIterable.class.getCanonicalName()), types.getWildcardType(null, null)); } public static String getConcreteClassCanonicalName(final TypeMirror returnType, final Class defaultConcreteClass) { @@ -56,7 +60,11 @@ public class CompileTimeResultSetMapper { return typeMirrorStringNoGenerics(returnType); } - public void mapToResultType(final Writer w, final String[] keys, final ExecutableElement eeMethod, final MaxRows maxRows, final String cal, final String cleaner) throws IOException, NoSuchMethodException, ClassNotFoundException { + /** + * + * @return true if calling code should close rs (ResultSet) and ps (PreparedStatement) if closePs is false, false otherwise + */ + public boolean mapToResultType(final Writer w, final String[] keys, final ExecutableElement eeMethod, final MaxRows maxRows, final String cal, final String cleaner, final boolean closePs) throws IOException, NoSuchMethodException, ClassNotFoundException { //final Method m = fromExecutableElement(eeMethod); //final Class returnType = m.getReturnType(); final TypeMirror returnTypeMirror = eeMethod.getReturnType(); @@ -79,18 +87,22 @@ public class CompileTimeResultSetMapper { collectionTypeMirror, componentTypeMirror, maxRows, cal, cleaner); - return; + return true; } toMap(w, keys, returnTypeMirror, typeArguments.get(0), typeArguments.get(1), maxRows, cal, cleaner); } else if (types.isAssignable(returnTypeMirror, iteratorType)) { final List typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments(); - if (types.isAssignable(returnTypeMirror, listIteratorType)) + if (types.isAssignable(returnTypeMirror, resultSetIterableType)) { + toResultSetIterable(w, keys, typeArguments.get(0), cal, cleaner, closePs); + return false; + } else if (types.isAssignable(returnTypeMirror, listIteratorType)) toListIterator(w, keys, typeArguments.get(0), maxRows, cal, cleaner); else toIterator(w, keys, typeArguments.get(0), maxRows, cal, cleaner); } else { toObject(w, keys, returnTypeMirror, cal, cleaner); } + return true; } public CompileTimeRowToObjectMapper getRowMapper(final String[] keys, TypeMirror returnTypeClass, String cal, TypeMirror mapValType, TypeMirror mapKeyType) { @@ -109,6 +121,35 @@ public class CompileTimeResultSetMapper { clean(w, cleaner).write(";\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n"); } + private void toResultSetIterable(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final String cal, final String cleaner, final boolean closePs) throws IOException, ClassNotFoundException { + w.write("\t\t\treturn com.moparisthebest.jdbc.util.ResultSetIterable.getResultSetIterable(rs,\n\t\t\t\t\trs.next() ? "); + + if(java8) { + w.append("(rs, ").append(cal == null ? "_cal" : cal).append(") -> {\n"); + } else { + final String returnTypeString = returnTypeMirror.toString(); + w.append("new com.moparisthebest.jdbc.util.ResultSetToObject<") + .append(returnTypeString).append(">() {\n\t\t\t\t\tpublic ") + .append(returnTypeString).append(" toObject(final ResultSet rs, final java.util.Calendar ") + .append(cal == null ? "_cal" : cal) + .append(") throws SQLException {\n"); + } + + // com.moparisthebest.jdbc.util.ResultSetToObject implementation + writeObject(w, keys, returnTypeMirror, cal); + w.write("\t\t\t\t\t\treturn "); + clean(w, cleaner).write(";\n"); + // end ResultSetToObject implementation + + if(!java8) + w.write("\t\t\t\t\t}\n"); + + w.append("\t\t\t\t\t}\n\t\t\t\t: null, ").append(cal == null ? "null" : cal).append(")"); + if(closePs) + w.append(".setPreparedStatementToClose(ps)"); + w.append(";\n"); + } + public void writeCollection(final Writer w, final String[] keys, final String returnTypeString, final String concreteTypeString, final TypeMirror componentTypeMirror, MaxRows maxRows, String cal, final String cleaner) throws IOException, ClassNotFoundException { maxRowInit(w, maxRows).write("\t\t\tfinal "); w.write(returnTypeString); diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java index 37f00af..1ba6866 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java @@ -349,6 +349,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { for (final VariableElement param : bindParams) setObject(w, ++count, databaseType, arrayNumberTypeName, arrayStringTypeName, param); + boolean closeRs = true; if (!parsedSQl.isSelect()) { if (returnType.equals("void")) { w.write("\t\t\tps.executeUpdate();\n"); @@ -370,20 +371,32 @@ public class JdbcMapperProcessor extends AbstractProcessor { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL sql parsed a wildcard column name which is not supported", methodElement); return false; } - rsm.mapToResultType(w, keys, eeMethod, maxRows, calendarName, cleanerName); - } - - // if no SQLException is thrown, we have to catch it here and wrap it with RuntimeException... - if (!sqlExceptionThrown) { - w.write("\t\t} catch(SQLException e) {\n\t\t\tthrow new RuntimeException(e);\n"); + closeRs = rsm.mapToResultType(w, keys, eeMethod, maxRows, calendarName, cleanerName, !cachePreparedStatements); } // close things - w.write("\t\t} finally {\n"); - if (parsedSQl.isSelect()) - w.write("\t\t\ttryClose(rs);\n"); - if (!cachePreparedStatements) - w.write("\t\t\ttryClose(ps);\n"); + if(closeRs) { + // like normal + // if no SQLException is thrown, we have to catch it here and wrap it with RuntimeException... + if (!sqlExceptionThrown) + w.write("\t\t} catch(SQLException e) {\n\t\t\tthrow new RuntimeException(e);\n"); + w.write("\t\t} finally {\n"); + if (parsedSQl.isSelect()) + w.write("\t\t\ttryClose(rs);\n"); + if (!cachePreparedStatements) + w.write("\t\t\ttryClose(ps);\n"); + } else { + // very annoying special handling in that if any exceptions are thrown, we have to close everything even if closeRs == false... + w.write("\t\t} catch(Throwable e) {\n"); + if (parsedSQl.isSelect()) + w.write("\t\t\ttryClose(rs);\n"); + if (!cachePreparedStatements) + w.write("\t\t\ttryClose(ps);\n"); + if (sqlExceptionThrown) + w.write("\t\t\tif(e instanceof SQLException)\n\t\t\t\tthrow (SQLException)e;\n"); + w.write("\t\t\tif(e instanceof RuntimeException)\n\t\t\t\tthrow (RuntimeException)e;\n"); + w.write("\t\t\tthrow new RuntimeException(e);\n"); + } w.write("\t\t}\n"); w.write("\t}\n"); diff --git a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/JdbcMapperTest.java b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/JdbcMapperTest.java index af85884..ecb2eb0 100644 --- a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/JdbcMapperTest.java +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/JdbcMapperTest.java @@ -1,12 +1,20 @@ package com.moparisthebest.jdbc.codegen; +import com.moparisthebest.jdbc.dto.FieldPerson; +import com.moparisthebest.jdbc.util.ResultSetIterable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1; import static com.moparisthebest.jdbc.QueryMapperTest.getConnection; +import static com.moparisthebest.jdbc.QueryMapperTest.people; import static com.moparisthebest.jdbc.TryClose.tryClose; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; /** @@ -24,7 +32,7 @@ public class JdbcMapperTest { @AfterClass public static void tearDown() throws Throwable { - //tryClose(dao); + tryClose(dao); } public PersonDAO getDao() { @@ -35,4 +43,30 @@ public class JdbcMapperTest { public void testName() throws Throwable { assertEquals(fieldPerson1.getFirstName(), getDao().getFirstName(fieldPerson1.getPersonNo())); } + + @Test + public void testList() throws SQLException { + final List fromDb = dao.getPeopleList(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()); + assertArrayEquals(people, fromDb.toArray()); + } + + @Test + public void testResultSetIterable() throws SQLException { + final ResultSetIterable rsi = dao.getPeopleResultSetIterable(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()); + final List fromDb = new ArrayList(); + for(final FieldPerson fieldPerson : rsi) + fromDb.add(fieldPerson); + rsi.close(); + assertArrayEquals(people, fromDb.toArray()); + } + + @Test + public void testResultSetIterableCachedPreparedStatement() throws SQLException { + final ResultSetIterable rsi = dao.getPeopleResultSetIterableCachedPreparedStatement(people[0].getPersonNo(), people[1].getPersonNo(), people[2].getPersonNo()); + final List fromDb = new ArrayList(); + for(final FieldPerson fieldPerson : rsi) + fromDb.add(fieldPerson); + rsi.close(); + assertArrayEquals(people, fromDb.toArray()); + } } diff --git a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java index 32ca883..62815ab 100644 --- a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java @@ -3,7 +3,9 @@ package com.moparisthebest.jdbc.codegen; import com.moparisthebest.jdbc.Cleaner; import com.moparisthebest.jdbc.dto.FieldPerson; import com.moparisthebest.jdbc.dto.Person; +import com.moparisthebest.jdbc.util.ResultSetIterable; +import java.io.Closeable; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; @@ -15,10 +17,10 @@ import java.util.*; @JdbcMapper.Mapper( // jndiName = "bob", // databaseType = JdbcMapper.DatabaseType.ORACLE -// cachePreparedStatements = false + cachePreparedStatements = JdbcMapper.OptionalBool.FALSE // , sqlParser = SimpleSQLParser.class ) -public interface PersonDAO { +public interface PersonDAO extends Closeable { @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE last_name = {lastName}") int setFirstName(String firstName, String lastName); @@ -132,4 +134,14 @@ public interface PersonDAO { Map getPersonStaticLimitMap(long personNo) throws SQLException; @JdbcMapper.SQL(value = "SELECT first_name, last_name, birth_date FROM person WHERE person_no = {personNo}", maxRows = Long.MAX_VALUE) Map> getPersonStaticLimitMapList(long personNo) throws SQLException; + + + @JdbcMapper.SQL("SELECT person_no, birth_date, last_name, first_name from person WHERE person_no IN ({personNo1},{personNo2},{personNo3}) ORDER BY person_no") + List getPeopleList(long personNo1, long personNo2, long personNo3) throws SQLException; + + @JdbcMapper.SQL("SELECT person_no, birth_date, last_name, first_name from person WHERE person_no IN ({personNo1},{personNo2},{personNo3}) ORDER BY person_no") + ResultSetIterable getPeopleResultSetIterable(long personNo1, long personNo2, long personNo3) throws SQLException; + + @JdbcMapper.SQL(value = "SELECT person_no, birth_date, last_name, first_name from person WHERE person_no IN ({personNo1},{personNo2},{personNo3}) ORDER BY person_no", cachePreparedStatement = JdbcMapper.OptionalBool.TRUE) + ResultSetIterable getPeopleResultSetIterableCachedPreparedStatement(long personNo1, long personNo2, long personNo3) throws SQLException; }