Implement OptimalInList.java which picks the optimal in list based on connection type

This commit is contained in:
Travis Burtrum 2018-05-23 23:41:19 -04:00
parent 9c1efdc715
commit af0c0f3483
9 changed files with 175 additions and 89 deletions

View File

@ -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=<YourStrong!Passw0rd>;' || 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=<YourStrong!Passw0rd>;' || 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=<YourStrong!Passw0rd>;' || 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=<YourStrong!Passw0rd>;' || 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;

View File

@ -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(?))";
}

View File

@ -28,6 +28,11 @@ public class BindInList implements InList {
this(defaultMaxSize);
}
@Override
public InList instance(Connection conn) {
return this;
}
public <T> InListObject inList(final Connection conn, final String columnName, final Collection<T> values) {
return values == null || values.isEmpty() ? InListObject.inEmpty : new BindInListObject(
toInList(columnName, values, this.maxSize),

View File

@ -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

View File

@ -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<Connection> 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);
}

View File

@ -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 <T>
* @return
* @throws SQLException
*/
@Override
public <T> InListObject inList(Connection conn, String columnName, Collection<T> 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 <T>
* @return
* @throws SQLException
*/
@Override
public <T> InListObject notInList(Connection conn, String columnName, Collection<T> values) throws SQLException {
return this.instance(conn).notInList(conn, columnName, values);
}
}

View File

@ -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;
}

View File

@ -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<Class<?>> noArrayInListSupport;
public static final Class<?> hsqlConnection, oracleConnection, mssqlConnection;
static {
Collection<Class<?>> no = new ArrayList<Class<?>>();
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

View File

@ -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<String> jdbcUrls;
public static final Map<String, DataSource> dataSources = new HashMap<String, DataSource>();
public static final Class<?> mssqlConnection = classForName("com.microsoft.sqlserver.jdbc.ISQLServerConnection");
static {
final Collection<String> jUrls = new ArrayList<String>();
final String jdbcUrl = System.getProperty("jdbcUrl", "all");