diff --git a/common/src/main/java/com/moparisthebest/jdbc/Factory.java b/common/src/main/java/com/moparisthebest/jdbc/Factory.java new file mode 100644 index 0000000..93e11ec --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/Factory.java @@ -0,0 +1,10 @@ +package com.moparisthebest.jdbc; + +import java.sql.SQLException; + +/** + * Created by mopar on 7/1/17. + */ +public interface Factory { + T create() throws SQLException; +} diff --git a/common/src/main/java/com/moparisthebest/jdbc/QueryRunner.java b/common/src/main/java/com/moparisthebest/jdbc/QueryRunner.java new file mode 100644 index 0000000..4f9914b --- /dev/null +++ b/common/src/main/java/com/moparisthebest/jdbc/QueryRunner.java @@ -0,0 +1,73 @@ +package com.moparisthebest.jdbc; + +import com.moparisthebest.jdbc.codegen.JdbcMapper; + +import java.sql.SQLException; + +import static com.moparisthebest.jdbc.TryClose.tryClose; + + +/** + * Created by mopar on 6/30/17. + */ +public class QueryRunner { + + private final Factory factory; + + public QueryRunner(final Factory factory) { + if(factory == null) + throw new NullPointerException("factory must be non-null"); + this.factory = factory; + } + + public E run(final Runner query) throws SQLException { + if(query == null) + throw new NullPointerException("query must be non-null"); + T dao = null; + try { + dao = factory.create(); + return query.run(dao); + } finally { + tryClose(dao); + } + } + + public E runInTransaction(final Runner query) throws SQLException { + if(query == null) + throw new NullPointerException("query must be non-null"); + T dao = null; + try { + dao = factory.create(); + dao.getConnection().setAutoCommit(false); + final E ret = query.run(dao); + dao.getConnection().commit(); + return ret; + } catch (final Throwable e) { + if (dao != null) { + try { + dao.getConnection().rollback(); + } catch(SQLException excep) { + // ignore to throw original + } + } + if(e instanceof SQLException) + throw (SQLException) e; + if(e instanceof RuntimeException) + throw (RuntimeException) e; + throw new RuntimeException("odd error should never happen", e); + } finally { + if (dao != null) { + try { + dao.getConnection().setAutoCommit(true); + } catch(SQLException excep) { + // ignore + } + tryClose(dao); + } + } + } + + public static interface Runner { + E run(T dao) throws SQLException; + } +} diff --git a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java index e0603e0..c3d63f5 100644 --- a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java +++ b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java @@ -1,24 +1,107 @@ package com.moparisthebest.jdbc.codegen; +import com.moparisthebest.jdbc.Factory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.sql.Connection; +import java.sql.SQLException; /** * Created by mopar on 5/24/17. */ -public abstract class JdbcMapperFactory { +public class JdbcMapperFactory implements Factory { static final String SUFFIX = "Bean"; @SuppressWarnings("unchecked") + public static Class getImplementationClass(final Class jdbcMapper) throws ClassNotFoundException { + return (Class) Class.forName(jdbcMapper.getName() + SUFFIX); + } + + public static Constructor getConnectionConstructor(final Class jdbcMapper) throws ClassNotFoundException, NoSuchMethodException { + return getImplementationClass(jdbcMapper).getConstructor(Connection.class); + } + + public static Constructor getDefaultConstructor(final Class jdbcMapper) throws ClassNotFoundException, NoSuchMethodException { + return getImplementationClass(jdbcMapper).getConstructor(); + } + + public static Constructor getFactoryConstructor(final Class jdbcMapper) throws ClassNotFoundException, NoSuchMethodException { + return getImplementationClass(jdbcMapper).getConstructor(Factory.class); + } + public static T create(final Class jdbcMapper, final Connection connection) { try { - return (T) Class.forName(jdbcMapper.getName() + SUFFIX).getConstructor(Connection.class).newInstance(connection); + return getConnectionConstructor(jdbcMapper).newInstance(connection); } catch (Throwable e) { throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); } } public static T create(final Class jdbcMapper) { - return create(jdbcMapper, null); + try { + return getDefaultConstructor(jdbcMapper).newInstance(); + } catch (Throwable e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } + } + + private final Constructor constructor; + private final Object[] args; + + public JdbcMapperFactory(final Class jdbcMapper) { + try { + this.constructor = getDefaultConstructor(jdbcMapper); + this.args = null; + } catch (Throwable e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } + } + + public JdbcMapperFactory(final Constructor constructor, final Object... args) { + if(constructor == null) + throw new NullPointerException("constructor must be non-null"); + this.constructor = constructor; + this.args = args; + } + + public JdbcMapperFactory(final Class queryMapperClass, final Factory connectionFactory) { + if(queryMapperClass == null) + throw new NullPointerException("queryMapperClass must be non-null"); + if(connectionFactory == null) + throw new NullPointerException("connectionFactory must be non-null"); + try { + this.constructor = queryMapperClass.getConstructor(Factory.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException("queryMapperClass must have a constructor that takes Factory", e); + } + this.args = new Object[]{connectionFactory}; + } + + public JdbcMapperFactory(final Class 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 + public T create() throws SQLException { + try { + return this.constructor.newInstance(args); + } catch (InstantiationException e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } } } 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 fbb7c4a..fdffc01 100644 --- a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java @@ -22,7 +22,7 @@ import java.time.*; * Created by mopar on 5/24/17. */ @JdbcMapper.Mapper( -// jndiName = "bob", + jndiName = "bob", // databaseType = JdbcMapper.DatabaseType.ORACLE cachePreparedStatements = JdbcMapper.OptionalBool.FALSE // , sqlParser = SimpleSQLParser.class diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingQueryMapper.java index 738ecc0..d24f3c4 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/CachingQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/CachingQueryMapper.java @@ -22,8 +22,8 @@ public class CachingQueryMapper extends QueryMapper { protected final Map cache; - protected CachingQueryMapper(Connection conn, String jndiName, ResultSetMapper cm, final int maxEntries) { - super(conn, jndiName, cm); + protected CachingQueryMapper(Connection conn, String jndiName, Factory factory, ResultSetMapper cm, final int maxEntries) { + super(conn, jndiName, factory, cm); if (maxEntries > 0) { // we want a limited cache final float loadFactor = 0.75f; // default for HashMaps // if we set the initialCapacity this way, nothing should ever need re-sized @@ -43,40 +43,56 @@ public class CachingQueryMapper extends QueryMapper { cache = new HashMap(); } + protected CachingQueryMapper(Connection conn, String jndiName, Factory factory, ResultSetMapper cm) { + this(conn, jndiName, factory, cm, 20); // default size of 20 + } + public CachingQueryMapper(Connection conn, ResultSetMapper cm, final int maxEntries) { - this(conn, null, cm, maxEntries); + this(conn, null, null, cm, maxEntries); } public CachingQueryMapper(Connection conn, final int maxEntries) { - this(conn, null, null, maxEntries); + this(conn, null, null, null, maxEntries); } public CachingQueryMapper(String jndiName, ResultSetMapper cm, final int maxEntries) { - this(null, jndiName, cm, maxEntries); + this(null, jndiName, null, cm, maxEntries); } public CachingQueryMapper(String jndiName, final int maxEntries) { - this(null, jndiName, null, maxEntries); + this(null, jndiName, null, null, maxEntries); } - protected CachingQueryMapper(Connection conn, String jndiName, ResultSetMapper cm) { - this(conn, jndiName, cm, 20); // default size of 20 + public CachingQueryMapper(Factory factory, ResultSetMapper cm, final int maxEntries) { + this(null, null, factory, cm, maxEntries); + } + + public CachingQueryMapper(Factory factory, final int maxEntries) { + this(null, null, factory, null, maxEntries); } public CachingQueryMapper(Connection conn, ResultSetMapper cm) { - this(conn, null, cm); + this(conn, null, null, cm); } public CachingQueryMapper(Connection conn) { - this(conn, null, null); + this(conn, null, null, null); } public CachingQueryMapper(String jndiName, ResultSetMapper cm) { - this(null, jndiName, cm); + this(null, jndiName, null, cm); } public CachingQueryMapper(String jndiName) { - this(null, jndiName, null); + this(null, jndiName, null, null); + } + + public CachingQueryMapper(Factory factory, ResultSetMapper cm) { + this(null, null, factory, cm); + } + + public CachingQueryMapper(Factory factory) { + this(null, null, factory, null); } protected PreparedStatement getPreparedStatement(String sql) throws SQLException { diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java index bb45f71..4b130d1 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java @@ -34,26 +34,26 @@ public class ListQueryMapper extends QueryMapper { public static final String inListReplace = "{inList}"; - private ListQueryMapper(Connection conn, String jndiName, QueryMapper delegate, ResultSetMapper cm, InList inList) { + private ListQueryMapper(Connection conn, String jndiName, Factory factory, QueryMapper delegate, ResultSetMapper cm, InList inList) { this.inList = inList; - this.delegate = delegate == null ? new QueryMapper(conn, jndiName, cm) : + this.delegate = delegate == null ? new QueryMapper(conn, jndiName, factory, cm) : (delegate instanceof ListQueryMapper ? ((ListQueryMapper)delegate).delegate : delegate); } public ListQueryMapper(QueryMapper delegate, InList inList) { - this(null, null, delegate, null, inList); + this(null, null, null, delegate, null, inList); } public ListQueryMapper(QueryMapper delegate) { - this(null, null, delegate, null, defaultInList); + this(null, null, null, delegate, null, defaultInList); } public ListQueryMapper(Connection conn, InList inList) { - this(conn, null, null, null, inList); + this(conn, null, null, null, null, inList); } public ListQueryMapper(Connection conn, ResultSetMapper cm, InList inList) { - this(conn, null, null, cm, inList); + this(conn, null, null, null, cm, inList); } public ListQueryMapper(Connection conn) { @@ -65,11 +65,11 @@ public class ListQueryMapper extends QueryMapper { } public ListQueryMapper(String jndiName, InList inList) { - this(null, jndiName, null, null, inList); + this(null, jndiName, null, null, null, inList); } public ListQueryMapper(String jndiName, ResultSetMapper cm, InList inList) { - this(null, jndiName, null, cm, inList); + this(null, jndiName, null, null, cm, inList); } public ListQueryMapper(String jndiName) { @@ -80,6 +80,23 @@ public class ListQueryMapper extends QueryMapper { this(jndiName, cm, defaultInList); } + public ListQueryMapper(Factory factory, InList inList) { + this(null, null, factory, null, null, inList); + } + + public ListQueryMapper(Factory factory, ResultSetMapper cm, InList inList) { + this(null, null, factory, null, cm, inList); + } + + public ListQueryMapper(Factory factory) { + this(factory, defaultInList); + } + + public ListQueryMapper(Factory factory, ResultSetMapper cm) { + this(factory, cm, defaultInList); + } + + // todo: get rid of wrap, cause, how do you know to close it or not? :'( public static ListQueryMapper wrap(final QueryMapper qm){ return qm instanceof ListQueryMapper ? (ListQueryMapper)qm : new ListQueryMapper(qm); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java index 317a55a..6055b85 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/NullQueryMapper.java @@ -16,25 +16,25 @@ public class NullQueryMapper extends QueryMapper { protected final boolean verbose; protected final QueryMapper delegate; - private NullQueryMapper(Connection conn, String jndiName, QueryMapper delegate, ResultSetMapper cm, boolean verbose) { + private NullQueryMapper(Connection conn, String jndiName, Factory factory, QueryMapper delegate, ResultSetMapper cm, boolean verbose) { this.verbose = verbose; - this.delegate = delegate == null ? new QueryMapper(conn, jndiName, cm) : delegate; + this.delegate = delegate == null ? new QueryMapper(conn, jndiName, factory, cm) : delegate; } public NullQueryMapper(QueryMapper delegate, boolean verbose) { - this(null, null, delegate, null, verbose); + this(null, null, null, delegate, null, verbose); } public NullQueryMapper(QueryMapper delegate) { - this(null, null, delegate, null, true); + this(null, null, null, delegate, null, true); } public NullQueryMapper(Connection conn, boolean verbose) { - this(conn, null, null, null, verbose); + this(conn, null, null, null, null, verbose); } public NullQueryMapper(Connection conn, ResultSetMapper cm, boolean verbose) { - this(conn, null, null, cm, verbose); + this(conn, null, null, null, cm, verbose); } public NullQueryMapper(Connection conn) { @@ -46,11 +46,11 @@ public class NullQueryMapper extends QueryMapper { } public NullQueryMapper(String jndiName, boolean verbose) { - this(null, jndiName, null, null, verbose); + this(null, jndiName, null, null, null, verbose); } public NullQueryMapper(String jndiName, ResultSetMapper cm, boolean verbose) { - this(null, jndiName, null, cm, verbose); + this(null, jndiName, null, null, cm, verbose); } public NullQueryMapper(String jndiName) { @@ -61,6 +61,23 @@ public class NullQueryMapper extends QueryMapper { this(jndiName, cm, true); } + public NullQueryMapper(Factory factory, boolean verbose) { + this(null, null, factory, null, null, verbose); + } + + public NullQueryMapper(Factory factory, ResultSetMapper cm, boolean verbose) { + this(null, null, factory, null, cm, verbose); + } + + public NullQueryMapper(Factory factory) { + this(factory, true); + } + + public NullQueryMapper(Factory factory, ResultSetMapper cm) { + this(factory, cm, true); + } + + // todo: remove this refer to ListQueryMapper for why public static NullQueryMapper wrap(final QueryMapper qm){ return qm instanceof NullQueryMapper ? (NullQueryMapper)qm : new NullQueryMapper(qm); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java index 52f235b..85e77b6 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java @@ -36,29 +36,40 @@ public class QueryMapper implements JdbcMapper { protected final ResultSetMapper cm; protected final Connection conn; protected final Context context; + protected final boolean closeConn; - protected QueryMapper(Connection conn, String jndiName, ResultSetMapper cm) { + protected QueryMapper(Connection conn, final String jndiName, final Factory factory, final ResultSetMapper cm) { this.cm = cm == null ? defaultRsm : cm; + boolean closeConn = false; Context context = null; - if (conn == null && jndiName != null) - try { - context = new InitialContext(); - DataSource ds = (DataSource) context.lookup(jndiName); - conn = ds.getConnection(); - } catch (Throwable e) { - e.printStackTrace(); - tryClose(conn); - } finally { - tryClose(context); - } + if(conn == null) { + if (factory != null) { + try { + conn = factory.create(); + closeConn = true; + } catch (SQLException 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) + throw new NullPointerException("Connection needs to be non-null for QueryMapper..."); this.conn = conn; this.context = context; - if (this.conn == null) - throw new NullPointerException("Connection needs to be non-null for QueryMapper..."); + this.closeConn = closeConn; } public QueryMapper(Connection conn, ResultSetMapper cm) { - this(conn, null, cm); + this(conn, null, null, cm); } public QueryMapper(Connection conn) { @@ -66,13 +77,21 @@ public class QueryMapper implements JdbcMapper { } public QueryMapper(String jndiName, ResultSetMapper cm) { - this(null, jndiName, cm); + this(null, jndiName, null, cm); } public QueryMapper(String jndiName) { this(jndiName, null); } + public QueryMapper(Factory factory, ResultSetMapper cm) { + this(null, null, factory, cm); + } + + public QueryMapper(Factory factory) { + this(factory, null); + } + /** * Only meant to be called by implementing classes */ @@ -80,11 +99,12 @@ public class QueryMapper implements JdbcMapper { this.cm = null; this.conn = null; this.context = null; + this.closeConn = false; } @Override public void close() { - if (context != null) { + if (closeConn) { tryClose(conn); tryClose(context); } diff --git a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java index 0879f3d..e497adf 100644 --- a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java +++ b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java @@ -108,7 +108,7 @@ public class QueryMapperTest { } } - public static Connection getConnection() throws Throwable { + public static Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:derby:memory:derbyDB;create=true"); } diff --git a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java new file mode 100644 index 0000000..f73ac5d --- /dev/null +++ b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java @@ -0,0 +1,62 @@ +package com.moparisthebest.jdbc; + +import com.moparisthebest.jdbc.codegen.JdbcMapperFactory; +import com.moparisthebest.jdbc.dto.Person; +import org.junit.Assert; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.SQLException; + +import static com.moparisthebest.jdbc.QueryMapperTest.*; + +/** + * Created by mopar on 7/1/17. + */ +public class QueryRunnerTest { + public static final QueryRunner qr = new QueryRunner(new JdbcMapperFactory(QueryMapper.class, new Factory() { + @Override + public Connection create() throws SQLException { + return QueryMapperTest.getConnection(); + } + })); + + private void testPerson(final Person expected, final String query) throws Throwable { + final Person actual = qr.runInTransaction(new QueryRunner.Runner() { + @Override + public Person run(final QueryMapper qm) throws SQLException { + return qm.toObject(query, expected.getClass(), expected.getPersonNo()); + } + }); + /* + System.out.println("expected: " + expected); + System.out.println("actual: " + actual); + */ + Assert.assertEquals(expected, actual); + } + + @Test + public void testFieldRegularPerson() throws Throwable { + testPerson(fieldPerson1, personRegular); + } + + @Test + public void testFieldRegularAndUnderscore() throws Throwable { + testPerson(fieldBoss1, bossRegularAndUnderscore); + } + + @Test + public void testFieldRegularAndUnderscoreReverse() throws Throwable { + testPerson(fieldBoss1, bossRegularAndUnderscoreReverse); + } + + @Test + public void testFieldRegular() throws Throwable { + testPerson(fieldBoss2, bossRegular); + } + + @Test + public void testFieldUnderscore() throws Throwable { + testPerson(fieldBoss3, bossUnderscore); + } +}