From af0c0f348313e5b55d7beeb75d6d71224c3e2947 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Wed, 23 May 2018 23:41:19 -0400 Subject: [PATCH] Implement OptimalInList.java which picks the optimal in list based on connection type --- .travis.yml | 8 ++ .../com/moparisthebest/jdbc/ArrayInList.java | 5 + .../com/moparisthebest/jdbc/BindInList.java | 5 + .../java/com/moparisthebest/jdbc/InList.java | 7 ++ .../moparisthebest/jdbc/ListQueryMapper.java | 38 ++++++- .../moparisthebest/jdbc/OptimalInList.java | 102 ++++++++++++++++++ .../jdbc/OracleArrayInList.java | 21 ++-- .../jdbc/codegen/QueryMapperQmDao.java | 72 +------------ .../moparisthebest/jdbc/QueryMapperTest.java | 6 +- 9 files changed, 175 insertions(+), 89 deletions(-) create mode 100644 querymapper/src/main/java/com/moparisthebest/jdbc/OptimalInList.java diff --git a/.travis.yml b/.travis.yml index bebd284..967707a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,9 +33,15 @@ script: - docker ps -a # java8+ supports everything - mvn -B -pl '!test' clean install || travis_terminate 1; + # everything against BIND - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' '-DjdbcUrl4=jdbc:sqlserver://localhost:1433;databaseName=master;username=sa;password=;' || travis_terminate 1; + # everything against BIND jdbcMapper, but OPTIMAL queryMapper + - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND -DqueryMapper.databaseType=OPTIMAL --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' '-DjdbcUrl4=jdbc:sqlserver://localhost:1433;databaseName=master;username=sa;password=;' || travis_terminate 1; + # h2 and postgre against ANY - mvn -B -pl test clean test -DjdbcMapper.databaseType=ANY '-DjdbcUrl=h2' '-DjdbcUrl1=jdbc:postgresql:test_db' || travis_terminate 1; + # oracle against ORACLE - mvn -B -pl test clean test -DjdbcMapper.databaseType=ORACLE --settings .travis-settings.xml -P oracle '-DjdbcUrl=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' || travis_terminate 1; + # hsql against UNNEST - mvn -B -pl test clean test -DjdbcMapper.databaseType=UNNEST '-DjdbcUrl=hsqldb' || travis_terminate 1; matrix: @@ -49,6 +55,7 @@ matrix: # java6 doesn't support ms-sql at all, and doesn't support h2 with ANY - mvn -B -pl '!test' clean install || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' || travis_terminate 1; + - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND -DqueryMapper.databaseType=OPTIMAL --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=ANY '-DjdbcUrl=jdbc:postgresql:test_db' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=ORACLE --settings .travis-settings.xml -P oracle '-DjdbcUrl=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=UNNEST '-DjdbcUrl=hsqldb' || travis_terminate 1; @@ -59,6 +66,7 @@ matrix: # java7 doesn't support h2 with ANY - mvn -B -pl '!test' clean install || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' '-DjdbcUrl4=jdbc:sqlserver://localhost:1433;databaseName=master;username=sa;password=;' || travis_terminate 1; + - mvn -B -pl test clean test -DjdbcMapper.databaseType=BIND -DqueryMapper.databaseType=OPTIMAL --settings .travis-settings.xml -P oracle '-DjdbcUrl1=jdbc:postgresql:test_db' '-DjdbcUrl2=jdbc:mariadb://127.0.0.1:3306/test_db?user=root' '-DjdbcUrl3=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' '-DjdbcUrl4=jdbc:sqlserver://localhost:1433;databaseName=master;username=sa;password=;' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=ANY '-DjdbcUrl=jdbc:postgresql:test_db' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=ORACLE --settings .travis-settings.xml -P oracle '-DjdbcUrl=jdbc:oracle:thin:travis_test/travis_test@127.0.0.1:1521/xe' || travis_terminate 1; - mvn -B -pl test clean test -DjdbcMapper.databaseType=UNNEST '-DjdbcUrl=hsqldb' || travis_terminate 1; diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ArrayInList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ArrayInList.java index c1fdc68..84306b7 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/ArrayInList.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ArrayInList.java @@ -29,6 +29,11 @@ public class ArrayInList implements InList { this(JdbcMapper.DatabaseType.ANY.arrayNumberTypeName, JdbcMapper.DatabaseType.ANY.arrayStringTypeName); } + @Override + public InList instance(Connection conn) { + return this; + } + protected String columnAppendIn(final String columnName) { return "(" + columnName + " = ANY(?))"; } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java index 2d8b50a..5cd4aaf 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/BindInList.java @@ -28,6 +28,11 @@ public class BindInList implements InList { this(defaultMaxSize); } + @Override + public InList instance(Connection conn) { + return this; + } + public InListObject inList(final Connection conn, final String columnName, final Collection values) { return values == null || values.isEmpty() ? InListObject.inEmpty : new BindInListObject( toInList(columnName, values, this.maxSize), diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/InList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/InList.java index c251434..430ee5c 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/InList.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/InList.java @@ -12,6 +12,13 @@ import java.util.Collection; */ public interface InList { + /** + * Returns an InList instance for use with this connection + * @param conn connection which may be inspected to determine best InList to use + * @return InList instance + */ + public InList instance(final Connection conn); + /** * Returns an Object who's .toString returns a String for a query, and QueryMapper knows how to bind to a PreparedStatement * @param columnName Column name for query diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java index e1bf46e..f962470 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/ListQueryMapper.java @@ -1,5 +1,6 @@ package com.moparisthebest.jdbc; +import com.moparisthebest.jdbc.codegen.JdbcMapper; import com.moparisthebest.jdbc.util.ResultSetIterable; import java.lang.reflect.Method; @@ -17,11 +18,38 @@ public class ListQueryMapper extends QueryMapper { private static final InList defaultInList; static { - InList def = null; + InList def; try { - final Class ensureContext = Class.forName(System.getProperty("QueryMapper.defaultInList.class", "com.moparisthebest.jdbc.BindInList")); - final Method method = ensureContext.getMethod(System.getProperty("QueryMapper.defaultInList.method", "instance")); - def = (InList) method.invoke(null); + final String inListClassName = System.getProperty("QueryMapper.defaultInList.class"); + if(inListClassName != null) { + final Class inListClass = Class.forName(inListClassName); + final Method method = inListClass.getMethod(System.getProperty("QueryMapper.defaultInList.method", "instance")); + def = (InList) method.invoke(null); + } else { + // todo: change default to OPTIMAL ? + final String type = System.getProperty("queryMapper.databaseType", System.getProperty("jdbcMapper.databaseType", "BIND")); + if(type.equals("OPTIMAL")) { + def = OptimalInList.instance(); + } else { + switch (JdbcMapper.DatabaseType.valueOf(type)) { + case DEFAULT: + case BIND: + def = BindInList.instance(); + break; + case ANY: + def = ArrayInList.instance(); + break; + case ORACLE: + def = OracleArrayInList.instance(); + break; + case UNNEST: + def = UnNestArrayInList.instance(); + break; + default: + throw new RuntimeException("Invalid queryMapper.databaseType: " + type); + } + } + } } catch (Throwable e) { // NEVER ignore throw new RuntimeException(e); @@ -35,7 +63,7 @@ public class ListQueryMapper extends QueryMapper { public static final String inListReplace = "{inList}"; private ListQueryMapper(Connection conn, String jndiName, Factory factory, QueryMapper delegate, ResultSetMapper cm, InList inList) { - this.inList = inList; + this.inList = inList.instance(conn); this.delegate = delegate == null ? new QueryMapper(conn, jndiName, factory, cm) : (delegate instanceof ListQueryMapper ? ((ListQueryMapper)delegate).delegate : delegate); } diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/OptimalInList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/OptimalInList.java new file mode 100644 index 0000000..a1fc046 --- /dev/null +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/OptimalInList.java @@ -0,0 +1,102 @@ +package com.moparisthebest.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collection; + +public class OptimalInList implements InList { + + private static final InList instance; + + public static InList instance() { + return instance; + } + + public static final Class hsqlConnection, oracleConnection, postgreConnection, h2Connection; + + static { + hsqlConnection = classForName("org.hsqldb.jdbc.JDBCConnection"); + oracleConnection = classForName("oracle.jdbc.OracleConnection"); + postgreConnection = classForName("org.postgresql.PGConnection"); + h2Connection = classForName("org.h2.jdbc.JdbcConnection"); + instance = new OptimalInList(); + } + + public static Class classForName(final String className) { + try { + return Class.forName(className); + } catch (Exception e) { + return null; + } + } + + public static boolean isWrapperFor(final Connection conn, final Class connectionClass) { + if (connectionClass == null) + return false; + try { + return conn.isWrapperFor(connectionClass); + } catch (Exception e) { + return false; + } + } + + private final InList any, oracle, unnest, bind; + + public OptimalInList(final InList any, final InList oracle, final InList unnest, final InList bind) { + this.any = any; + this.oracle = oracle; + this.unnest = unnest; + this.bind = bind; + } + + protected OptimalInList() { + this(ArrayInList.instance(), OracleArrayInList.instance(), UnNestArrayInList.instance(), BindInList.instance()); + } + + @Override + public InList instance(Connection conn) { + if (isWrapperFor(conn, postgreConnection) + // java6 version of h2 doesn't support this + //IFJAVA8_START + || isWrapperFor(conn, h2Connection) + //IFJAVA8_END + ) + return any; + if (isWrapperFor(conn, oracleConnection)) + return oracle; + if (isWrapperFor(conn, hsqlConnection)) + return unnest; + // works for everything + return bind; + } + + /** + * Don't call this which inspect connection type each time, get an InList once with .instance(Connection) and use it forever + * + * @param conn + * @param columnName Column name for query + * @param values values for in list + * @param + * @return + * @throws SQLException + */ + @Override + public InListObject inList(Connection conn, String columnName, Collection values) throws SQLException { + return this.instance(conn).inList(conn, columnName, values); + } + + /** + * Don't call this which inspect connection type each time, get an InList once with .instance(Connection) and use it forever + * + * @param conn + * @param columnName Column name for query + * @param values values for in list + * @param + * @return + * @throws SQLException + */ + @Override + public InListObject notInList(Connection conn, String columnName, Collection values) throws SQLException { + return this.instance(conn).notInList(conn, columnName, values); + } +} diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/OracleArrayInList.java b/querymapper/src/main/java/com/moparisthebest/jdbc/OracleArrayInList.java index 2d22c2e..1698d51 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/OracleArrayInList.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/OracleArrayInList.java @@ -2,12 +2,12 @@ package com.moparisthebest.jdbc; import com.moparisthebest.jdbc.codegen.JdbcMapper; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Array; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collection; + +import static com.moparisthebest.jdbc.OptimalInList.oracleConnection; /** * Created by mopar on 4/29/15. @@ -20,19 +20,16 @@ public class OracleArrayInList extends ArrayInList { return instance; } - private static final Class oracleConnection; private static final Method createArray; static { - Class oc; - Method ca; - try { - oc = Class.forName("oracle.jdbc.OracleConnection"); - ca = oc.getDeclaredMethod("createOracleArray", String.class, Object.class); - } catch (Exception e) { - throw new RuntimeException(e); - } - oracleConnection = oc; + Method ca = null; + if(oracleConnection != null) + try { + ca = oracleConnection.getDeclaredMethod("createOracleArray", String.class, Object.class); + } catch (Exception e) { + throw new RuntimeException(e); + } createArray = ca; } diff --git a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java index 849e7fd..b386a7c 100644 --- a/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java +++ b/test/src/main/java/com/moparisthebest/jdbc/codegen/QueryMapperQmDao.java @@ -55,82 +55,12 @@ public class QueryMapperQmDao implements QmDao { public static final String selectNumVal = "SELECT num_val FROM val WHERE val_no = ?"; public static final String selectStrVal = "SELECT str_val FROM val WHERE val_no = ?"; - private static final Collection> noArrayInListSupport; - public static final Class hsqlConnection, oracleConnection, mssqlConnection; - - static { - Collection> no = new ArrayList>(); - for(final String connectionClassName : new String[]{ - "org.apache.derby.iapi.jdbc.EngineConnection" - , "org.hsqldb.jdbc.JDBCConnection" // does not support ArrayInList but *does* support UnNestArrayInList - , "org.sqlite.jdbc3.JDBC3Connection" - , "org.mariadb.jdbc.MariaDbConnection" - , "com.microsoft.sqlserver.jdbc.ISQLServerConnection" - , "oracle.jdbc.OracleConnection" // does not support ArrayInList but *does* support OracleArrayInList - // h2 doesn't support this with java6 either... - /*IFJAVA6_START - , "org.h2.jdbc.JdbcConnection" - IFJAVA6_END*/ - }) - try { - no.add(Class.forName(connectionClassName)); - } catch(Exception e) { - // ignore - } - noArrayInListSupport = Collections.unmodifiableCollection(no); - hsqlConnection = classForName("org.hsqldb.jdbc.JDBCConnection"); - oracleConnection = classForName("oracle.jdbc.OracleConnection"); - mssqlConnection = classForName("com.microsoft.sqlserver.jdbc.SQLServerConnection"); - } - - private static Class classForName(final String className) { - try { - return Class.forName(className); - } catch(Exception e) { - return null; - } - } - - public static boolean isWrapperFor(final Connection conn, final Class connectionClass) { - if(connectionClass == null) - return false; - try { - return conn.isWrapperFor(connectionClass); - } catch(Exception e) { - return false; - } - } - - public static boolean supportsArrayInList(final Connection conn) { - for(final Class connectionClass : noArrayInListSupport) { - try { - if(conn.isWrapperFor(connectionClass)) - return false; - } catch (SQLException e) { - // ignore... how could this happen? - } - } - // assume Connections DO support this unless we KNOW otherwise - return true; - } - - public static InList getBestInList(final Connection conn) { - if(isWrapperFor(conn, oracleConnection)) - return OracleArrayInList.instance(); - if(isWrapperFor(conn, hsqlConnection)) - return UnNestArrayInList.instance(); - if(supportsArrayInList(conn)) - return ArrayInList.instance(); - // works for everything - return BindInList.instance(); - } - protected final QueryMapper qm; protected final ListQueryMapper lqm; public QueryMapperQmDao(final Connection conn, final ResultSetMapper rsm) { this.qm = new QueryMapper(conn, rsm); - this.lqm = new ListQueryMapper(qm, getBestInList(qm.getConnection())); + this.lqm = new ListQueryMapper(qm); } @Override diff --git a/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java b/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java index a61afef..a727b8e 100644 --- a/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java +++ b/test/src/test/java/com/moparisthebest/jdbc/QueryMapperTest.java @@ -22,8 +22,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; //IFJAVA8_END +import static com.moparisthebest.jdbc.OptimalInList.classForName; +import static com.moparisthebest.jdbc.OptimalInList.isWrapperFor; +import static com.moparisthebest.jdbc.OptimalInList.oracleConnection; import static com.moparisthebest.jdbc.TryClose.tryClose; -import static com.moparisthebest.jdbc.codegen.QueryMapperQmDao.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -72,6 +74,8 @@ public class QueryMapperTest { public static final Collection jdbcUrls; public static final Map dataSources = new HashMap(); + public static final Class mssqlConnection = classForName("com.microsoft.sqlserver.jdbc.ISQLServerConnection"); + static { final Collection jUrls = new ArrayList(); final String jdbcUrl = System.getProperty("jdbcUrl", "all");