Remove requirement for @JdbcMapper.Mapper to implement JdbcMapper, logic around requiring close() method or not

This commit is contained in:
Travis Burtrum 2017-05-28 23:38:10 -04:00
parent 5960341297
commit 2f19f2ad46
7 changed files with 96 additions and 24 deletions

View File

@ -25,8 +25,15 @@ public interface JdbcMapper extends Closeable {
*/ */
String jndiName() default ""; String jndiName() default "";
boolean cachePreparedStatements() default true; /**
* This defaults to true if a close() method exists to override/implement, false otherwise
*/
OptionalBool cachePreparedStatements() default OptionalBool.DEFAULT;
/**
* This defaults to SimpleSQLParser, PrestoSQLParser is another option for Java 8, or implement your own
* @return
*/
Class<? extends SQLParser> sqlParser() default SQLParser.class; Class<? extends SQLParser> sqlParser() default SQLParser.class;
} }
@ -38,7 +45,10 @@ public interface JdbcMapper extends Closeable {
*/ */
String value(); String value();
OptionalBool cachePreparedStatement() default OptionalBool.INHERIT; /**
* This defaults to the value of the class-level @JdbcMapper.Mapper.cachePreparedStatements annotation, but can be configured on a per-method level here
*/
OptionalBool cachePreparedStatement() default OptionalBool.DEFAULT;
/** /**
* Maximum array length. * Maximum array length.
@ -56,18 +66,18 @@ public interface JdbcMapper extends Closeable {
} }
public enum OptionalBool { public enum OptionalBool {
INHERIT, DEFAULT,
TRUE, TRUE,
FALSE; FALSE;
public boolean combine(final boolean inherited) { public boolean combine(final boolean def) {
switch (this) { switch (this) {
case TRUE: case TRUE:
return true; return true;
case FALSE: case FALSE:
return false; return false;
} }
return inherited; return def;
} }
} }
} }

View File

@ -10,7 +10,7 @@ public abstract class JdbcMapperFactory {
static final String SUFFIX = "Bean"; static final String SUFFIX = "Bean";
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends JdbcMapper> T create(final Class<T> jdbcMapper, final Connection connection) { public static <T> T create(final Class<T> jdbcMapper, final Connection connection) {
try { try {
return (T) Class.forName(jdbcMapper.getName() + SUFFIX).getConstructor(Connection.class).newInstance(connection); return (T) Class.forName(jdbcMapper.getName() + SUFFIX).getConstructor(Connection.class).newInstance(connection);
} catch (Throwable e) { } catch (Throwable e) {
@ -18,7 +18,7 @@ public abstract class JdbcMapperFactory {
} }
} }
public static <T extends JdbcMapper> T create(final Class<T> jdbcMapper) { public static <T> T create(final Class<T> jdbcMapper) {
return create(jdbcMapper, null); return create(jdbcMapper, null);
} }
} }

View File

@ -146,17 +146,36 @@ public class JdbcMapperProcessor extends AbstractProcessor {
} }
w.write("\n\t\tif (this.conn == null)\n" + w.write("\n\t\tif (this.conn == null)\n" +
"\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n" + "\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n" +
"\n\t@Override\n\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n" "\n\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n"
); );
// loop through methods // loop through methods
final Types typeUtils = processingEnv.getTypeUtils();
int cachedPreparedStatements = 0; int cachedPreparedStatements = 0;
ExecutableElement closeMethod = null;
boolean lookupCloseMethod = true;
final boolean defaultCachePreparedStatements;
switch (mapper.cachePreparedStatements()) {
case TRUE:
defaultCachePreparedStatements = true;
break;
case FALSE:
defaultCachePreparedStatements = false;
break;
default:
defaultCachePreparedStatements = (closeMethod = getCloseMethod(genClass)) != null;
lookupCloseMethod = false;
}
for (final Element methodElement : genClass.getEnclosedElements()) { for (final Element methodElement : genClass.getEnclosedElements()) {
// can only implement abstract methods // can only implement abstract methods
if (methodElement.getKind() != ElementKind.METHOD || !methodElement.getModifiers().contains(Modifier.ABSTRACT)) if (methodElement.getKind() != ElementKind.METHOD || !methodElement.getModifiers().contains(Modifier.ABSTRACT))
continue; continue;
final ExecutableElement eeMethod = (ExecutableElement) methodElement; final ExecutableElement eeMethod = (ExecutableElement) methodElement;
if(lookupCloseMethod)
if((closeMethod = getCloseMethod(eeMethod)) != null) {
lookupCloseMethod = false;
continue; // skip close method
}
final JdbcMapper.SQL sql = eeMethod.getAnnotation(JdbcMapper.SQL.class); final JdbcMapper.SQL sql = eeMethod.getAnnotation(JdbcMapper.SQL.class);
if (sql == null || sql.value().isEmpty()) { if (sql == null || sql.value().isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL with non-empty query is required on abstract or interface methods", methodElement); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL with non-empty query is required on abstract or interface methods", methodElement);
@ -199,7 +218,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write(" throws "); w.write(" throws ");
} }
for (final TypeMirror thrownType : thrownTypes) { for (final TypeMirror thrownType : thrownTypes) {
sqlExceptionThrown |= typeUtils.isSameType(thrownType, sqlExceptionType); sqlExceptionThrown |= types.isSameType(thrownType, sqlExceptionType);
w.write(thrownType.toString()); w.write(thrownType.toString());
if (++count != numThrownTypes) if (++count != numThrownTypes)
w.write(", "); w.write(", ");
@ -228,7 +247,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
if (parsedSQl.isSelect()) if (parsedSQl.isSelect())
w.write("\t\tResultSet rs = null;\n"); w.write("\t\tResultSet rs = null;\n");
w.write("\t\ttry {\n\t\t\tps = "); w.write("\t\ttry {\n\t\t\tps = ");
final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(mapper.cachePreparedStatements()); final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(defaultCachePreparedStatements);
if (cachePreparedStatements) { if (cachePreparedStatements) {
w.write("this.prepareStatement("); w.write("this.prepareStatement(");
w.write(Integer.toString(cachedPreparedStatements)); w.write(Integer.toString(cachedPreparedStatements));
@ -286,6 +305,15 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write("\t}\n"); w.write("\t}\n");
} }
// look on super classes and interfaces recursively
if(lookupCloseMethod)
closeMethod = getCloseMethod(genClass);
if(closeMethod == null && (cachedPreparedStatements > 0 || doJndi)) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Jdbc.Mapper extended classes with cachedPreparedStatements or jndiNames must have a public void close() method to override or implement, because they must be closed", genClass);
continue;
}
if (cachedPreparedStatements > 0) { if (cachedPreparedStatements > 0) {
w.write("\n\tprivate final PreparedStatement[] psCache = new PreparedStatement["); w.write("\n\tprivate final PreparedStatement[] psCache = new PreparedStatement[");
w.write(Integer.toString(cachedPreparedStatements)); w.write(Integer.toString(cachedPreparedStatements));
@ -296,13 +324,18 @@ public class JdbcMapperProcessor extends AbstractProcessor {
} }
// close method // close method
w.write("\n\t@Override\n\tpublic void close() {\n\t\t"); if(closeMethod != null) {
if (cachedPreparedStatements > 0) // if cachedPreparedStatements > 0 or doJndi are true, class MUST have a close() method to override as it
w.write("for(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n\t\t"); // MUST be called to clean up
w.write("tryClose(conn);\n"); w.write("\n\t@Override\n\tpublic void close() {\n");
if (doJndi) if (cachedPreparedStatements > 0)
w.write("\t\ttryClose(ctx);\n"); w.write("\t\tfor(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n");
w.write("\t}\n"); if (doJndi)
w.write("\t\ttryClose(ctx);\n\t\tif(ctx != null)\n\t\t\ttryClose(conn);\n");
if(closeMethod.getEnclosingElement().getKind() != ElementKind.INTERFACE && !closeMethod.getEnclosingElement().equals(genClass))
w.write("\t\tsuper.close();\n");
w.write("\t}\n");
}
// end close method // end close method
w.write("}\n"); w.write("}\n");
@ -408,4 +441,32 @@ public class JdbcMapperProcessor extends AbstractProcessor {
return Class.forName(tm.toString()); return Class.forName(tm.toString());
} }
} }
public ExecutableElement getCloseMethod(final TypeElement genClass) {
ExecutableElement ret = null;
for (final Element methodElement : genClass.getEnclosedElements()) {
if((ret = getCloseMethod(methodElement)) != null)
return ret;
}
// superclasses
final TypeMirror superclass = genClass.getSuperclass();
if(superclass.getKind() == TypeKind.DECLARED && (ret = getCloseMethod((TypeElement)types.asElement(superclass))) != null)
return ret;
// interfaces
for(final TypeMirror iface : genClass.getInterfaces()) {
if(iface.getKind() == TypeKind.DECLARED && (ret = getCloseMethod((TypeElement)types.asElement(iface))) != null)
return ret;
}
return ret;
}
public static ExecutableElement getCloseMethod(final Element element) {
return element.getKind() != ElementKind.METHOD ? null : getCloseMethod((ExecutableElement) element);
}
public static ExecutableElement getCloseMethod(final ExecutableElement methodElement) {
return methodElement.getReturnType().getKind() == TypeKind.VOID &&
methodElement.getSimpleName().toString().equals("close") &&
methodElement.getParameters().isEmpty() ? methodElement : null;
}
} }

View File

@ -24,7 +24,7 @@ public class JdbcMapperTest {
@AfterClass @AfterClass
public static void tearDown() throws Throwable { public static void tearDown() throws Throwable {
tryClose(dao); //tryClose(dao);
} }
public PersonDAO getDao() { public PersonDAO getDao() {

View File

@ -2,6 +2,7 @@ package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.dto.FieldPerson; import com.moparisthebest.jdbc.dto.FieldPerson;
import java.io.Closeable;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -13,10 +14,10 @@ import java.util.Map;
*/ */
@JdbcMapper.Mapper( @JdbcMapper.Mapper(
// jndiName = "bob", // jndiName = "bob",
cachePreparedStatements = false // cachePreparedStatements = false
// , sqlParser = SimpleSQLParser.class // , sqlParser = SimpleSQLParser.class
) )
public interface PersonDAO extends JdbcMapper { public interface PersonDAO {
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE last_name = {lastName}") @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE last_name = {lastName}")
int setFirstName(String firstName, String lastName); int setFirstName(String firstName, String lastName);

View File

@ -13,8 +13,8 @@ import java.util.Map;
*/ */
@JdbcMapper.Mapper( @JdbcMapper.Mapper(
// jndiName = "bob", // jndiName = "bob",
cachePreparedStatements = false // cachePreparedStatements = false,
, sqlParser = PrestoSQLParser.class sqlParser = PrestoSQLParser.class
) )
public interface PrestoPersonDAO extends PersonDAO { public interface PrestoPersonDAO extends PersonDAO {

View File

@ -24,7 +24,7 @@ public class PrestoPersonDAOTest extends JdbcMapperTest {
@AfterClass @AfterClass
public static void tearDown() throws Throwable { public static void tearDown() throws Throwable {
tryClose(dao); //tryClose(dao);
} }
public PersonDAO getDao() { public PersonDAO getDao() {