Tweak JdbcMapperFactory, now used for QueryMapper and JdbcMapper generated classes

This commit is contained in:
Travis Burtrum 2017-07-03 03:05:53 -04:00
parent 08e2c9354d
commit e246af935f
6 changed files with 109 additions and 91 deletions

View File

@ -8,6 +8,8 @@ import javax.naming.NamingException;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
@ -20,9 +22,23 @@ public class JdbcMapperFactory<T> implements Factory<T> {
static final String SUFFIX = "Bean"; static final String SUFFIX = "Bean";
static {
try{
final Class<?> ensureContext = Class.forName(System.getProperty("QueryMapper.ensureContext.class", System.getProperty("JdbcMapper.ensureContext.class", "com.gcl.containerless.EnsureContext")));
final Method method = ensureContext.getMethod(System.getProperty("QueryMapper.ensureContext.method", System.getProperty("JdbcMapper.ensureContext.method", "setup")));
method.invoke(null);
}catch(Throwable e){
// ignore
//e.printStackTrace();
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Class<? extends T> getImplementationClass(final Class<T> jdbcMapper) throws ClassNotFoundException { public static <T> Class<? extends T> getImplementationClass(final Class<T> jdbcMapper) throws ClassNotFoundException {
if(jdbcMapper.isInterface() || Modifier.isAbstract(jdbcMapper.getModifiers()))
return (Class<? extends T>) Class.forName(jdbcMapper.getName() + SUFFIX); return (Class<? extends T>) Class.forName(jdbcMapper.getName() + SUFFIX);
else
return jdbcMapper;
} }
public static <T> Constructor<? extends T> getConnectionConstructor(final Class<T> jdbcMapper) throws ClassNotFoundException, NoSuchMethodException { public static <T> Constructor<? extends T> getConnectionConstructor(final Class<T> jdbcMapper) throws ClassNotFoundException, NoSuchMethodException {
@ -45,6 +61,14 @@ public class JdbcMapperFactory<T> implements Factory<T> {
} }
} }
public static <T> T create(final Class<T> jdbcMapper, final Factory<Connection> connectionFactory) {
try {
return getFactoryConstructor(jdbcMapper).newInstance(connectionFactory);
} catch (Throwable e) {
throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e);
}
}
public static <T> T create(final Class<T> jdbcMapper) { public static <T> T create(final Class<T> jdbcMapper) {
try { try {
return getDefaultConstructor(jdbcMapper).newInstance(); return getDefaultConstructor(jdbcMapper).newInstance();
@ -115,10 +139,22 @@ public class JdbcMapperFactory<T> implements Factory<T> {
; ;
} }
public static <T> Factory<T> of(final Class<T> jdbcMapper) {
return new JdbcMapperFactory<T>(jdbcMapper);
}
public static <T> Factory<T> of(final Class<T> jdbcMapper, final Factory<Connection> connectionFactory) {
return new JdbcMapperFactory<T>(jdbcMapper, connectionFactory);
}
public static <T> Factory<T> of(final Class<T> jdbcMapper, final String jndiName) {
return of(jdbcMapper, connectionFactory(jndiName));
}
private final Constructor<? extends T> constructor; private final Constructor<? extends T> constructor;
private final Object[] args; private final Object[] args;
public JdbcMapperFactory(final Class<T> jdbcMapper) { private JdbcMapperFactory(final Class<T> jdbcMapper) {
try { try {
this.constructor = getDefaultConstructor(jdbcMapper); this.constructor = getDefaultConstructor(jdbcMapper);
this.args = null; this.args = null;
@ -127,39 +163,19 @@ public class JdbcMapperFactory<T> implements Factory<T> {
} }
} }
public JdbcMapperFactory(final Constructor<? extends T> constructor, final Object... args) { private JdbcMapperFactory(final Class<T> jdbcMapper, final Factory<Connection> connectionFactory) {
if (constructor == null) if (jdbcMapper == null)
throw new NullPointerException("constructor must be non-null"); throw new NullPointerException("jdbcMapper must be non-null");
this.constructor = constructor;
this.args = args;
}
public JdbcMapperFactory(final Class<T> queryMapperClass, final Factory<Connection> connectionFactory) {
if (queryMapperClass == null)
throw new NullPointerException("queryMapperClass must be non-null");
if (connectionFactory == null) if (connectionFactory == null)
throw new NullPointerException("connectionFactory must be non-null"); throw new NullPointerException("connectionFactory must be non-null");
try { try {
this.constructor = queryMapperClass.getConstructor(Factory.class); this.constructor = getFactoryConstructor(jdbcMapper);
} catch (NoSuchMethodException e) { } catch (Throwable e) {
throw new RuntimeException("queryMapperClass must have a constructor that takes Factory<Connection>", e); throw new RuntimeException("jdbcMapper must have a constructor that takes Factory<Connection>", e);
} }
this.args = new Object[]{connectionFactory}; this.args = new Object[]{connectionFactory};
} }
public JdbcMapperFactory(final Class<T> queryMapperClass, final String jndiName) {
if (queryMapperClass == null)
throw new NullPointerException("queryMapperClass must be non-null");
if (jndiName == null)
throw new NullPointerException("jndiName must be non-null");
try {
this.constructor = queryMapperClass.getConstructor(String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException("queryMapperClass must have a constructor that takes String", e);
}
this.args = new Object[]{jndiName};
}
@Override @Override
public T create() throws SQLException { public T create() throws SQLException {
try { try {

View File

@ -149,10 +149,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write(packageName); w.write(packageName);
w.write(";\n\n"); w.write(";\n\n");
} }
if (doJndi) { w.write("import com.moparisthebest.jdbc.Factory;\n\n");
w.write("import javax.naming.InitialContext;\n");
w.write("import javax.sql.DataSource;\n");
}
w.write("import java.sql.*;\n\n"); w.write("import java.sql.*;\n\n");
w.write("import static com.moparisthebest.jdbc.util.ResultSetUtil.*;\n"); w.write("import static com.moparisthebest.jdbc.util.ResultSetUtil.*;\n");
w.write("import static com.moparisthebest.jdbc.TryClose.tryClose;\n\n"); w.write("import static com.moparisthebest.jdbc.TryClose.tryClose;\n\n");
@ -164,36 +161,16 @@ public class JdbcMapperProcessor extends AbstractProcessor {
w.write(" extends "); w.write(" extends ");
} }
w.write(genClass.getSimpleName().toString()); w.write(genClass.getSimpleName().toString());
w.write(" {\n\n\tprivate final Connection conn;\n"); w.write(" {\n\n\tprivate final Connection conn;\n\tprivate final boolean closeConn;\n\n");
if (doJndi) { if (doJndi) {
w.write("\tprivate final InitialContext ctx;\n\n\tpublic "); w.write("\tprivate static final Factory<Connection> _conFactory = com.moparisthebest.jdbc.codegen.JdbcMapperFactory.connectionFactory(\"");
w.append(mapper.jndiName()).append("\");\n\n\tpublic ");
w.write(className); w.write(className);
w.write("() {\n\t\tthis(null);\n\t}\n"); w.write("() throws SQLException {\n\t\tthis(_conFactory);\n\t}\n");
} }
w.write("\n\tpublic "); w.write("\n\tpublic ");
w.write(className); w.write(className);
w.write("(Connection conn) {\n\t\t"); w.write("(Connection conn) {\n\t\tthis.conn = conn;\n\t\tthis.closeConn = false;\n\t\tif (this.conn == null)\n" +
if (doJndi) {
w.write("InitialContext ctx = null;\n" +
"\t\tif (conn == null)\n" +
"\t\t\ttry {\n" +
"\t\t\t\tctx = new InitialContext();\n" +
"\t\t\t\tDataSource ds = (DataSource) ctx.lookup(\"");
w.write(mapper.jndiName()); // todo: escape this? I don't think anyone needs that, for now...
w.write("\");\n" +
"\t\t\t\tconn = ds.getConnection();\n" +
"\t\t\t} catch (Throwable e) {\n" +
"\t\t\t\ttryClose(ctx);\n" +
"\t\t\t\ttryClose(conn);\n" +
"\t\t\t\tthrow new RuntimeException(e);\n" +
"\t\t\t}\n" +
"\t\tthis.conn = conn;\n" +
"\t\tthis.ctx = ctx;"
);
} else {
w.write("this.conn = conn;");
}
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\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n" "\n\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n"
); );
@ -458,10 +435,22 @@ public class JdbcMapperProcessor extends AbstractProcessor {
if (cachedPreparedStatements > 0) if (cachedPreparedStatements > 0)
w.write("\t\tfor(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n"); w.write("\t\tfor(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n");
if (doJndi) if (doJndi)
w.write("\t\ttryClose(ctx);\n\t\tif(ctx != null)\n\t\t\ttryClose(conn);\n"); w.write("\t\tif(closeConn)\n\t\t\ttryClose(conn);\n");
if (closeMethod.getEnclosingElement().getKind() != ElementKind.INTERFACE && !closeMethod.getEnclosingElement().equals(genClass)) if (closeMethod.getEnclosingElement().getKind() != ElementKind.INTERFACE && !closeMethod.getEnclosingElement().equals(genClass))
w.write("\t\tsuper.close();\n"); w.write("\t\tsuper.close();\n");
w.write("\t}\n"); w.write("\t}\n");
// and we can create constructors that set closeConn to true!
w.write("\n\tpublic ");
w.write(className);
w.write("(final Factory<Connection> connectionFactory) throws SQLException {\n\t\tthis.conn = connectionFactory.create();\n\t\tthis.closeConn = true;\n\t\tif (this.conn == null)\n" +
"\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n"
);
w.write("\n\tpublic ");
w.write(className);
w.write("(final String jndiName) throws SQLException {\n\t\tthis(com.moparisthebest.jdbc.codegen.JdbcMapperFactory.connectionFactory(jndiName));\n\t}\n"
);
} }
// end close method // end close method

View File

@ -28,7 +28,7 @@ import java.time.*;
// , sqlParser = SimpleSQLParser.class // , sqlParser = SimpleSQLParser.class
, allowReflection = JdbcMapper.OptionalBool.TRUE , allowReflection = JdbcMapper.OptionalBool.TRUE
) )
public interface PersonDAO extends Closeable { public interface PersonDAO extends JdbcMapper {
@JdbcMapper.SQL("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)") @JdbcMapper.SQL("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)")
void createTablePerson(); void createTablePerson();

View File

@ -0,0 +1,39 @@
package com.moparisthebest.jdbc.codegen;
import com.moparisthebest.jdbc.*;
import com.moparisthebest.jdbc.dto.Person;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1;
import static org.junit.Assert.assertEquals;
/**
* Created by mopar on 7/3/17.
*/
public class PersonDAOQueryRunnerTest {
public static final QueryRunner<PersonDAO> pqr = QueryRunner.withRetry(JdbcMapperFactory.of(PersonDAO.class, new Factory<Connection>() {
@Override
public Connection create() throws SQLException {
return QueryMapperTest.getConnection();
}
}), 10, QueryRunner.fixedDelay(5).withJitter(5));
@Test
public void testPerson() throws Exception {
//final QueryRunner<ListQueryMapper> lqr = pqr.withFactory(() -> new ListQueryMapper(QueryMapperTest::getConnection));
assertEquals(fieldPerson1, pqr.runRetryFuture(new QueryRunner.Runner<PersonDAO, Person>() {
@Override
public Person run(final PersonDAO dao) throws SQLException {
if(Math.random() < 0.5) {
System.out.println("fake fail");
throw new SQLException("fake 50% failure rate");
}
return dao.getPerson(fieldPerson1.getPersonNo());
}
}).get());
}
}

View File

@ -1,13 +1,10 @@
package com.moparisthebest.jdbc; package com.moparisthebest.jdbc;
import com.moparisthebest.jdbc.codegen.JdbcMapper; import com.moparisthebest.jdbc.codegen.JdbcMapper;
import com.moparisthebest.jdbc.codegen.JdbcMapperFactory;
import com.moparisthebest.jdbc.util.ResultSetIterable; import com.moparisthebest.jdbc.util.ResultSetIterable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.io.*; import java.io.*;
import java.lang.reflect.Method;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
//IFJAVA8_START //IFJAVA8_START
@ -22,27 +19,16 @@ public class QueryMapper implements JdbcMapper {
public static final Object noBind = new Object(); public static final Object noBind = new Object();
public static final ResultSetMapper defaultRsm = new ResultSetMapper(); public static final ResultSetMapper defaultRsm = new ResultSetMapper();
static {
try{
final Class<?> ensureContext = Class.forName(System.getProperty("QueryMapper.ensureContext.class", "com.gcl.containerless.EnsureContext"));
final Method method = ensureContext.getMethod(System.getProperty("QueryMapper.ensureContext.method", "setup"));
method.invoke(null);
}catch(Throwable e){
// ignore
//e.printStackTrace();
}
}
protected final ResultSetMapper cm; protected final ResultSetMapper cm;
protected final Connection conn; protected final Connection conn;
protected final Context context;
protected final boolean closeConn; protected final boolean closeConn;
protected QueryMapper(Connection conn, final String jndiName, final Factory<Connection> factory, final ResultSetMapper cm) { protected QueryMapper(Connection conn, final String jndiName, Factory<Connection> factory, final ResultSetMapper cm) {
this.cm = cm == null ? defaultRsm : cm; this.cm = cm == null ? defaultRsm : cm;
boolean closeConn = false; boolean closeConn = false;
Context context = null;
if(conn == null) { if(conn == null) {
if(factory == null && jndiName != null)
factory = JdbcMapperFactory.connectionFactory(jndiName);
if (factory != null) { if (factory != null) {
try { try {
conn = factory.create(); conn = factory.create();
@ -50,21 +36,11 @@ public class QueryMapper implements JdbcMapper {
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("factory failed to create connection", e); throw new RuntimeException("factory failed to create connection", e);
} }
} else if (jndiName != null)
try {
context = new InitialContext();
DataSource ds = (DataSource) context.lookup(jndiName);
conn = ds.getConnection();
closeConn = true;
} catch (Throwable e) {
tryClose(context);
throw new RuntimeException("JNDI lookup failed to create connection", e);
} }
} }
if (conn == null) if (conn == null)
throw new NullPointerException("Connection needs to be non-null for QueryMapper..."); throw new NullPointerException("Connection needs to be non-null for QueryMapper...");
this.conn = conn; this.conn = conn;
this.context = context;
this.closeConn = closeConn; this.closeConn = closeConn;
} }
@ -98,7 +74,6 @@ public class QueryMapper implements JdbcMapper {
protected QueryMapper() { protected QueryMapper() {
this.cm = null; this.cm = null;
this.conn = null; this.conn = null;
this.context = null;
this.closeConn = false; this.closeConn = false;
} }
@ -106,7 +81,6 @@ public class QueryMapper implements JdbcMapper {
public void close() { public void close() {
if (closeConn) { if (closeConn) {
tryClose(conn); tryClose(conn);
tryClose(context);
} }
} }

View File

@ -14,7 +14,7 @@ import static com.moparisthebest.jdbc.QueryMapperTest.*;
* Created by mopar on 7/1/17. * Created by mopar on 7/1/17.
*/ */
public class QueryRunnerTest { public class QueryRunnerTest {
public static final QueryRunner<QueryMapper> qr = QueryRunner.withRetry(new JdbcMapperFactory<QueryMapper>(QueryMapper.class, new Factory<Connection>() { public static final QueryRunner<QueryMapper> qr = QueryRunner.withRetry(JdbcMapperFactory.of(QueryMapper.class, new Factory<Connection>() {
@Override @Override
public Connection create() throws SQLException { public Connection create() throws SQLException {
return QueryMapperTest.getConnection(); return QueryMapperTest.getConnection();