mirror of
https://github.com/moparisthebest/JdbcMapper
synced 2024-11-24 10:02:14 -05:00
Remove Blob and Clob annotations in favor of SQL bind syntax
This commit is contained in:
parent
ba7fb733b2
commit
edd6fc6e65
@ -103,35 +103,6 @@ public interface JdbcMapper extends Closeable {
|
|||||||
long maxRows() default -1;
|
long maxRows() 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 {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to QueryMapper.toObject for arrays, and .toSingleMap for maps
|
* Equivalent to QueryMapper.toObject for arrays, and .toSingleMap for maps
|
||||||
*/
|
*/
|
||||||
|
@ -34,7 +34,7 @@ import static com.moparisthebest.jdbc.codegen.JdbcMapperFactory.SUFFIX;
|
|||||||
@SupportedSourceVersion(SourceVersion.RELEASE_5)
|
@SupportedSourceVersion(SourceVersion.RELEASE_5)
|
||||||
public class JdbcMapperProcessor extends AbstractProcessor {
|
public class JdbcMapperProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
private static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([^}]+)\\}");
|
public static final Pattern paramPattern = Pattern.compile("\\{(([^\\s]+)\\s+(([Nn][Oo][Tt]\\s+)?[Ii][Nn]\\s+))?([BbCc][Ll][Oo][Bb]\\s*:\\s*([^:]+\\s*:\\s*)?)?([^}]+)\\}");
|
||||||
|
|
||||||
public static final SourceVersion RELEASE_8;
|
public static final SourceVersion RELEASE_8;
|
||||||
public static boolean java8;
|
public static boolean java8;
|
||||||
@ -334,19 +334,37 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
|
final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
|
||||||
final StringBuffer sb = new StringBuffer();
|
final StringBuffer sb = new StringBuffer();
|
||||||
while (bindParamMatcher.find()) {
|
while (bindParamMatcher.find()) {
|
||||||
final String paramName = bindParamMatcher.group(5);
|
final String paramName = bindParamMatcher.group(7);
|
||||||
final VariableElement bindParam = paramMap.get(paramName);
|
final VariableElement bindParam = paramMap.get(paramName);
|
||||||
if (bindParam == null) {
|
if (bindParam == null) {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL sql has bind param '%s' not in method parameter list", paramName), methodElement);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL sql has bind param '%s' not in method parameter list", paramName), methodElement);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
unusedParams.remove(paramName);
|
unusedParams.remove(paramName);
|
||||||
|
final String clobBlob = bindParamMatcher.group(5);
|
||||||
final String inColumnName = bindParamMatcher.group(2);
|
final String inColumnName = bindParamMatcher.group(2);
|
||||||
if (inColumnName == null) {
|
if (inColumnName == null) {
|
||||||
bindParams.add(bindParam);
|
|
||||||
bindParamMatcher.appendReplacement(sb, "?");
|
bindParamMatcher.appendReplacement(sb, "?");
|
||||||
|
if(clobBlob == null){
|
||||||
|
bindParams.add(bindParam);
|
||||||
} else {
|
} else {
|
||||||
bindParams.add(new InListVariableElement(bindParam));
|
// regex ensures this can only be C for clob or B for blob
|
||||||
|
final boolean clobNotBlob = 'C' == Character.toUpperCase(clobBlob.charAt(0));
|
||||||
|
final String blobCharset = bindParamMatcher.group(6);
|
||||||
|
if(clobNotBlob) {
|
||||||
|
if(blobCharset != null)
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "blob character set not valid with clob", bindParam);
|
||||||
|
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.CLOB));
|
||||||
|
} else {
|
||||||
|
bindParams.add(new SpecialVariableElement(bindParam, SpecialVariableElement.SpecialType.BLOB, blobCharset == null ? null :
|
||||||
|
blobCharset.substring(0, blobCharset.indexOf(':')).trim()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
final boolean not = bindParamMatcher.group(4) != null;
|
final boolean not = bindParamMatcher.group(4) != null;
|
||||||
final String replacement;
|
final String replacement;
|
||||||
switch (databaseType) {
|
switch (databaseType) {
|
||||||
@ -632,7 +650,10 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
String method = null;
|
String method = null;
|
||||||
|
|
||||||
// special behavior
|
// special behavior
|
||||||
if (param instanceof InListVariableElement) {
|
if (param instanceof SpecialVariableElement) {
|
||||||
|
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||||
|
switch (specialParam.specialType) {
|
||||||
|
case IN_LIST: {
|
||||||
final boolean collection;
|
final boolean collection;
|
||||||
final TypeMirror componentType;
|
final TypeMirror componentType;
|
||||||
if (o.getKind() == TypeKind.ARRAY) {
|
if (o.getKind() == TypeKind.ARRAY) {
|
||||||
@ -642,12 +663,12 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
collection = true;
|
collection = true;
|
||||||
final DeclaredType dt = (DeclaredType) o;
|
final DeclaredType dt = (DeclaredType) o;
|
||||||
if (dt.getTypeArguments().isEmpty()) {
|
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(), ((InListVariableElement) param).delegate);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax requires a Collection with a generic type parameter" + o.toString(), specialParam.delegate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
componentType = dt.getTypeArguments().get(0);
|
componentType = dt.getTypeArguments().get(0);
|
||||||
} else {
|
} else {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), ((InListVariableElement) param).delegate);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL in list syntax only valid on Collections or arrays" + o.toString(), specialParam.delegate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
w.write("ps.setArray(");
|
w.write("ps.setArray(");
|
||||||
@ -683,7 +704,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
w.write("conn.createArrayOf(\"");
|
w.write("conn.createArrayOf(\"");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", ((InListVariableElement) param).delegate);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "default DatabaseType? should never happen!!", specialParam.delegate);
|
||||||
}
|
}
|
||||||
w.write(type);
|
w.write(type);
|
||||||
w.write("\", ");
|
w.write("\", ");
|
||||||
@ -693,8 +714,7 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
w.write("));\n");
|
w.write("));\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class);
|
case BLOB: {
|
||||||
if (blob != null) {
|
|
||||||
if (types.isAssignable(o, stringType)) {
|
if (types.isAssignable(o, stringType)) {
|
||||||
w.write("try {\n\t\t\t\tps.setBlob(");
|
w.write("try {\n\t\t\t\tps.setBlob(");
|
||||||
w.write(Integer.toString(index));
|
w.write(Integer.toString(index));
|
||||||
@ -703,27 +723,27 @@ public class JdbcMapperProcessor extends AbstractProcessor {
|
|||||||
w.write(" == null ? null : new java.io.ByteArrayInputStream(");
|
w.write(" == null ? null : new java.io.ByteArrayInputStream(");
|
||||||
w.write(variableName);
|
w.write(variableName);
|
||||||
w.write(".getBytes(\"");
|
w.write(".getBytes(\"");
|
||||||
w.write(blob.charsetName());
|
w.write(specialParam.blobStringCharset == null ? "UTF-8" : specialParam.blobStringCharset);
|
||||||
w.write("\")));\n\t\t\t} catch (java.io.UnsupportedEncodingException e) {\n" +
|
w.write("\")));\n\t\t\t} catch (java.io.UnsupportedEncodingException e) {\n" +
|
||||||
"\t\t\t\tthrow new SQLException(\"String to Blob UnsupportedEncodingException\", e);\n" +
|
"\t\t\t\tthrow new SQLException(\"String to Blob UnsupportedEncodingException\", e);\n" +
|
||||||
"\t\t\t}\n");
|
"\t\t\t}\n");
|
||||||
return;
|
return;
|
||||||
} else if (!(types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType) || types.isAssignable(o, fileType) || types.isAssignable(o, byteArrayType))) {
|
} else if (!(types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType) || types.isAssignable(o, fileType) || types.isAssignable(o, byteArrayType))) {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Blob only valid for String, byte[], Blob, InputStream, and File", param);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Blob only valid for String, byte[], Blob, InputStream, and File", specialParam.delegate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
|
case CLOB: {
|
||||||
if (clob != null) {
|
|
||||||
if (types.isAssignable(o, stringType)) {
|
if (types.isAssignable(o, stringType)) {
|
||||||
method = "Clob";
|
method = "Clob";
|
||||||
variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")";
|
variableName = variableName + " == null ? null : new java.io.StringReader(" + variableName + ")";
|
||||||
} else if (!(types.isAssignable(o, readerType) || types.isAssignable(o, clobType))) {
|
} 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);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Clob only valid for String, Clob, Reader", specialParam.delegate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// end special behavior
|
// end special behavior
|
||||||
|
|
||||||
// we are going to put most common ones up top so it should execute faster normally
|
// we are going to put most common ones up top so it should execute faster normally
|
||||||
|
@ -96,7 +96,10 @@ public class SimpleSQLChecker implements SQLChecker {
|
|||||||
public Object getFakeBindParam(final VariableElement param, final Connection conn, final ArrayInList arrayInList) throws SQLException {
|
public Object getFakeBindParam(final VariableElement param, final Connection conn, final ArrayInList arrayInList) throws SQLException {
|
||||||
final TypeMirror o = param.asType();
|
final TypeMirror o = param.asType();
|
||||||
// special behavior
|
// special behavior
|
||||||
if (param instanceof InListVariableElement) {
|
if (param instanceof SpecialVariableElement) {
|
||||||
|
final SpecialVariableElement specialParam = (SpecialVariableElement) param;
|
||||||
|
switch (specialParam.specialType) {
|
||||||
|
case IN_LIST: {
|
||||||
final TypeMirror componentType;
|
final TypeMirror componentType;
|
||||||
if (o.getKind() == TypeKind.ARRAY) {
|
if (o.getKind() == TypeKind.ARRAY) {
|
||||||
componentType = ((ArrayType) o).getComponentType();
|
componentType = ((ArrayType) o).getComponentType();
|
||||||
@ -104,7 +107,7 @@ public class SimpleSQLChecker implements SQLChecker {
|
|||||||
final DeclaredType dt = (DeclaredType) o;
|
final DeclaredType dt = (DeclaredType) o;
|
||||||
componentType = dt.getTypeArguments().get(0);
|
componentType = dt.getTypeArguments().get(0);
|
||||||
} else {
|
} else {
|
||||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate);
|
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (types.isAssignable(componentType, numberType)) {
|
if (types.isAssignable(componentType, numberType)) {
|
||||||
@ -112,16 +115,13 @@ public class SimpleSQLChecker implements SQLChecker {
|
|||||||
} else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) {
|
} else if (types.isAssignable(componentType, stringType) || types.isAssignable(componentType, enumType)) {
|
||||||
return arrayInList.toArray(conn, false, new String[]{defaultString});
|
return arrayInList.toArray(conn, false, new String[]{defaultString});
|
||||||
} else {
|
} else {
|
||||||
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((InListVariableElement) param).delegate);
|
getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, "invalid in-list-variable type, returning null", ((SpecialVariableElement) param).delegate);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final JdbcMapper.Blob blob = param.getAnnotation(JdbcMapper.Blob.class);
|
case BLOB:
|
||||||
if (blob != null) {
|
|
||||||
return new ByteArrayInputStream(new byte[1]);
|
return new ByteArrayInputStream(new byte[1]);
|
||||||
} else {
|
case CLOB:
|
||||||
final JdbcMapper.Clob clob = param.getAnnotation(JdbcMapper.Clob.class);
|
|
||||||
if (clob != null) {
|
|
||||||
return new StringReader(defaultString);
|
return new StringReader(defaultString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,26 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* Created by mopar on 6/1/17.
|
* Created by mopar on 6/1/17.
|
||||||
*/
|
*/
|
||||||
class InListVariableElement implements VariableElement {
|
class SpecialVariableElement implements VariableElement {
|
||||||
|
|
||||||
|
enum SpecialType {
|
||||||
|
IN_LIST,
|
||||||
|
CLOB,
|
||||||
|
BLOB,
|
||||||
|
}
|
||||||
|
|
||||||
final VariableElement delegate;
|
final VariableElement delegate;
|
||||||
|
final SpecialType specialType;
|
||||||
|
final String blobStringCharset;
|
||||||
|
|
||||||
InListVariableElement(final VariableElement delegate) {
|
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType) {
|
||||||
|
this(delegate, specialType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpecialVariableElement(final VariableElement delegate, final SpecialType specialType, final String blobStringCharset) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
this.specialType = specialType;
|
||||||
|
this.blobStringCharset = blobStringCharset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -85,4 +99,11 @@ class InListVariableElement implements VariableElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//IFJAVA8_END
|
//IFJAVA8_END
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "InListVariableElement{" +
|
||||||
|
"delegate=" + delegate +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.moparisthebest.jdbc.codegen;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.paramPattern;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class ParamPatternTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPattern() {
|
||||||
|
testMatch("{personNo}", null, null, null, null, null, null, "personNo");
|
||||||
|
testMatch("{last_name IN lastNames}", "last_name IN ", "last_name", "IN ", null, null, null, "lastNames");
|
||||||
|
testMatch("{last_name not in lastNames}", "last_name not in ", "last_name", "not in ", "not ", null, null, "lastNames");
|
||||||
|
testMatch("{clob:comment}", null, null, null, null, "clob:", null, "comment");
|
||||||
|
testMatch("{clob: comment}", null, null, null, null, "clob: ", null, "comment");
|
||||||
|
testMatch("{blob: comment}", null, null, null, null, "blob: ", null, "comment");
|
||||||
|
testMatch("{Blob: comment}", null, null, null, null, "Blob: ", null, "comment");
|
||||||
|
testMatch("{blob:utf-16:comment}", null, null, null, null, "blob:utf-16:", "utf-16:", "comment");
|
||||||
|
String blobCharset = "utf-16:";
|
||||||
|
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
|
||||||
|
blobCharset = "utf-16 : ";
|
||||||
|
assertEquals("utf-16", blobCharset.substring(0, blobCharset.indexOf(':')).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatch(final String sql, final String... expected) {
|
||||||
|
final Matcher bindParamMatcher = paramPattern.matcher(sql);
|
||||||
|
while (bindParamMatcher.find()) {
|
||||||
|
final String[] matches = new String[bindParamMatcher.groupCount()];
|
||||||
|
for (int x = 0; x < matches.length; )
|
||||||
|
matches[x] = bindParamMatcher.group(++x);
|
||||||
|
//System.out.println(bindParamMatcher.group() + ": " + java.util.Arrays.toString(matches));
|
||||||
|
assertArrayEquals(bindParamMatcher.group(), expected, matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,8 +42,8 @@ public interface PersonDAO extends JdbcMapper {
|
|||||||
@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}")
|
@JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
|
||||||
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException;
|
void setFirstNameBlob(String firstName, long personNo) throws SQLException;
|
||||||
|
|
||||||
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
||||||
long getPersonNo(String lastName) throws SQLException;
|
long getPersonNo(String lastName) throws SQLException;
|
||||||
|
@ -42,8 +42,8 @@ public interface PrestoPersonDAO extends 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}")
|
@JdbcMapper.SQL("UPDATE person SET first_name = {blob:firstName} WHERE person_no = {personNo}")
|
||||||
void setFirstNameBlob(@JdbcMapper.Blob String firstName, long personNo) throws SQLException;
|
void setFirstNameBlob(String firstName, long personNo) throws SQLException;
|
||||||
|
|
||||||
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
@JdbcMapper.SQL("SELECT person_no FROM person WHERE last_name = {lastName}")
|
||||||
long getPersonNo(String lastName) throws SQLException;
|
long getPersonNo(String lastName) throws SQLException;
|
||||||
|
Loading…
Reference in New Issue
Block a user