Add support for binding Blob, Clob, java.sql.Array to PreparedStatement in JdbcMapper

This commit is contained in:
Travis Burtrum 2017-05-29 18:26:11 -04:00
parent 12fc0edbd4
commit 2162e35456
3 changed files with 103 additions and 51 deletions

View File

@ -65,6 +65,35 @@ public interface JdbcMapper extends Closeable {
int arrayMaxLength() default -1; int arrayMaxLength() default -1;
} }
// these are to annotate parameters for special bind to PreparedStatement behavior
/**
* Use PreparedStatement.setBlob
*
* Only required for String
*
* Also valid for byte[], InputStream, Blob, File. But for those .setBlob is called even without this annotation
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface Blob {
/**
* The charsetName sent into String.getBytes()
*/
String charsetName() default "UTF-8";
}
/**
* Use PreparedStatement.setClob
*
* Only valid for String
*
* Also valid for Clob, Reader. But for those .setClob is called even without this annotation
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface Clob {}
public enum OptionalBool { public enum OptionalBool {
DEFAULT, DEFAULT,
TRUE, TRUE,

View File

@ -31,8 +31,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
private Types types; private Types types;
private TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType, private TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType,
byteArrayType, inputStreamType, fileType, blobType byteArrayType, inputStreamType, fileType, blobType, sqlArrayType
//, clobStringType, blobStringType, arrayListObjectType
; ;
public JdbcMapperProcessor() { public JdbcMapperProcessor() {
@ -58,11 +57,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
//byteArrayType = this.types.getArrayType(elements.getTypeElement(byte.class.getCanonicalName()).asType()); //byteArrayType = this.types.getArrayType(elements.getTypeElement(byte.class.getCanonicalName()).asType());
//byteArrayType = elements.getTypeElement(byte.class.getCanonicalName()).asType(); //byteArrayType = elements.getTypeElement(byte.class.getCanonicalName()).asType();
byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE)); byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE));
/* sqlArrayType = elements.getTypeElement(java.sql.Array.class.getCanonicalName()).asType();
clobStringType = elements.getTypeElement(ClobString.class.getCanonicalName()).asType();
blobStringType = elements.getTypeElement(BlobString.class.getCanonicalName()).asType();
arrayListObjectType = elements.getTypeElement(ArrayInList.ArrayListObject.class.getCanonicalName()).asType();
*/
} }
@Override @Override
@ -263,7 +258,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
// now bind parameters // now bind parameters
int count = 0; int count = 0;
for (final VariableElement param : bindParams) for (final VariableElement param : bindParams)
setObject(w, ++count, param.getSimpleName().toString(), param.asType()); setObject(w, ++count, param);
if (!parsedSQl.isSelect()) { if (!parsedSQl.isSelect()) {
if (returnType.equals("void")) { if (returnType.equals("void")) {
@ -352,54 +347,79 @@ public class JdbcMapperProcessor extends AbstractProcessor {
return true; return true;
} }
private void setObject(final Writer w, final int index, String variableName, final TypeMirror o) throws SQLException, IOException { private void setObject(final Writer w, final int index, final VariableElement param) throws SQLException, IOException {
String variableName = param.getSimpleName().toString();
final TypeMirror o = param.asType();
w.write("\t\t\t"); w.write("\t\t\t");
// we are going to put most common ones up top so it should execute faster normally
String method = null; String method = null;
// todo: avoid string concat here
if (o.getKind().isPrimitive() || types.isAssignable(o, stringType) || types.isAssignable(o, numberType)) { // special behavior
method = "Object"; final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class);
// java.util.Date support, put it in a Timestamp if(blob != null) {
} else if (types.isAssignable(o, utilDateType)) { if (types.isAssignable(o, stringType)) {
method = "Object"; w.write("try {\n\t\t\t\tps.setBlob(");
// might need to wrap with Timestamp w.write(Integer.toString(index));
if (types.isSameType(o, utilDateType)) w.write(", ");
variableName = "new java.sql.Timestamp(" + variableName + ".getTime())"; w.write(variableName);
// CLOB support w.write(" == null ? null : new java.io.ByteArrayInputStream(");
} else if (types.isAssignable(o, readerType) || types.isAssignable(o, clobType)) { w.write(variableName);
method = "Clob"; w.write(".getBytes(\"");
/* w.write(blob.charsetName());
} else if (o instanceof ClobString) { w.write("\")));\n\t\t\t} catch (java.io.UnsupportedEncodingException e) {\n" +
ps.setObject(index, ((ClobString) o).s == null ? null : ((ClobString) o).s); "\t\t\t\tthrow new SQLException(\"String to Blob UnsupportedEncodingException\", e);\n" +
*/ "\t\t\t}\n");
// BLOB support return;
} else if (types.isAssignable(o, byteArrayType)) { } else if (!(types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType) || types.isAssignable(o, fileType) || types.isAssignable(o, byteArrayType))) {
method = "Blob"; processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Blob only valid for String, byte[], Blob, InputStream, and File", param);
variableName = "new java.io.ByteArrayInputStream(" + variableName + ")"; return;
} else if (types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType)) {
method = "Blob";
} else if (types.isAssignable(o, fileType)) {
// todo: does this close the InputStream properly????
w.write("\t\t\ttry {\n" +
"\t\t\t\tps.setBlob(" + index + ", new java.io.FileInputStream(" + variableName + "));\n" +
"\t\t\t} catch (java.io.FileNotFoundException e) {\n" +
"\t\t\t\tthrow new SQLException(\"File to Blob FileNotFoundException\", e);\n" +
"\t\t\t}");
return;
/*
} else if (o instanceof BlobString) {
try {
ps.setBlob(index, ((BlobString) o).s == null ? null : new ByteArrayInputStream(((BlobString) o).s.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
throw new SQLException("String to Blob UnsupportedEncodingException", e);
} }
} else if (o instanceof ArrayInList.ArrayListObject) {
ps.setArray(index, ((ArrayInList.ArrayListObject) o).getArray());
*/
} else { } else {
// probably won't get here ever, but just in case... final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
method = "Object"; if(clob != null) {
if (types.isAssignable(o, stringType)) {
method = "Clob";
variableName = variableName + " == null ? null : new StringReader("+variableName+")";
} else if (!(types.isAssignable(o, readerType) || types.isAssignable(o, clobType))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Clob only valid for String, Clob, Reader", param);
return;
}
}
} }
// end special behavior
// we are going to put most common ones up top so it should execute faster normally
// todo: avoid string concat here
if(method == null)
if (o.getKind().isPrimitive() || types.isAssignable(o, stringType) || types.isAssignable(o, numberType)) {
method = "Object";
// java.util.Date support, put it in a Timestamp
} else if (types.isAssignable(o, utilDateType)) {
method = "Object";
// might need to wrap with Timestamp
if (types.isSameType(o, utilDateType))
variableName = "new java.sql.Timestamp(" + variableName + ".getTime())";
// CLOB support
} else if (types.isAssignable(o, readerType) || types.isAssignable(o, clobType)) {
method = "Clob";
} else if (types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType)) {
method = "Blob";
} else if (types.isAssignable(o, fileType)) {
// todo: does this close the InputStream properly????
w.write("\t\t\ttry {\n" +
"\t\t\t\tps.setBlob(" + index + ", new java.io.FileInputStream(" + variableName + "));\n" +
"\t\t\t} catch (java.io.FileNotFoundException e) {\n" +
"\t\t\t\tthrow new SQLException(\"File to Blob FileNotFoundException\", e);\n" +
"\t\t\t}");
return;
} else if (types.isAssignable(o, byteArrayType)) {
method = "Blob";
variableName = "new java.io.ByteArrayInputStream(" + variableName + ")";
} else if (types.isAssignable(o, sqlArrayType)) {
method = "Array";
} else {
// probably won't get here ever, but just in case...
method = "Object";
}
w.write("ps.set"); w.write("ps.set");
w.write(method); w.write(method);
w.write('('); w.write('(');

View File

@ -28,6 +28,9 @@ public interface PersonDAO {
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException; void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException;
@JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}") @JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}")
String getFirstName(long personNo) throws SQLException; String getFirstName(long personNo) throws SQLException;