mirror of
https://github.com/moparisthebest/JdbcMapper
synced 2024-12-21 14:58:51 -05:00
Re-work IN LIST support in JdbcMapper to re-use created arrays, and free them after use
This commit is contained in:
parent
17a429399c
commit
c2204b4635
@ -2,6 +2,7 @@ package com.moparisthebest.jdbc;
|
||||
|
||||
import javax.naming.Context;
|
||||
import java.io.Closeable;
|
||||
import java.sql.Array;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
@ -48,6 +49,16 @@ public class TryClose { // tries to close certain object types
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryClose(Array obj) {
|
||||
if (obj == null)
|
||||
return;
|
||||
try {
|
||||
obj.free();
|
||||
} catch (Throwable e) {
|
||||
// ignore...
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryClose(Closeable obj) {
|
||||
if (obj == null)
|
||||
return;
|
||||
|
@ -22,6 +22,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
//IFJAVA8_START
|
||||
import java.time.*;
|
||||
import java.util.stream.Stream;
|
||||
//IFJAVA8_END
|
||||
|
||||
import static com.moparisthebest.jdbc.TryClose.tryClose;
|
||||
@ -69,7 +70,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
static TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType, connectionType, jdbcMapperType,
|
||||
byteArrayType, inputStreamType, fileType, blobType, sqlArrayType, collectionType, calendarType, cleanerType, enumType;
|
||||
//IFJAVA8_START
|
||||
static TypeMirror instantType, localDateTimeType, localDateType, localTimeType, zonedDateTimeType, offsetDateTimeType, offsetTimeType;
|
||||
static TypeMirror streamType, instantType, localDateTimeType, localDateType, localTimeType, zonedDateTimeType, offsetDateTimeType, offsetTimeType;
|
||||
//IFJAVA8_END
|
||||
private TypeElement cleanerElement;
|
||||
private JdbcMapper.DatabaseType defaultDatabaseType;
|
||||
@ -105,6 +106,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
blobType = elements.getTypeElement(Blob.class.getCanonicalName()).asType();
|
||||
calendarType = elements.getTypeElement(Calendar.class.getCanonicalName()).asType();
|
||||
//IFJAVA8_START
|
||||
streamType = types.getDeclaredType(elements.getTypeElement(Stream.class.getCanonicalName()), types.getWildcardType(null, null));
|
||||
instantType = elements.getTypeElement(Instant.class.getCanonicalName()).asType();
|
||||
localDateTimeType = elements.getTypeElement(LocalDateTime.class.getCanonicalName()).asType();
|
||||
localDateType = elements.getTypeElement(LocalDate.class.getCanonicalName()).asType();
|
||||
@ -308,6 +310,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
|
||||
// build query and bind param order
|
||||
final List<VariableElement> bindParams = new ArrayList<VariableElement>();
|
||||
final Map<String, SpecialVariableElement> inListBindParams = new LinkedHashMap<>();
|
||||
final String sqlStatement;
|
||||
String calendarName = null, cleanerName = null;
|
||||
CompileTimeResultSetMapper.MaxRows maxRows = CompileTimeResultSetMapper.MaxRows.getMaxRows(sql.maxRows());
|
||||
@ -349,6 +352,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
|
||||
final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
int inListBindParamsIdx = -1;
|
||||
while (bindParamMatcher.find()) {
|
||||
final String paramName = bindParamMatcher.group(7);
|
||||
final VariableElement bindParam = paramMap.get(paramName);
|
||||
@ -380,7 +384,12 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
} else {
|
||||
if(clobBlob != null)
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "cannot combine in/not in and clob/blob", bindParam);
|
||||
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.IN_LIST));
|
||||
SpecialVariableElement inListBindParam = inListBindParams.get(paramName);
|
||||
if(inListBindParam == null) {
|
||||
inListBindParam = new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.IN_LIST, ++inListBindParamsIdx);
|
||||
inListBindParams.put(paramName, inListBindParam);
|
||||
}
|
||||
bindParams.add(inListBindParam);
|
||||
final boolean not = bindParamMatcher.group(4) != null;
|
||||
final String replacement;
|
||||
switch (databaseType) {
|
||||
@ -433,7 +442,12 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
w.write("\t\tPreparedStatement ps = null;\n");
|
||||
if (parsedSQl.isSelect())
|
||||
w.write("\t\tResultSet rs = null;\n");
|
||||
w.write("\t\ttry {\n\t\t\tps = ");
|
||||
if(!inListBindParams.isEmpty())
|
||||
w.append("\t\tfinal Array[] _bindArrays = new Array[").append(Integer.toString(inListBindParams.size())).append("];\n");
|
||||
w.write("\t\ttry {\n");
|
||||
for (final SpecialVariableElement param : inListBindParams.values())
|
||||
setArray(w, databaseType, arrayNumberTypeName, arrayStringTypeName, param);
|
||||
w.write("\t\t\tps = ");
|
||||
final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(defaultCachePreparedStatements);
|
||||
if (cachePreparedStatements) {
|
||||
w.write("this.prepareStatement(");
|
||||
@ -450,7 +464,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
// now bind parameters
|
||||
int count = 0;
|
||||
for (final VariableElement param : bindParams)
|
||||
setObject(w, ++count, databaseType, arrayNumberTypeName, arrayStringTypeName, param);
|
||||
setObject(w, ++count, param);
|
||||
|
||||
boolean closeRs = true;
|
||||
if (!parsedSQl.isSelect()) {
|
||||
@ -484,6 +498,8 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
if (!sqlExceptionThrown)
|
||||
w.write("\t\t} catch(SQLException e) {\n\t\t\tthrow new RuntimeException(e);\n");
|
||||
w.write("\t\t} finally {\n");
|
||||
if(!inListBindParams.isEmpty())
|
||||
w.append("\t\t\tfor(final Array _bindArray : _bindArrays)\n\t\t\t\ttryClose(_bindArray);\n");
|
||||
if (parsedSQl.isSelect())
|
||||
w.write("\t\t\ttryClose(rs);\n");
|
||||
if (!cachePreparedStatements)
|
||||
@ -499,6 +515,10 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
w.write("\t\t\tif(e instanceof SQLException)\n\t\t\t\tthrow (SQLException)e;\n");
|
||||
w.write("\t\t\tif(e instanceof RuntimeException)\n\t\t\t\tthrow (RuntimeException)e;\n");
|
||||
w.write("\t\t\tthrow new RuntimeException(e);\n");
|
||||
if(!inListBindParams.isEmpty()) {
|
||||
w.write("\t\t} finally {\n");
|
||||
w.append("\t\t\tfor(final Array _bindArray : _bindArrays)\n\t\t\t\ttryClose(_bindArray);\n");
|
||||
}
|
||||
}
|
||||
w.write("\t\t}\n");
|
||||
|
||||
@ -658,44 +678,57 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
w.write("\t}\n");
|
||||
}
|
||||
|
||||
private void setObject(final Writer w, final int index, final JdbcMapper.DatabaseType databaseType, final String arrayNumberTypeName, final String arrayStringTypeName, final VariableElement param) throws SQLException, IOException {
|
||||
String variableName = param.getSimpleName().toString();
|
||||
final TypeMirror o = param.asType();
|
||||
w.write("\t\t\t");
|
||||
String method = null;
|
||||
private enum InListArgType {
|
||||
ARRAY,
|
||||
COLLECTION,
|
||||
STREAM,
|
||||
}
|
||||
|
||||
// special behavior
|
||||
if (param instanceof SpecialVariableElement) {
|
||||
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||
switch (specialParam.specialType) {
|
||||
case IN_LIST: {
|
||||
final boolean collection;
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
collection = false;
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
collection = true;
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
if (dt.getTypeArguments().isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Collection with a generic type parameter" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
w.write("ps.setArray(");
|
||||
w.write(Integer.toString(index));
|
||||
w.write(", ");
|
||||
final String type = types.isAssignable(componentType, numberType) ? arrayNumberTypeName : arrayStringTypeName;
|
||||
switch (databaseType) {
|
||||
case ORACLE:
|
||||
w.write("conn.unwrap(oracle.jdbc.OracleConnection.class).createOracleArray(\"");
|
||||
private void setArray(final Writer w, final JdbcMapper.DatabaseType databaseType, final String arrayNumberTypeName, final String arrayStringTypeName, final SpecialVariableElement specialParam) throws IOException {
|
||||
final String variableName = specialParam.getSimpleName().toString();
|
||||
final TypeMirror o = specialParam.asType();
|
||||
|
||||
// todo: if oracle driver is not on compile-time classpath, would need to do:
|
||||
// we could also create a fake-oracle module that just had a oracle.jdbc.OracleConnection class implementing createOracleArray()...
|
||||
final InListArgType inListArgType;
|
||||
final TypeMirror componentType;
|
||||
if (o.getKind() == TypeKind.ARRAY) {
|
||||
inListArgType = InListArgType.ARRAY;
|
||||
componentType = ((ArrayType) o).getComponentType();
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, collectionType)) {
|
||||
inListArgType = InListArgType.COLLECTION;
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
if (dt.getTypeArguments().isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Collection with a generic type parameter" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
//IFJAVA8_START
|
||||
} else if (o.getKind() == TypeKind.DECLARED && types.isAssignable(o, streamType)) {
|
||||
inListArgType = InListArgType.STREAM;
|
||||
final DeclaredType dt = (DeclaredType) o;
|
||||
if (dt.getTypeArguments().isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Stream with a generic type parameter" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
componentType = dt.getTypeArguments().get(0);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
//IFJAVA8_END
|
||||
/*IFJAVA6_START
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
|
||||
return;
|
||||
}
|
||||
IFJAVA6_END*/
|
||||
w.append("\t\t\t_bindArrays[").append(Integer.toString(specialParam.index)).append("] = ");
|
||||
final String type = types.isAssignable(componentType, numberType) ? arrayNumberTypeName : arrayStringTypeName;
|
||||
switch (databaseType) {
|
||||
case ORACLE:
|
||||
w.write("conn.unwrap(oracle.jdbc.OracleConnection.class).createOracleArray(\"");
|
||||
|
||||
// todo: if oracle driver is not on compile-time classpath, would need to do:
|
||||
// we could also create a fake-oracle module that just had a oracle.jdbc.OracleConnection class implementing createOracleArray()...
|
||||
/*
|
||||
private static final Class<?> oracleConnection;
|
||||
private static final Method createArray;
|
||||
@ -713,20 +746,42 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
createArray = ca;
|
||||
}
|
||||
*/
|
||||
//w.write("(Array) createArray.invoke(conn.unwrap(oracleConnection), \"");
|
||||
break;
|
||||
case STANDARD:
|
||||
w.write("conn.createArrayOf(\"");
|
||||
break;
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", specialParam.delegate);
|
||||
}
|
||||
w.write(type);
|
||||
w.write("\", ");
|
||||
w.write(variableName);
|
||||
if (collection)
|
||||
w.write(".toArray()");
|
||||
w.write("));\n");
|
||||
//w.write("(Array) createArray.invoke(conn.unwrap(oracleConnection), \"");
|
||||
break;
|
||||
case STANDARD:
|
||||
w.write("conn.createArrayOf(\"");
|
||||
break;
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", specialParam.delegate);
|
||||
}
|
||||
w.write(type);
|
||||
w.write("\", ");
|
||||
w.write(variableName);
|
||||
switch (inListArgType) {
|
||||
case COLLECTION:
|
||||
w.append(".toArray(new ").append(componentType.toString()).append("[").append(variableName).append(".size()])");
|
||||
break;
|
||||
case STREAM:
|
||||
w.append(".toArray(").append(componentType.toString()).append("[]::new)");
|
||||
break;
|
||||
}
|
||||
w.write(");\n");
|
||||
}
|
||||
|
||||
private void setObject(final Writer w, final int index, final VariableElement param) throws IOException {
|
||||
String variableName = param.getSimpleName().toString();
|
||||
final TypeMirror o = param.asType();
|
||||
w.write("\t\t\t");
|
||||
String method = null;
|
||||
|
||||
// special behavior
|
||||
if (param instanceof SpecialVariableElement) {
|
||||
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||
switch (specialParam.specialType) {
|
||||
case IN_LIST: {
|
||||
w.write("ps.setArray(");
|
||||
w.write(Integer.toString(index));
|
||||
w.append(", _bindArrays[").append(Integer.toString(specialParam.index)).append("]);\n");
|
||||
return;
|
||||
}
|
||||
case BLOB: {
|
||||
|
@ -20,15 +20,25 @@ class SpecialVariableElement implements VariableElement {
|
||||
final VariableElement delegate;
|
||||
final SpecialType specialType;
|
||||
final String blobStringCharset;
|
||||
final int index;
|
||||
|
||||
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType) {
|
||||
this(delegate, specialType, null);
|
||||
this(delegate, specialType, null, 0);
|
||||
}
|
||||
|
||||
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType, final int index) {
|
||||
this(delegate, specialType, null, index);
|
||||
}
|
||||
|
||||
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType, final String blobStringCharset) {
|
||||
this(delegate, specialType, blobStringCharset, 0);
|
||||
}
|
||||
|
||||
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType, final String blobStringCharset, final int index) {
|
||||
this.delegate = delegate;
|
||||
this.specialType = specialType;
|
||||
this.blobStringCharset = blobStringCharset;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,4 +120,5 @@ TODO
|
||||
|
||||
* DOCUMENTATION!!!!!
|
||||
* sql other than select return boolean, int > 0 ?
|
||||
* @RunInTransaction void support
|
||||
* @RunInTransaction void support
|
||||
* QueryMapper mapping errors should be clearer, especially if a .finish(ResultSet) throws an error
|
@ -226,8 +226,17 @@ public interface PersonDAO extends JdbcMapper {
|
||||
@JdbcMapper.SQL("SELECT str_val FROM val WHERE val_no = 4")
|
||||
SameClassPathEnumPerson.FirstName getSameClassPathEnumNull() throws SQLException;
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos}")
|
||||
List<FieldPerson> getPeopleInListArray(Long[] personNos);
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos}")
|
||||
List<FieldPerson> getPeopleInListCollection(Collection<Long> personNos);
|
||||
|
||||
//IFJAVA8_START
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos} OR {first_name IN names} OR {last_name IN names}")
|
||||
Stream<FieldPerson> getPeopleInListStream(Stream<Long> personNos, Stream<String> names);
|
||||
|
||||
@JdbcMapper.SQL("SELECT birth_date FROM person WHERE person_no = {personNo}")
|
||||
Instant getBirthDateInstant(long personNo);
|
||||
|
||||
|
@ -226,8 +226,17 @@ public interface PrestoPersonDAO extends PersonDAO {
|
||||
@JdbcMapper.SQL("SELECT str_val FROM val WHERE val_no = 4")
|
||||
SameClassPathEnumPerson.FirstName getSameClassPathEnumNull() throws SQLException;
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos}")
|
||||
List<FieldPerson> getPeopleInListArray(Long[] personNos);
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos}")
|
||||
List<FieldPerson> getPeopleInListCollection(Collection<Long> personNos);
|
||||
|
||||
//IFJAVA8_START
|
||||
|
||||
@SQL("SELECT first_name, last_name FROM person WHERE {person_no IN personNos} OR {first_name IN names} OR {last_name IN names}")
|
||||
Stream<FieldPerson> getPeopleInListStream(Stream<Long> personNos, Stream<String> names);
|
||||
|
||||
@JdbcMapper.SQL("SELECT birth_date FROM person WHERE person_no = {personNo}")
|
||||
Instant getBirthDateInstant(long personNo);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user