1. 程式人生 > >JDBC之PreparedStatement的用法

JDBC之PreparedStatement的用法

jdbc(java database connectivity,java資料庫連線)的api中的主要的四個類之一的java.sql.statement要求開發者付出大量的時間和精力。在使用statement獲取jdbc訪問時所具有的一個共通的問題是輸入適當格式的日期和時間戳:2002-02-05 20:56 或者 02/05/02 8:56 pm。

通過使用java.sql.preparedstatement,這個問題可以自動解決。一個preparedstatement是從 java.sql.connection物件和所提供的sql字串得到的,sql字串中包含問號(?),這些問號標明變數的位置,然後提供變數的值,最後執行語句,例如:

stringsql = "select * from people p where p.id = ? and p.name = ?";
preparedstatement ps = connection.preparestatement(sql);
ps.setint(1,id);
ps.setstring(2,name);
resultset rs = ps.executequery();
使用preparedstatement的另一個優點是字串不是動態建立的。下面是一個動態建立字串的例子:

stringsql = "select * from people p where p.i = "+id;


這允許jvm(javavirtual machine,java虛擬機器)和驅動/資料庫快取語句和字串並提高效能。

preparedstatement也提供資料庫無關性。當顯示宣告的sql越少,那麼潛在的sql語句的資料庫依賴性就越小。

由於preparedstatement具備很多優點,開發者可能通常都使用它,只有在完全是因為效能原因或者是在一行sql語句中沒有變數的時候才使用通常的statement。

一個完整的preparedstatement的例子:

package jstarproject;
import java.sql.*;

public class mypreparedstatement {

private final string db_driver="com.microsoft.jdbc.sqlserver.sqlserverdriver";
private final string url = "jdbc:microsoft:sqlserver://127.0.0.1:1433;databasename=pubs";

  public mypreparedstatement()
  {
  }
  public void query() throws sqlexception{
    connection conn = this.getconnection();
    string strsql = "select emp_id from employee where emp_id = ?";
    preparedstatement pstmt = conn.preparestatement(strsql);
    pstmt.setstring(1,"pma42628m");
    resultset rs = pstmt.executequery();


    while(rs.next()){
       string fname = rs.getstring("emp_id");
       system.out.println("the fname is " + fname);
    }
    rs.close();
    pstmt.close();
    conn.close();

  }
  private connection getconnection() throws sqlexception{
//    class.
    connection conn = null;
    try {
      class.forname(db_driver);
      conn = drivermanager.getconnection(url,"sa","sa");
    }
    catch (classnotfoundexception ex) {}
    return conn;
  }
  //main
  public static void main(string[] args) throws sqlexception {
    mypreparedstatement jdbctest1 = new mypreparedstatement();
    jdbctest1.query();
  }
}


為什麼要始終使用PreparedStatement代替Statement?為什麼要始終使用PreparedStatement代替Statement?


在JDBC應用中,如果你已經是稍有水平開發者,你就應該始終以PreparedStatement代替Statement.也就是說,在任何時候都不要使用Statement.
基於以下的原因:
一.程式碼的可讀性和可維護性.
雖然用PreparedStatement來代替Statement會使程式碼多出幾行,但這樣的程式碼無論從可讀性還是可維護性上來說.都比直接用Statement的程式碼高很多檔次:

stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");

perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();

不用我多說,對於第一種方法.別說其他人去讀你的程式碼,就是你自己過一段時間再去讀,都會覺得傷心.

二.PreparedStatement盡最大可能提高效能.
每一種資料庫都會盡最大努力對預編譯語句提供最大的效能優化.因為預編譯語句有可能被重複呼叫.所以語句在被DB的編譯器編譯後的執行程式碼被快取下來,那麼下次呼叫時只要是相同的預編譯語句就不需要編譯,只要將引數直接傳入編譯過的語句執行程式碼中(相當於一個涵數)就會得到執行.這並不是說只有一個 Connection中多次執行的預編譯語句被快取,而是對於整個DB中,只要預編譯的語句語法和快取中匹配.那麼在任何時候就可以不需要再次編譯而可以直接執行.而statement的語句中,即使是相同一操作,而由於每次操作的資料不同所以使整個語句相匹配的機會極小,幾乎不太可能匹配.比如:
insert into tb_name (col1,col2) values ('11','22');
insert into tb_name (col1,col2) values ('11','23');
即使是相同操作但因為資料內容不一樣,所以整個個語句本身不能匹配,沒有快取語句的意義.事實是沒有資料庫會對普通語句編譯後的執行程式碼快取.

當然並不是所以預編譯語句都一定會被快取,資料庫本身會用一種策略,比如使用頻度等因素來決定什麼時候不再快取已有的預編譯結果.以儲存有更多的空間儲存新的預編譯語句.

三.最重要的一點是極大地提高了安全性.

即使到目前為止,仍有一些人連基本的惡義SQL語法都不知道.
String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";
如果我們把[' or '1' = '1]作為varpasswd傳入進來.使用者名稱隨意,看看會成為什麼?

select * from tb_name = '隨意' and passwd = '' or '1' = '1';
因為'1'='1'肯定成立,所以可以任何通過驗證.更有甚者:
把[';drop table tb_name;]作為varpasswd傳入進來,則:
select * from tb_name = '隨意' and passwd = '';drop table tb_name;有些資料庫是不會讓你成功的,但也有很多資料庫就可以使這些語句得到執行.

而如果你使用預編譯語句.你傳入的任何內容就不會和原來的語句發生任何匹配的關係.只要全使用預編譯語句,你就用不著對傳入的資料做任何過慮.而如果使用普通的statement,有可能要對drop,;等做費盡心機的判斷和過慮.

上面的幾個原因,還不足讓你在任何時候都使用PreparedStatement嗎?

方法:

void addBatch()
將一組引數新增到此 PreparedStatement 物件的批處理命令中。
void clearParameters()
立即清除當前引數值。
boolean execute()
在此 PreparedStatement 物件中執行 SQL 語句,該語句可以是任何種類的 SQL 語句。
ResultSet executeQuery()
在此 PreparedStatement 物件中執行 SQL 查詢,並返回該查詢生成的 ResultSet 物件。
int executeUpdate()
在此 PreparedStatement 物件中執行 SQL 語句,該語句必須是一個 SQL 資料操作語言(Data Manipulation Language,DML)語句,比如 INSERT、UPDATE 或 DELETE 語句;或者是無返回內容的 SQL 語句,比如 DDL 語句。
ResultSetMetaData getMetaData()
獲取包含有關 ResultSet 物件列資訊的 ResultSetMetaData 物件,ResultSet 物件將在執行此 PreparedStatement 物件時返回。
ParameterMetaData getParameterMetaData()
獲取此 PreparedStatement 物件的引數的編號、型別和屬性。
void setArray(int parameterIndex, Array x)
將指定引數設定為給定 java.sql.Array 物件。
void setAsciiStream(int parameterIndex, InputStream x)
將指定引數設定為給定輸入流。
void setAsciiStream(int parameterIndex, InputStream x, int length)
將指定引數設定為給定輸入流,該輸入流將具有給定位元組數。
void setAsciiStream(int parameterIndex, InputStream x, long length)
將指定引數設定為給定輸入流,該輸入流將具有指定位元組數。
void setBigDecimal(int parameterIndex, BigDecimal x)
將指定引數設定為給定 java.math.BigDecimal 值。
void setBinaryStream(int parameterIndex, InputStream x)
將指定引數設定為給定輸入流。
void setBinaryStream(int parameterIndex, InputStream x, int length)
將指定引數設定為給定輸入流,該輸入流將具有給定位元組數。
void setBinaryStream(int parameterIndex, InputStream x, long length)
將指定引數設定為給定輸入流,該輸入流將具有指定位元組數。
void setBlob(int parameterIndex, Blob x)
將指定引數設定為給定 java.sql.Blob 物件。
void setBlob(int parameterIndex, InputStream inputStream)
將指定引數設定為 InputStream 物件。
void setBlob(int parameterIndex, InputStream inputStream, long length)
將指定引數設定為 InputStream 物件。
void setBoolean(int parameterIndex, boolean x)
將指定引數設定為給定 Java boolean 值。
void setByte(int parameterIndex, byte x)
將指定引數設定為給定 Java byte 值。
void setBytes(int parameterIndex, byte[] x)
將指定引數設定為給定 Java byte 陣列。
void setCharacterStream(int parameterIndex, Reader reader)
將指定引數設定為給定 Reader 物件。
void setCharacterStream(int parameterIndex, Reader reader, int length)
將給定引數設定為給定 Reader 物件,該物件具有給定字元數長度。
void setCharacterStream(int parameterIndex, Reader reader, long length)
將指定引數設定為給定 Reader 物件,該物件具有給定字元數長度。
void setClob(int parameterIndex, Clob x)
將指定引數設定為給定 java.sql.Clob 物件。
void setClob(int parameterIndex, Reader reader)
將指定引數設定為 Reader 物件。
void setClob(int parameterIndex, Reader reader, long length)
將指定引數設定為 Reader 物件。
void setDate(int parameterIndex, Date x)
使用執行應用程式的虛擬機器的預設時區將指定引數設定為給定 java.sql.Date 值。
void setDate(int parameterIndex, Date x, Calendar cal)
使用給定的 Calendar 物件將指定引數設定為給定 java.sql.Date 值。
void setDouble(int parameterIndex, double x)
將指定引數設定為給定 Java double 值。
void setFloat(int parameterIndex, float x)
將指定引數設定為給定 Java REAL 值。
void setInt(int parameterIndex, int x)
將指定引數設定為給定 Java int 值。
void setLong(int parameterIndex, long x)
將指定引數設定為給定 Java long 值。
void setNCharacterStream(int parameterIndex, Reader value)
將指定引數設定為 Reader 物件。
void setNCharacterStream(int parameterIndex, Reader value, long length)
將指定引數設定為 Reader 物件。
void setNClob(int parameterIndex, NClob value)
將指定引數設定為 java.sql.NClob 物件。
void setNClob(int parameterIndex, Reader reader)
將指定引數設定為 Reader 物件。
void setNClob(int parameterIndex, Reader reader, long length)
將指定引數設定為 Reader 物件。
void setNString(int parameterIndex, String value)
將指定引數設定為給定 String 物件。
void setNull(int parameterIndex, int sqlType)
將指定引數設定為 SQL NULL。
void setNull(int parameterIndex, int sqlType, String typeName)
將指定引數設定為 SQL NULL。
void setObject(int parameterIndex, Object x)
使用給定物件設定指定引數的值。
void setObject(int parameterIndex, Object x, int targetSqlType)
使用給定物件設定指定引數的值。
void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)
使用給定物件設定指定引數的值。
void setRef(int parameterIndex, Ref x)
將指定引數設定為給定 REF(<structured-type>) 值。
void setRowId(int parameterIndex, RowId x)
將指定引數設定為給定 java.sql.RowId 物件。
void setShort(int parameterIndex, short x)
將指定引數設定為給定 Java short 值。
void setSQLXML(int parameterIndex, SQLXML xmlObject)
將指定引數設定為給定 java.sql.SQLXML 物件。
void setString(int parameterIndex, String x)
將指定引數設定為給定 Java String 值。
void setTime(int parameterIndex, Time x)
將指定引數設定為給定 java.sql.Time 值。
void setTime(int parameterIndex, Time x, Calendar cal)
使用給定的 Calendar 物件將指定引數設定為給定 java.sql.Time 值。
void setTimestamp(int parameterIndex, Timestamp x)
將指定引數設定為給定 java.sql.Timestamp 值。
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
使用給定的 Calendar 物件將指定引數設定為給定 java.sql.Timestamp 值。
void setUnicodeStream(int parameterIndex, InputStream x, int length)
已過時。
void setURL(int parameterIndex, URL x)
將指定引數設定為給定 java.net.URL 值。