Named Parameters for PreparedStatement
PreparedStatement 的命名参数
----------使得JDBC代码更易于读写
PreparedStatement 的命名参数
----------使得JDBC代码更易于读写
作者Adam Crume
PreparedStatement语法存在的问题
PreparedStatement的问题来源于它的参数的语法,PreparedStatement语句的参数是匿名并被通过索引访问,如下代码所示:
PreparedStatement p = con.prepareStatement("select * from people where (first_name = ? or last_name = ?) and address = ?");
p.setString(1, name);
p.setString(2, name);
p.setString(3, address);
对于一两个小的查询来说,这不是问题,但是对于大量参数的查询,跟踪索引就会变得困难。开发者必须小心翼翼的阅读SQL语句并查找出对应的参数索引,如果一个参数被插入或被删除,所有的索引就要被重新命名,很显然,当这个被插入或删除的参数在最开始,而后面还有更多的参数的时候,或者查询被重新组织了,这两种情况都会导致很多麻烦。
解决方法?(Workarounds)
有一个方法可以解决重新命名索引,那就是使用一个计数器来跟踪索引,如下代码所示:
PreparedStatement p = con.prepareStatement("select * from people where (first_name = ? or last_name = ?) and address = ?");
int i = 1;
p.setString(i++, name);
p.setString(i++, name);
p.setString(i++, address);
这种方法对于某个参数被插入或者删除的情况来说,有很好的效果,但是代码依然可读性差,而且程序员依然要注意确保参数的顺序。
NamedParameterStatement
现在我们来看看NamedParameterStatement这个类,这个类和PreparedStatement的基本相同,除了一点,那就是不是用问号?来顶替索引,而是用一个变量来顶替索引,如下面的代码所示:
String query = "select * from people where (first_name = :name or last_name = :name) and address = :address");
NamedParameterStatement p = new NamedParameterStatement(con, query);
p.setString("name", name);
p.setString("address", address);
这个类的内幕就是,NamedParameterStatement类做了一个转换,它使用HashMap将SQL语句种对应的变量转换成问号,然后再创建一个对应的PreparedStatement语句,并且保持了对应变量和索引的映射关系,这两个类是兼容的,所以你可以分别加以利用。
论性能
两者的性能的差别在于NamedParameterStatement多了一个转换的过程,但这个过程所消耗的性能是非常之小,经过本机的测试,执行一条简单的查询表的记录的语句,PreparedStatement花了332毫秒,而NamedParameterStatement则花了352毫秒,总之,性能不是问题
总结
NamedParameterStatement作为PreparedStatement的代替是高效而且无需配置的,同时它的简便的接口也提高了程序员的效率,或者更重要的是,由于代码的可读性增强,维护会变得更加轻松。
作者简介:
Adam 有着4年编写java程序的专业经验,他专攻J2SE核心以及java虚拟机的内核,他目前在 Central Technology Services公司搞web和桌面应用
附:NamedParameterStatement类的源代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This class wraps around a {@link PreparedStatement} and allows the programmer to set parameters by name instead
* of by index. This eliminates any confusion as to which parameter index represents what. This also means that
* rearranging the SQL statement or adding a parameter doesn't involve renumbering your indices.
* Code such as this:
*
* Connection con=getConnection();
* String query="select * from my_table where name=? or address=?";
* PreparedStatement p=con.prepareStatement(query);
* p.setString(1, "bob");
* p.setString(2, "123 terrace ct");
* ResultSet rs=p.executeQuery();
*
* can be replaced with:
*
* Connection con=getConnection();
* String query="select * from my_table where name=:name or address=:address";
* NamedParameterStatement p=new NamedParameterStatement(con, query);
* p.setString("name", "bob");
* p.setString("address", "123 terrace ct");
* ResultSet rs=p.executeQuery();
*
* @author adam_crume
*/
public class NamedParameterStatement {
/** The statement this object is wrapping. */
private final PreparedStatement statement;
/** Maps parameter names to arrays of ints which are the parameter indices. */
private final Map indexMap;
/**
* Creates a NamedParameterStatement. Wraps a call to
* c.{@link Connection#prepareStatement(java.lang.String)
prepareStatement}.
* @param connection the database connection
* @param query the parameterized query
* @throws SQLException if the statement could not be created
*/
public NamedParameterStatement(Connection connection, String query) throws
SQLException {
indexMap=new HashMap();
String parsedQuery=parse(query, indexMap);
statement=connection.prepareStatement(parsedQuery);
}
/**
* Parses a query with named parameters. The parameter-index mappings are
put into the map, and the
* parsed query is returned. DO NOT CALL FROM CLIENT CODE. This
method is non-private so JUnit code can
* test it.
* @param query query to parse
* @param paramMap map to hold parameter-index mappings
* @return the parsed query
*/
static final String parse(String query, Map paramMap) {
// I was originally using regular expressions, but they didn't work well
for ignoring
// parameter-like strings inside quotes.
int length=query.length();
StringBuffer parsedQuery=new StringBuffer(length);
boolean inSingleQuote=false;
boolean inDoubleQuote=false;
int index=1;
for(int i=0;i<length;i++) {
char c=query.charAt(i);
if(inSingleQuote) {
if(c=='/'') {
inSingleQuote=false;
}
} else if(inDoubleQuote) {
if(c=='"') {
inDoubleQuote=false;
}
} else {
if(c=='/'') {
inSingleQuote=true;
} else if(c=='"') {
inDoubleQuote=true;
} else if(c==':' && i+1<length &&
Character.isJavaIdentifierStart(query.charAt(i+1))) {
int j=i+2;
while(j<length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name=query.substring(i+1,j);
c='?'; // replace the parameter with a question mark
i+=name.length(); // skip past the end if the parameter
List indexList=(List)paramMap.get(name);
if(indexList==null) {
indexList=new LinkedList();
paramMap.put(name, indexList);
}
indexList.add(new Integer(index));
index++;
}
}
parsedQuery.append(c);
}
// replace the lists of Integer objects with arrays of ints
for(Iterator itr=paramMap.entrySet().iterator(); itr.hasNext();) {
Map.Entry entry=(Map.Entry)itr.next();
List list=(List)entry.getValue();
int[] indexes=new int[list.size()];
int i=0;
for(Iterator itr2=list.iterator(); itr2.hasNext();) {
Integer x=(Integer)itr2.next();
indexes[i++]=x.intValue();
}
entry.setValue(indexes);
}
return parsedQuery.toString();
}
/**
* Returns the indexes for a parameter.
* @param name parameter name
* @return parameter indexes
* @throws IllegalArgumentException if the parameter does not exist
*/
private int[] getIndexes(String name) {
int[] indexes=(int[])indexMap.get(name);
if(indexes==null) {
throw new IllegalArgumentException("Parameter not found: "+name);
}
return indexes;
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setObject(int, java.lang.Object)
*/
public void setObject(String name, Object value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setObject(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setString(int, java.lang.String)
*/
public void setString(String name, String value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setString(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(String name, int value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setInt(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setLong(String name, long value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setLong(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
*/
public void setTimestamp(String name, Timestamp value) throws SQLException
{
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setTimestamp(indexes , value);
}
}
/**
* Returns the underlying statement.
* @return the statement
*/
public PreparedStatement getStatement() {
return statement;
}
/**
* Executes the statement.
* @return true if the first result is a {@link ResultSet}
* @throws SQLException if an error occurred
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
return statement.execute();
}
/**
* Executes the statement, which must be a query.
* @return the query results
* @throws SQLException if an error occurred
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
return statement.executeQuery();
}
/**
* Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
statement;
* or an SQL statement that returns nothing, such as a DDL statement.
* @return number of rows affected
* @throws SQLException if an error occurred
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
return statement.executeUpdate();
}
/**
* Closes the statement.
* @throws SQLException if an error occurred
* @see Statement#close()
*/
public void close() throws SQLException {
statement.close();
}
/**
* Adds the current set of parameters as a batch entry.
* @throws SQLException if something went wrong
*/
public void addBatch() throws SQLException {
statement.addBatch();
}
/**
* Executes all of the batched statements.
*
* See {@link Statement#executeBatch()} for details.
* @return update counts for each statement
* @throws SQLException if something went wrong
*/
public int[] executeBatch() throws SQLException {
return statement.executeBatch();
}
}
PreparedStatement语法存在的问题
PreparedStatement的问题来源于它的参数的语法,PreparedStatement语句的参数是匿名并被通过索引访问,如下代码所示:
PreparedStatement p = con.prepareStatement("select * from people where (first_name = ? or last_name = ?) and address = ?");
p.setString(1, name);
p.setString(2, name);
p.setString(3, address);
对于一两个小的查询来说,这不是问题,但是对于大量参数的查询,跟踪索引就会变得困难。开发者必须小心翼翼的阅读SQL语句并查找出对应的参数索引,如果一个参数被插入或被删除,所有的索引就要被重新命名,很显然,当这个被插入或删除的参数在最开始,而后面还有更多的参数的时候,或者查询被重新组织了,这两种情况都会导致很多麻烦。
解决方法?(Workarounds)
有一个方法可以解决重新命名索引,那就是使用一个计数器来跟踪索引,如下代码所示:
PreparedStatement p = con.prepareStatement("select * from people where (first_name = ? or last_name = ?) and address = ?");
int i = 1;
p.setString(i++, name);
p.setString(i++, name);
p.setString(i++, address);
这种方法对于某个参数被插入或者删除的情况来说,有很好的效果,但是代码依然可读性差,而且程序员依然要注意确保参数的顺序。
NamedParameterStatement
现在我们来看看NamedParameterStatement这个类,这个类和PreparedStatement的基本相同,除了一点,那就是不是用问号?来顶替索引,而是用一个变量来顶替索引,如下面的代码所示:
String query = "select * from people where (first_name = :name or last_name = :name) and address = :address");
NamedParameterStatement p = new NamedParameterStatement(con, query);
p.setString("name", name);
p.setString("address", address);
这个类的内幕就是,NamedParameterStatement类做了一个转换,它使用HashMap将SQL语句种对应的变量转换成问号,然后再创建一个对应的PreparedStatement语句,并且保持了对应变量和索引的映射关系,这两个类是兼容的,所以你可以分别加以利用。
论性能
两者的性能的差别在于NamedParameterStatement多了一个转换的过程,但这个过程所消耗的性能是非常之小,经过本机的测试,执行一条简单的查询表的记录的语句,PreparedStatement花了332毫秒,而NamedParameterStatement则花了352毫秒,总之,性能不是问题
总结
NamedParameterStatement作为PreparedStatement的代替是高效而且无需配置的,同时它的简便的接口也提高了程序员的效率,或者更重要的是,由于代码的可读性增强,维护会变得更加轻松。
作者简介:
Adam 有着4年编写java程序的专业经验,他专攻J2SE核心以及java虚拟机的内核,他目前在 Central Technology Services公司搞web和桌面应用
附:NamedParameterStatement类的源代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This class wraps around a {@link PreparedStatement} and allows the programmer to set parameters by name instead
* of by index. This eliminates any confusion as to which parameter index represents what. This also means that
* rearranging the SQL statement or adding a parameter doesn't involve renumbering your indices.
* Code such as this:
*
* Connection con=getConnection();
* String query="select * from my_table where name=? or address=?";
* PreparedStatement p=con.prepareStatement(query);
* p.setString(1, "bob");
* p.setString(2, "123 terrace ct");
* ResultSet rs=p.executeQuery();
*
* can be replaced with:
*
* Connection con=getConnection();
* String query="select * from my_table where name=:name or address=:address";
* NamedParameterStatement p=new NamedParameterStatement(con, query);
* p.setString("name", "bob");
* p.setString("address", "123 terrace ct");
* ResultSet rs=p.executeQuery();
*
* @author adam_crume
*/
public class NamedParameterStatement {
/** The statement this object is wrapping. */
private final PreparedStatement statement;
/** Maps parameter names to arrays of ints which are the parameter indices. */
private final Map indexMap;
/**
* Creates a NamedParameterStatement. Wraps a call to
* c.{@link Connection#prepareStatement(java.lang.String)
prepareStatement}.
* @param connection the database connection
* @param query the parameterized query
* @throws SQLException if the statement could not be created
*/
public NamedParameterStatement(Connection connection, String query) throws
SQLException {
indexMap=new HashMap();
String parsedQuery=parse(query, indexMap);
statement=connection.prepareStatement(parsedQuery);
}
/**
* Parses a query with named parameters. The parameter-index mappings are
put into the map, and the
* parsed query is returned. DO NOT CALL FROM CLIENT CODE. This
method is non-private so JUnit code can
* test it.
* @param query query to parse
* @param paramMap map to hold parameter-index mappings
* @return the parsed query
*/
static final String parse(String query, Map paramMap) {
// I was originally using regular expressions, but they didn't work well
for ignoring
// parameter-like strings inside quotes.
int length=query.length();
StringBuffer parsedQuery=new StringBuffer(length);
boolean inSingleQuote=false;
boolean inDoubleQuote=false;
int index=1;
for(int i=0;i<length;i++) {
char c=query.charAt(i);
if(inSingleQuote) {
if(c=='/'') {
inSingleQuote=false;
}
} else if(inDoubleQuote) {
if(c=='"') {
inDoubleQuote=false;
}
} else {
if(c=='/'') {
inSingleQuote=true;
} else if(c=='"') {
inDoubleQuote=true;
} else if(c==':' && i+1<length &&
Character.isJavaIdentifierStart(query.charAt(i+1))) {
int j=i+2;
while(j<length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name=query.substring(i+1,j);
c='?'; // replace the parameter with a question mark
i+=name.length(); // skip past the end if the parameter
List indexList=(List)paramMap.get(name);
if(indexList==null) {
indexList=new LinkedList();
paramMap.put(name, indexList);
}
indexList.add(new Integer(index));
index++;
}
}
parsedQuery.append(c);
}
// replace the lists of Integer objects with arrays of ints
for(Iterator itr=paramMap.entrySet().iterator(); itr.hasNext();) {
Map.Entry entry=(Map.Entry)itr.next();
List list=(List)entry.getValue();
int[] indexes=new int[list.size()];
int i=0;
for(Iterator itr2=list.iterator(); itr2.hasNext();) {
Integer x=(Integer)itr2.next();
indexes[i++]=x.intValue();
}
entry.setValue(indexes);
}
return parsedQuery.toString();
}
/**
* Returns the indexes for a parameter.
* @param name parameter name
* @return parameter indexes
* @throws IllegalArgumentException if the parameter does not exist
*/
private int[] getIndexes(String name) {
int[] indexes=(int[])indexMap.get(name);
if(indexes==null) {
throw new IllegalArgumentException("Parameter not found: "+name);
}
return indexes;
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setObject(int, java.lang.Object)
*/
public void setObject(String name, Object value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setObject(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setString(int, java.lang.String)
*/
public void setString(String name, String value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setString(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(String name, int value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setInt(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setLong(String name, long value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setLong(indexes , value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
*/
public void setTimestamp(String name, Timestamp value) throws SQLException
{
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setTimestamp(indexes , value);
}
}
/**
* Returns the underlying statement.
* @return the statement
*/
public PreparedStatement getStatement() {
return statement;
}
/**
* Executes the statement.
* @return true if the first result is a {@link ResultSet}
* @throws SQLException if an error occurred
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
return statement.execute();
}
/**
* Executes the statement, which must be a query.
* @return the query results
* @throws SQLException if an error occurred
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
return statement.executeQuery();
}
/**
* Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
statement;
* or an SQL statement that returns nothing, such as a DDL statement.
* @return number of rows affected
* @throws SQLException if an error occurred
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
return statement.executeUpdate();
}
/**
* Closes the statement.
* @throws SQLException if an error occurred
* @see Statement#close()
*/
public void close() throws SQLException {
statement.close();
}
/**
* Adds the current set of parameters as a batch entry.
* @throws SQLException if something went wrong
*/
public void addBatch() throws SQLException {
statement.addBatch();
}
/**
* Executes all of the batched statements.
*
* See {@link Statement#executeBatch()} for details.
* @return update counts for each statement
* @throws SQLException if something went wrong
*/
public int[] executeBatch() throws SQLException {
return statement.executeBatch();
}
}
转自:http://doodle.5d6d.com/thread-50-1-1.html
原文:http://www.javaworld.com/javaworld/jw-04-2007/jw-04-jdbc.html?fsrc=rss-index