本節(jié)介紹Statement接口及其子類PreparedStatement和CallableStatement。 它還描述了相關(guān)主題,包括轉(zhuǎn)義語法,性能提示和自動(dòng)生成的鍵
13.1 Statement 接口
Statement接口定義了執(zhí)行不包含參數(shù)標(biāo)記的SQL語句的方法。 PreparedStatement接口添加了設(shè)置輸入?yún)?shù)的方法,CallableStatement接口添加了用于檢索從存儲過程返回的輸出參數(shù)值的方法
13.1.1 創(chuàng)建 Statement 接口
語句對象由Connection對象創(chuàng)建,如代碼示例13-1所示:
Connection conn = dataSource.getConnection(user, passwd);
Statement stmt = conn.createStatement()
每個(gè)Connection對象都可以創(chuàng)建可以由程序同時(shí)使用的多個(gè)Statement對象。 這在代碼示例13-2中演示
Connection conn = ds.getConnection(user, passwd);
// create two instances of Statement
Statement stmt1 = conn.createStatement();
Statement stmt2 = conn.createStatement();
13.1.1 設(shè)置結(jié)果集特性
可以使用其他構(gòu)造函數(shù)來設(shè)置語句生成的任何結(jié)果集的類型,并發(fā)性或類型,并發(fā)性和可保持性。 有關(guān)ResultSet界面的更多信息,請參見第15章“結(jié)果集”
代碼示例13-3創(chuàng)建一個(gè)Statement對象,該對象返回可滾動(dòng)的結(jié)果集,對ResultSet對象打開時(shí)所做的更改不敏感,可以更新,并且在調(diào)用commit時(shí)不關(guān)閉ResultSet對象
Connection conn = ds.getConnection(user, passwd);
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResultSet.HOLD_CURSORS_OVER_COMMIT);
13.1.2 執(zhí)行 Statement 對象
用于執(zhí)行Statement對象的方法取決于正在執(zhí)行的SQL語句的類型。 如果Statement對象表示返回ResultSet對象的SQL查詢,則應(yīng)該使用executeQuery方法。 如果SQL已知為DDL語句或返回更新計(jì)數(shù)的DML語句,則應(yīng)使用executeUpdate方法。 如果SQL語句的類型不知道,則應(yīng)該使用execute方法
13.1.2.1 返回 ResultSet 對象
代碼示例13-4顯示了返回ResultSet對象的SQL字符串的執(zhí)行
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“select TITLE, AUTHOR, ISBN " +
"from BOOKLIST”);
while (rs.next()){
...
}
如果正在執(zhí)行的SQL字符串不返回ResultSet對象,那么executeQuery方法會拋出一個(gè)SQLException
13.1.2.2 返回更新的行數(shù)
在代碼示例13-5中,正在執(zhí)行的SQL語句返回受SQL數(shù)據(jù)操作語言(DML)語句更新影響的行數(shù),或者對于不返回任何內(nèi)容的SQL語句返回0。
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate(“update STOCK set ORDER = ‘Y’ " +
"where SUPPLY = 0”);
if (rows > 0) {
...
}
執(zhí)行返回更新計(jì)數(shù)的Statement對象
如果正在執(zhí)行的SQL字符串返回一個(gè)ResultSet,那么executeUpdate方法會拋出SQLException
備注:如果您的數(shù)據(jù)庫支持返回可能超過Integer.MAX_VALUE的更新計(jì)數(shù),請使用executeLargeUpdate方法
13.1.2.3 返回未知或多個(gè)結(jié)果
如果有多個(gè)結(jié)果,或者如果在運(yùn)行時(shí)之前不知道Statement對象返回的結(jié)果的類型或數(shù)量,則Statement對象應(yīng)該使用方法execute執(zhí)行。 方法getMoreResults,getUpdateCount和getResultSet可用于檢索所有結(jié)果
備注:如果您的數(shù)據(jù)庫支持返回可能超過Integer.MAX_VALUE的更新計(jì)數(shù),請使用getLargeUpdateCount 方法
如果第一個(gè)結(jié)果是ResultSet對象,那么方法execute將返回true,如果它是更新計(jì)數(shù),則返回false
當(dāng)方法執(zhí)行返回true時(shí),方法getResultSet被調(diào)用來檢索ResultSet對象。 當(dāng)執(zhí)行返回false時(shí),方法getUpdateCount返回一個(gè)int。 如果該數(shù)字大于或等于零,則表示該語句返回的更新計(jì)數(shù)。 如果為-1,則表示沒有更多結(jié)果
如果返回多個(gè)結(jié)果,可以調(diào)用getMoreResults方法來獲取下一個(gè)結(jié)果。 與方法執(zhí)行一樣,如果下一個(gè)結(jié)果是ResultSet對象,getMoreResults將返回true,如果下一個(gè)結(jié)果是更新計(jì)數(shù)或沒有更多結(jié)果可用,則返回false
Statement stmt = conn.createStatement();
boolean retval = cstmt.execute(sql_queries);
ResultSet rs;
int count;
do {
if (retval == false) {count = stmt.getUpdateCount();
if (count == -1) {
// no more results
break;
} else {
// process update count
}
} else { // ResultSet
rs = stmt.getResultSet();
// process ResultSet
}
retval = stmt.getMoreResults();
while (true);
默認(rèn)情況下,對方法getMoreResults的每次調(diào)用都會關(guān)閉getResultSet方法返回的任何先前的ResultSet對象。 但是,getMoreResults方法可能會使用一個(gè)參數(shù)來指定getResultSet返回的ResultSet對象是否應(yīng)該被關(guān)閉。 Statement接口定義了可以提供給方法getMoreResults的三個(gè)常量
- CLOSE_CURRENT_RESULTS :表示當(dāng)返回下一個(gè)ResultSet對象時(shí),當(dāng)前的ResultSet對象應(yīng)該被關(guān)閉
- KEEP_CURRENT_RESULTS :表示當(dāng)返回下一個(gè)ResultSet對象時(shí),不應(yīng)關(guān)閉當(dāng)前的ResultSet對象
- CLOSE_ALL_RESULTS : 表示當(dāng)返回下一個(gè)結(jié)果時(shí),任何已保持打開的ResultSet對象應(yīng)該被關(guān)閉
如果當(dāng)前結(jié)果是更新計(jì)數(shù)而不是ResultSet對象,則將忽略傳遞給getMoreResults的任何參數(shù)
要確定驅(qū)動(dòng)程序是否實(shí)現(xiàn)此功能,應(yīng)用程序可以調(diào)用DatabaseMetaData方法支持多打開結(jié)果
ResultSet rs1 = stmt.getResultSet();
rs1.next();
...
retval = stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
if (retval == true) {
ResultSet rs2 = stmt.getResultSet();
rs2.next();
...
rs1.next();
}
retval = stmt.getMoreResults(Statement.CLOSE_ALL_RESULTS);
...
13.1.3 限制Statement對象的執(zhí)行時(shí)間
setQueryTimeout方法可用于指定JDBC驅(qū)動(dòng)程序嘗試取消正在運(yùn)行的語句之前的最短時(shí)間。 JDBC驅(qū)動(dòng)程序必須將此限制應(yīng)用于execute,executeBatch,executeQuery和executeUpdate方法。 一旦數(shù)據(jù)源有機(jī)會處理終止運(yùn)行命令的請求,SQLException將被拋出給客戶端,并且不會對先前運(yùn)行的命令發(fā)生額外的處理,而無需重新執(zhí)行Statement
一些JDBC驅(qū)動(dòng)程序?qū)崿F(xiàn)也可以將此限制應(yīng)用于ResultSet方法。 有關(guān)詳細(xì)信息,請咨詢驅(qū)動(dòng)程序供應(yīng)商文檔
在聲明批處理的情況下,定義了超時(shí)是否應(yīng)用于通過addBatch方法添加的單個(gè)SQL命令或由executeBatch方法調(diào)用的整個(gè)SQL命令的實(shí)現(xiàn)的實(shí)現(xiàn)
13.1.4 關(guān)閉 Statement 對象
一個(gè)應(yīng)用程序調(diào)用Statement.close方法來指示它已經(jīng)完成處理語句。 當(dāng)創(chuàng)建它們的連接關(guān)閉時(shí),所有Statement對象將被關(guān)閉。 但是,一旦應(yīng)用程序完成處理,就關(guān)閉語句是很好的編碼習(xí)慣。 這允許立即釋放語句所使用的任何外部資源
關(guān)閉Statement對象將關(guān)閉并聲明該Statement對象生成的ResultSet的任何實(shí)例。 在垃圾回收再次運(yùn)行之前,ResultSet對象持有的資源可能不會被釋放,所以在不再需要時(shí)顯式關(guān)閉ResultSet對象是一個(gè)很好的做法
一旦聲明已關(guān)閉,除了isClosed或close方法之外,任何訪問其方法的嘗試將導(dǎo)致拋出SQLException
關(guān)于Statement對象的這些注釋也適用于PreparedStatement和CallableStatement對象
13.2 PreparedStatement 接口
PreparedStatement接口擴(kuò)展了Statement,增加了為語句中包含的參數(shù)標(biāo)記設(shè)置值的功能
PreparedStatement對象表示可以準(zhǔn)備或預(yù)編譯的SQL語句,用于執(zhí)行一次,然后執(zhí)行多次。 在SQL字符串中由“?”表示的參數(shù)標(biāo)記用于指定在運(yùn)行時(shí)可能會有變化的語句的輸入值
13.2.1 創(chuàng)建 PreparedStatement
Prepared語句的實(shí)例以與Statement對象相同的方式創(chuàng)建,除了在創(chuàng)建語句時(shí)提供SQL命令
Connection conn = ds.getConnection(user, passwd);
PreparedStatement ps = conn.prepareStatement(“INSERT INTO BOOKLIST" +
"(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)”);
13.2.1.1 設(shè)置 ResultSet 特性
與createStatement一樣,prepareStatement方法定義了一個(gè)構(gòu)造函數(shù),可以用于指定該準(zhǔn)備語句生成的結(jié)果集的特征
Connection conn = ds.getConnection(user, passwd);
PreparedStatement ps = conn.prepareStatement(
“SELECT AUTHOR, TITLE FROM BOOKLIST WHERE ISBN = ?”,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE);
13,2.2 設(shè)置參數(shù)
PreparedStatement接口定義了setter方法,用于替換預(yù)編譯SQL字符串中每個(gè)參數(shù)標(biāo)記的值。 方法的名稱遵循模式“set <Type>”
例如,setString方法用于指定一個(gè)需要字符串的參數(shù)標(biāo)記的值。 這些設(shè)置器方法中的每一個(gè)至少需要兩個(gè)參數(shù)。 第一個(gè)始終是等于要設(shè)置的參數(shù)的順序位置的int,從1開始。第二個(gè)和任何其余參數(shù)指定要分配給參數(shù)的值
PreparedStatement ps = conn.prepareStatement(“INSERT INTO BOOKLIST" +
"(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)”);
ps.setString(1, “Zamiatin, Evgenii”);
ps.setString(2, “We”);
ps.setLong(3, 140185852L);
必須為PreparedStatement對象中的每個(gè)參數(shù)標(biāo)記提供一個(gè)值才能執(zhí)行。 用于執(zhí)行PreparedStatement對象(executeQuery,executeUpdate和execute)的方法將拋出SQLException,如果不為參數(shù)標(biāo)記提供值
為PreparedStatement對象的參數(shù)標(biāo)記設(shè)置的值在執(zhí)行時(shí)不會重置。 可以調(diào)用clearParameters方法以顯式清除已設(shè)置的值。 用不同的值設(shè)置參數(shù)將替換以前的值
備注:如果在執(zhí)行PreparedStatement對象時(shí),JDBC驅(qū)動(dòng)程序通過方法setAsciiStream,setBinaryStream,setCharacterStream,setNCharacterStream或setUnicodeStream讀取為參數(shù)標(biāo)記設(shè)置的值,則在下一次執(zhí)行PreparedStatement對象之前必須重置這些參數(shù),否則SQLException 將被拋出。 如果在同一個(gè)PreparedStatement對象執(zhí)行中的多個(gè)參數(shù)標(biāo)記使用相同的流,也會拋出SQLException
對于任何給定的語句,應(yīng)用程序不應(yīng)修改傳遞給setXXX方法的值參數(shù),在setXXX方法被調(diào)用之后,并且在執(zhí)行后續(xù)執(zhí)行executeQuery,executeUpdate,executeBatch或clearParameters方法被調(diào)用之前。 如果有一個(gè)后續(xù)setXXX方法調(diào)用覆蓋上一個(gè)值或者如果Statement不被重用,應(yīng)用程序可以在執(zhí)行execute,executeQuery,executeUpdate,executeBatch或clearParameters方法之后修改value參數(shù)。 不符合此限制可能導(dǎo)致不可預(yù)知的行為
13.2.2.1 類型轉(zhuǎn)換
在PreparedStatement setter方法中指定的數(shù)據(jù)類型是Java編程語言中的數(shù)據(jù)類型。 JDBC驅(qū)動(dòng)程序負(fù)責(zé)將其映射到相應(yīng)的JDBC類型(java.sql.Types中定義的SQL類型之一),以便它是發(fā)送到數(shù)據(jù)源的適當(dāng)類型。 默認(rèn)映射在附錄B表B-2中指定。
13.2.2.2 國際字符集轉(zhuǎn)換
SQL:2003提供對國際字符集類型的支持,這些類型在SQL:2003規(guī)范中被描述為一個(gè)實(shí)現(xiàn)定義的字符集。以下JDBC類型可用于訪問國際字符集類型:NCHAR,NVARCHAR,LONGNVARCHAR和NCLOB。這些類型類似于CHAR,VARCHAR,LONGVARCHAR和CLOB,除了使用替代字符集(國際字符集)對值進(jìn)行編碼。由于Java類型使用UTF-16編碼字符數(shù)據(jù),所以沒有理由使用備用Java類型來保存這些值。然而,區(qū)分CLOB和NCLOB可能是有利的。 JDBC規(guī)范使用字符串表示NCHAR,NVARCHAR和LONGNVARCHAR數(shù)據(jù),在Java字符集和國際字符集之間自動(dòng)轉(zhuǎn)換。 JDBC使用NClob來表示NCLOB值。 Clob和NClob值之間沒有自動(dòng)轉(zhuǎn)換。有關(guān)Java語言如何使用Unicode的其他信息,請參閱java.lang.Character的Java API文檔
為了最大的可移植性,當(dāng)特定值對應(yīng)于國際字符類型時(shí),應(yīng)用程序必須向JDBC驅(qū)動(dòng)程序指示。 當(dāng)指定作為國家字符類型的參數(shù)標(biāo)記的值時(shí),應(yīng)用程序?qū)⒄{(diào)用setNString,setNCharStream,setNClob或setObject方法。 如果使用setObject方法,則必須將目標(biāo)數(shù)據(jù)類型指定為Types.NCHAR,Types.NCLOB,Types.NVARCHAR或Types.LONGNVARCHAR。 如果應(yīng)用程序未指示參數(shù)標(biāo)記值對應(yīng)于國家字符類型,則JDBC驅(qū)動(dòng)程序可能會錯(cuò)誤地解釋該值,從而導(dǎo)致數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤的可能性。 在JDBC驅(qū)動(dòng)程序可以檢測到可能發(fā)生數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤的情況下,對setXXX方法的調(diào)用將導(dǎo)致拋出SQLException。 驅(qū)動(dòng)程序可能不總是發(fā)現(xiàn)可能會發(fā)生數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤。
如果驅(qū)動(dòng)程序不支持國家字符類型,則嘗試調(diào)用setNString,setNCharacterStream,setNClob或setObject的方法,將目標(biāo)數(shù)據(jù)類型指定為國際字符集,可能會導(dǎo)致拋出SQLException
要檢索一個(gè)國際字符值,應(yīng)用程序?qū)⒄{(diào)用getNString,getNClob,getNCharacterStream或getObject的方法
13,2,2,3 使用方法setObject進(jìn)行類型轉(zhuǎn)換
方法setObject可用于將Java編程語言中的對象轉(zhuǎn)換為JDBC類型
當(dāng)setObject傳遞Java對象和JDBC數(shù)據(jù)類型時(shí),轉(zhuǎn)換是顯式的。 驅(qū)動(dòng)程序?qū)L試將對象轉(zhuǎn)換為指定的JDBC類型,然后再將其傳遞給數(shù)據(jù)源。 如果對象無法轉(zhuǎn)換為目標(biāo)類型,則拋出SQLException對象。 在代碼示例13-11中,類型為整型的Java對象正在轉(zhuǎn)換為JDBC類型SHORT
Integer value = new Integer(15);
ps.setObject(1, value, java.sql.Types.SHORT);
如果沒有使用type參數(shù)調(diào)用setObject,則使用該對象類型的默認(rèn)映射隱式地映射Java對象
Integer value = new Integer(15);
// value is mapped to java.sql.Types.INTEGER
ps.setObject(1, value);
方法setObject將對具有自定義映射的SQL UDT執(zhí)行自定義映射。 有關(guān)詳細(xì)信息,請參見第17章“自定義類型映射”。
13.2.2.4 設(shè)置 NULL 參數(shù)
方法setNull可用于將任何參數(shù)設(shè)置為JDBC NULL。 它需要兩個(gè)參數(shù),參數(shù)標(biāo)記的順序位置和參數(shù)的JDBC類型
ps.setNull(2, java.sql.Types.VARCHAR);
如果將Java null傳遞給使用Java對象的任何setter方法,則該參數(shù)將被設(shè)置為JDBC NULL
并非所有數(shù)據(jù)庫都允許將非類型的Null發(fā)送到底層數(shù)據(jù)源。 為了最大的可移植性,應(yīng)該使用setNull或setObject(int parameterIndex,Object x,int sqlType)方法,而不是使用setObject(int parameterIndex,Object x)。
13.2.2.5 清除參數(shù)
通過調(diào)用clearParameters方法可以明確地清除為PreparedStatement對象的IN參數(shù)標(biāo)記設(shè)置的值。 PreparedStatement對象用于表示設(shè)置值的任何資源也被釋放
13.2.3 描述PreparedStatement對象的輸出和輸入
PreparedStatement.getMetaData方法檢索一個(gè)ResultSetMetaData對象,該對象包含執(zhí)行時(shí)準(zhǔn)備語句返回的列的描述。 ResultSetMetaData對象包含每個(gè)返回列的記錄。 ResultSetMetaData接口中的方法提供有關(guān)返回的列數(shù)和每列的特性的信息
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM CATALOG");
ResultSetMetaData rsmd = pstmt.getMetaData();
int colCount = rsmd.getColumnCount();
int colType;
String colLabel;
for (int i = 1; i <= colCount; i++) {
colType = rsmd.getColumnType(i);
colLabel = rsmd.getColumnLabel(i);
...
}
PreparedStatement.getParameterMetaData方法返回一個(gè)ParameterMetaData對象,描述出現(xiàn)在PreparedStatement對象中的參數(shù)標(biāo)記。 ParameterMetaData接口中的方法提供了有關(guān)參數(shù)數(shù)量及其特征的信息
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM BOOKLIST WHERE ISBN = ?");
ParameterMetaData pmd = pstmt.getParameterMetaData();
int colType = pmd.getParameterType(1);
...
13.2.4 執(zhí)行 PreparedStatement 對象
與Statement對象一樣,用于執(zhí)行PreparedStatement對象的方法取決于正在執(zhí)行的SQL語句的類型。 如果PreparedStatement對象是返回ResultSet對象的查詢,則應(yīng)使用executeQuery方法執(zhí)行。 如果是返回更新計(jì)數(shù)的DML語句,則應(yīng)使用executeUpdate方法執(zhí)行。 只有當(dāng)語句的返回類型未知時(shí),才應(yīng)使用方法execute
如果使用SQL字符串作為參數(shù)調(diào)用了任何PreparedStatement execute方法,則拋出SQLException
13.2.5 返回 ResultSet 對象
代碼示例13-16顯示正在準(zhǔn)備并然后多次執(zhí)行的查詢
PreparedStatement pstmt = conn.prepareStatement(“SELECT AUTHOR, " +
"TITLE FROM BOOKLIST WHERE SECTION = ?”);
for (int i = 1; i <= maxSectionNumber; i++) {
pstmt.setInt(1, i);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// process the record
}
rs.close();
}
pstmt.close();
如果正在執(zhí)行的語句不返回ResultSet對象,則executeQuery拋出SQLException
13.2.6 返回更新行數(shù)
如果有多個(gè)結(jié)果,或者如果PreparedStatement對象返回的結(jié)果的類型或數(shù)量在運(yùn)行時(shí)尚未知,則PreparedStatement對象應(yīng)使用方法execute執(zhí)行。 方法getMoreResults,getUpdateCount和getResultSet可用于檢索所有結(jié)果
如果您的數(shù)據(jù)庫支持返回可能超過Integer.MAX_VALUE的更新計(jì)數(shù),請使用getLargeUpdateCount方法
PreparedStatement pstmt = conn.prepareStatement(sqlStatement);
boolean retval = pstmt.execute();
ResultSet rs;
int count;
do {
if (retval == false) {
count = pstmt.getUpdateCount();
if (count == -1) {
// no more results
break;
} else {
// process update count
}
} else { // ResultSet
rs = pstmt.getResultSet();
// process ResultSet
}
retval = cstmt.getMoreResults();
while (true);
13.2 CallableStatement 接口
CallableStatement接口使用用于執(zhí)行和檢索存儲過程結(jié)果的方法擴(kuò)展了PreparedStatement
13.3 創(chuàng)建 CallableStatement 對象
與Statement和PreparedStatement對象一樣,CallableStatement對象由Connection對象創(chuàng)建。 代碼示例13-19顯示了創(chuàng)建一個(gè)CallableStatement對象,用于調(diào)用存儲過程'validate',它具有一個(gè)返回參數(shù)和另外兩個(gè)參數(shù)
CallableStatement cstmt = conn.prepareCall(
“{? = call validate(?, ?)}”);
本章中的所有示例都使用轉(zhuǎn)義語法來調(diào)用存儲過程。 請參見第116頁的“存儲過程和函數(shù)”
13.3.2 設(shè)置參數(shù)
CallableStatement對象可以采用三種類型的參數(shù):IN,OUT和INOUT。 該參數(shù)可以被指定為序數(shù)參數(shù)或命名參數(shù)。 必須為表示IN或INOUT參數(shù)的語句中的每個(gè)參數(shù)標(biāo)記設(shè)置一個(gè)值。 必須為表示OUT或INOUT參數(shù)的每個(gè)參數(shù)標(biāo)記調(diào)用registerOutParameter方法
可以使用DatabaseMetaData方法getProcedureColumns來確定存儲過程的參數(shù)的數(shù)量,類型和屬性
參數(shù)ordinals(它們是傳遞給適當(dāng)setter方法的整數(shù))引用語句中的參數(shù)標(biāo)記(“?”),從一開始。 語句中的文字參數(shù)值不會增加參數(shù)標(biāo)記的序數(shù)值。 在代碼示例13-20中,兩個(gè)參數(shù)標(biāo)記具有序數(shù)值1和2。
CallableStatement cstmt = con.prepareCall(
"{CALL PROC(?, "Literal_Value", ?)}");
cstmt.setString(1, "First");
cstmt.setString(2, "Third");
命名參數(shù)也可用于指定具體參數(shù)。 當(dāng)過程具有許多具有默認(rèn)值的參數(shù)時(shí),這是特別有用的。 命名參數(shù)可用于僅指定沒有默認(rèn)值的值。 參數(shù)的名稱對應(yīng)于DatabaseMetaData.getProcedureColumns返回的COLUMN_NAME字段
在代碼示例13-21中,過程COMPLEX_PROC需要十個(gè)參數(shù),但僅需要第一和第五個(gè)參數(shù)PARAM_1和PARAM_5
CallableStatement cstmt = con.prepareCall(
"{CALL COMPLEX_PROC(?, ?)}";
cstmt.setString("PARAM_1", "Price");
cstmt.setFloat("PARAM_5", 150.25);
可以調(diào)用DatabaseMetaData.support Named Parameters方法來確定JDBC驅(qū)動(dòng)程序和底層數(shù)據(jù)源是否支持指定命名參數(shù)
不能將設(shè)置參數(shù)與序號和名稱組合在同一語句中。 如果在同一語句中使用ordinals和名稱作為參數(shù),則拋出SQLException
在某些情況下,可能無法僅為一個(gè)過程提供一些參數(shù)。 例如,如果過程名稱過載,則數(shù)據(jù)源根據(jù)參數(shù)數(shù)量確定要調(diào)用的過程。 必須提供足夠的參數(shù)以允許數(shù)據(jù)源解決任何歧義
13.3.2.1 IN Parameters
IN參數(shù)是使用Setter方法分配的值,如第100頁“設(shè)置參數(shù)”中所述。在代碼示例13-22中,設(shè)置字符串參數(shù)和日期參數(shù)
cstmt.setString(1, “October”);
cstmt.setDate(2, date);
13.3.2.2 OUT Parameters
在執(zhí)行CallableStatement對象之前,必須調(diào)用registerOutParameter方法來設(shè)置每個(gè)OUT參數(shù)的類型。 當(dāng)存儲過程從執(zhí)行返回時(shí),它將使用這些類型來設(shè)置任何OUT參數(shù)的值
可以使用CallableStatement接口中定義的適當(dāng)?shù)膅etter方法檢索OUT參數(shù)的值。 代碼示例13-23顯示了具有兩個(gè)OUT參數(shù),一個(gè)字符串和浮點(diǎn)數(shù)以及OUT參數(shù)值檢索的存儲過程的執(zhí)行
CallableStatement cstmt = conn.prepareCall(
“{CALL GET_NAME_AND_NUMBER(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.STRING);
cstmt.registerOutParameter(2, java.sql.Types.FLOAT);
cstmt.execute();
// Retrieve OUT parameters
String name = cstmt.getString(1);
float number = cstmt.getFloat(2);
13.3.2.3 INOUT Parameters
輸入?yún)?shù)和輸出參數(shù)的參數(shù)都必須使用適當(dāng)?shù)膕etter方法進(jìn)行設(shè)置,也可以通過調(diào)用registerOutParameter方法進(jìn)行注冊。 setter方法隱含的類型(見附錄B“數(shù)據(jù)類型轉(zhuǎn)換表”中的表B-1)和提供給方法registerOutParameter的類型必須相同
代碼示例13-24顯示了存儲過程calc,它需要一個(gè)INPUT float參數(shù)
CallableStatement cstmt = conn.prepareCall(“{CALL CALC(?)}”);
cstmt.setFloat(1, 1237.98f);
ctsmt.registerOutParameter(1, java.sql.Types.FLOAT);
cstmt.execute();
float f = cstmt.getFloat(1);
13.3.2.4 清除參數(shù)
通過調(diào)用clearParameters方法,可以明確地清除為CallableStatement對象IN參數(shù)標(biāo)記或OUT參數(shù)標(biāo)記注冊的值。 CallableStatement對象用于表示集合或注冊值的任何資源也將被釋放。
13.3.2 執(zhí)行 CallableStatement 對象
與Statement和PreparedStatement對象一樣,用于執(zhí)行CallableStatement對象的方法取決于是否返回單個(gè)ResultSet對象,更新計(jì)數(shù)或多個(gè)混合結(jié)果
13.3.3 返回 一個(gè)單例的 ResultSet 對象
代碼示例13-25顯示了一個(gè)CallableStatement對象的執(zhí)行,該對象需要一個(gè)輸入?yún)?shù)并返回單個(gè)ResultSet對象
CallableStatement cstmt = conn.prepareCall(“{CALL GETINFO(?)}”);
cstmt.setLong(1, 1309944422);
ResultSet rs = cstmt.executeQuery();
// process the results
while (rs.next()) {
...
}
rs.close();
cstmt.close();
如果存儲過程不返回ResultSet對象,那么executeQuery將拋出SQLException。
13.3.3.2 返回一個(gè) 更新行數(shù)
代碼示例13-26顯示了返回更新計(jì)數(shù)的CallableStatement對象的執(zhí)行
CallableStatement cstmt = conn.prepareCall(“{call GETCOUNT(?)}”);
cstmt.setString(1, “Smith”);
int count = cstmt.executeUpdate();
cstmt.close();
如果存儲過程返回一個(gè)ResultSet,那么executeUpdate方法會拋出一個(gè)SQLException
13.3.3.3 返回未知或多個(gè)結(jié)果
如果有多個(gè)結(jié)果,或者如果CallableStatement對象返回的結(jié)果的類型或數(shù)量在運(yùn)行時(shí)才不知道,那么CallableStatement對象應(yīng)該使用方法execute執(zhí)行。 方法getMoreResults,getUpdateCount和getResultSet可用于檢索所有結(jié)果
代碼示例13-27顯示了如何從CallableStatment對象檢索所有結(jié)果
CallableStatement cstmt = conn.prepareCall(procCall);
boolean retval = cstmt.execute();
ResultSet rs;
int count;
do {
if (retval == false) {
count = cstmt.getUpdateCount();
if (count == -1) {
// no more results
break;
} else {
// process update count
}
} else { // ResultSet
rs = cstmt.getResultSet();
// process ResultSet
}
retval = cstmt.getMoreResults();
while (true);
13.3.4 REF光游標(biāo)支持
REF CURSOR數(shù)據(jù)類型由幾個(gè)數(shù)據(jù)庫支持。 要從存儲過程返回REF CURSOR,可以使用CallableStatement方法registerOutParameter指定Types.REF_CURSOR作為要返回的數(shù)據(jù)類型。 CallableStatement方法getObject將指定ResultSet作為將返回的對象轉(zhuǎn)換為的類型,將被調(diào)用來檢索表示REF CURSOR的ResultSet。 返回的結(jié)果集是前向,只讀結(jié)果
如果調(diào)用registerOutParameter指定Types.REF CURSOR,并且JDBC驅(qū)動(dòng)程序不支持此數(shù)據(jù)類型,則會拋出SQLFeatureNotSupportedException
CallableStatement cstmt = conn.prepareCall(" { call mySproc(?) }");
cstmt.registerOutParameter(1, Types.REF_CURSOR);
cstmt.executeQuery();
ResultSet rs = cstmt.getObject(1, ResultSet.class);
while (rs.next ()) {
System.out.println("Name="+ rs.getString(1));
}
13.4 轉(zhuǎn)義語法
Statement對象中使用的SQL字符串可能包含JDBC轉(zhuǎn)義語法。 Escape語法允許驅(qū)動(dòng)程序更容易地掃描需要特殊處理的語法。 在驅(qū)動(dòng)層實(shí)現(xiàn)這一特殊處理可以提高應(yīng)用的可移植性。
可能需要以下特殊轉(zhuǎn)義處理
- 通常使用的功能不具有由SQL定義的標(biāo)準(zhǔn)語法,或者底層數(shù)據(jù)源支持的本機(jī)語法在供應(yīng)商之間差異很大。 在這種情況下,驅(qū)動(dòng)程序可以將轉(zhuǎn)義語法轉(zhuǎn)換為特定的本地語法。
- 基礎(chǔ)數(shù)據(jù)源不支持但由驅(qū)動(dòng)程序?qū)崿F(xiàn)的功能
使用方法setEscapeProcessing打開或關(guān)閉Statement對象的轉(zhuǎn)義處理,默認(rèn)為on。 RowSet接口還包括一個(gè)setEscapeProcessing方法。 RowSet方法適用于用于填充RowSet對象的SQL字符串。 setEscapeProcessing方法對于PreparedStatement對象不起作用,因?yàn)樵趧?chuàng)建PreparedStatement對象時(shí),它的SQL字符串可能已被預(yù)編譯
JDBC定義了以下的轉(zhuǎn)義語法:
- scalar functions
- date and time literals
- outer joins
- calling stored procedures and functions
- escape characters for LIKE clauses
13.4.1 Scalar 函數(shù)
幾乎所有底層數(shù)據(jù)源都支持標(biāo)量值上的數(shù)字,字符串,時(shí)間,日期,系統(tǒng)和轉(zhuǎn)換函數(shù)。 訪問標(biāo)量函數(shù)的轉(zhuǎn)義語法是:
{fn <function-name> (argument list)}
例如,以下代碼調(diào)用帶有兩個(gè)參數(shù)的函數(shù)concat來連接:
{fn concat("Hot", "Java")}
以下語法將獲取當(dāng)前數(shù)據(jù)庫用戶的名稱:
{fn user()}
Scalar函數(shù)可能由具有稍微不同的本機(jī)語法的不同數(shù)據(jù)源支持,并且所有驅(qū)動(dòng)程序可能不支持Scalar函數(shù)。 驅(qū)動(dòng)程序?qū)⒂成滢D(zhuǎn)義的函數(shù)調(diào)用到本機(jī)語法或直接實(shí)現(xiàn)函數(shù)
各種DatabaseMetaData方法列出了支持的功能。 例如,getNumericFunctions方法返回一個(gè)逗號分隔的Open Group CLI名稱的數(shù)字函數(shù)列表,方法getStringFunctions返回字符串函數(shù)等等
附錄C“標(biāo)量函數(shù)”提供了驅(qū)動(dòng)程序希望支持的標(biāo)量函數(shù)的列表。只有在數(shù)據(jù)源支持時(shí)才需要驅(qū)動(dòng)程序來實(shí)現(xiàn)這些函數(shù),但是
標(biāo)量函數(shù)的轉(zhuǎn)義語法只能用于調(diào)用附錄C“標(biāo)量函數(shù)”中定義的標(biāo)量函數(shù)。轉(zhuǎn)義語法不用于調(diào)用用戶定義或供應(yīng)商特定的標(biāo)量函數(shù)
13.4.2 日期和時(shí)間文字
數(shù)據(jù)源在日期,時(shí)間和時(shí)間戳文字中使用的語法有很大不同。 JDBC API支持這些文字的語法的ISO標(biāo)準(zhǔn)格式,使用驅(qū)動(dòng)程序轉(zhuǎn)換為本機(jī)語法的轉(zhuǎn)義子句
日期文字的轉(zhuǎn)義語法是:
{d 'yyyy-mm-dd'}
驅(qū)動(dòng)程序?qū)⒂玫刃У谋镜乇硎痉ㄌ鎿Qescape子句。 例如,如果這是底層數(shù)據(jù)源的適當(dāng)格式,驅(qū)動(dòng)程序可能會用'28 -FEB-99'替換{d'1999-02-28'}
TIME和TIMESTAMP文字的轉(zhuǎn)義語法是:
{t 'hh:mm:ss'}
{ts 'yyyy-mm-dd hh:mm:ss.f . . .'}
備注:當(dāng)指定日期或時(shí)間戳文字中的mm或dd時(shí),前導(dǎo)零可能會被省略
13.4.3 Outer Joins
外部連接是高級功能,并且不受所有數(shù)據(jù)源的支持。 有關(guān)外部聯(lián)接的說明,請參閱相關(guān)的SQL文檔
外連接的轉(zhuǎn)義語法是:
{oj <outer-join>}
where <outer-join> has the form:
table {LEFT|RIGHT|FULL} OUTER JOIN {table | <outer-join>} ON search-condition
(注意,前一行中的花括號({})表示必須使用它們之間的項(xiàng)之一;它們不是語法的一部分。)以下SELECT語句使用外連接的轉(zhuǎn)義語法。
Statement stmt = con.createStatement();
stmt.executeQuery("SELECT * FROM {oj TABLE1 " +
"LEFT OUTER JOIN TABLE2 ON DEPT_NO = 003420930}");
JDBC API提供了三個(gè)DatabaseMetaData方法來確定驅(qū)動(dòng)程序支持的外部連接的種類:supportsOuterJoins,supportsFullOuterJoins和supportsLimitedOuterJoins
13.3.4 存儲過程和功能
如果數(shù)據(jù)庫支持存儲過程,則可以使用JDBC轉(zhuǎn)義語法調(diào)用它們,如下所示:
{call <procedure_name> [(<argument-list>)]}
or, where a procedure returns a result parameter:
{? = call <procedure_name> [(<argument-list>)]}
方括號表示(參數(shù)列表)部分是可選的。 輸入?yún)?shù)可以是文字或參數(shù)標(biāo)記。 有關(guān)參數(shù)的信息,請參見第108頁上的“設(shè)置參數(shù)”
如果數(shù)據(jù)庫支持存儲過程,則DatabaseMetaData.supportsStoredProcedures方法返回true
JDBC驅(qū)動(dòng)程序可以選擇性地為使用存儲過程的轉(zhuǎn)義語法調(diào)用用戶定義或供應(yīng)商定義的函數(shù)提供支持
如果數(shù)據(jù)庫支持使用存儲過程的轉(zhuǎn)義語法調(diào)用用戶定義或供應(yīng)商定義的函數(shù),則DatabaseMetaData.supportsStoredFunctionsUsingCallSyntax方法返回true。 有關(guān)其他信息,請參閱JDBC驅(qū)動(dòng)程序的文檔
13.4.5 LIKE 轉(zhuǎn)義字符
百分號(%)和下劃線()字符是SQL LIKE子句中的通配符(%匹配零個(gè)或多個(gè)字符,與一個(gè)字符匹配)。 為了從字面上解釋它們,它們之前可以是一個(gè)反斜杠(\),它是字符串中的一個(gè)特殊的轉(zhuǎn)義字符。 可以通過在LIKE謂詞的末尾包含以下語法來指定要用作轉(zhuǎn)義字符的字符:
{escape '<escape-character>'}
例如,以下查詢使用反斜杠作為轉(zhuǎn)義字符,并查找以下劃線開頭的標(biāo)識符名稱。 請注意,Java編譯器將無法將該反斜杠識別為字符,除非其前面是反斜杠
stmt.executeQuery("SELECT name FROM Identifiers " +
"WHERE Id LIKE '\_%' {escape '\'}");
13.4.6 限制返回行轉(zhuǎn)義
用于限制查詢返回的行數(shù)的轉(zhuǎn)義語法是:
{limit <limit clause>}
where the format for the <limit clause> is:
rows [offset row_offset]
方括號表示“offset row_offset”部分是可選的。 為行指定的值表示從此查詢返回的最大行數(shù)。 row_offset表示在開始返回行之前從查詢返回的行中跳過的行數(shù)。 對于row_offset,值為0表示不跳過任何行。 rows和row_offset的值必須為0或更大的整數(shù)值
The following query will return no more than 20 rows:
Statement stmt = con.createStatement();
stmt.executeQuery("SELECT * FROM TABLE1 " +
"WHERE F1 >100 {limit 20}");
13.5 性能提示
Statement接口有兩種方法可用于向JDBC驅(qū)動(dòng)程序提供提示:setFetchDirection和setFetchSize。 提供給這些方法的值應(yīng)用于由語句生成的每個(gè)結(jié)果集。 可以使用ResultSet接口中相同名稱的方法為該結(jié)果集提供提示。
如果驅(qū)動(dòng)程序不適合,則通過此接口提供給驅(qū)動(dòng)程序的提示可能會被驅(qū)動(dòng)程序忽略
getFetchDirection和getFetchSize方法返回提示的當(dāng)前值。 如果在調(diào)用相應(yīng)的setter方法之前調(diào)用這些方法中的任一種,則返回的值是實(shí)現(xiàn)定義的
13.6 檢索自動(dòng)生成的值
許多數(shù)據(jù)庫系統(tǒng)具有在插入行時(shí)自動(dòng)生成值的機(jī)制。 根據(jù)執(zhí)行的SQL,表定義和數(shù)據(jù)源的配置,生成的值可能是也可能不是唯一的或表示鍵值。 可以調(diào)用以獲取生成的值的Statement.getGeneratedKeys方法返回一個(gè)具有每個(gè)自動(dòng)生成值的列的ResultSet對象。 方法執(zhí)行,executeUpdate或Connection.prepareStatement接受可選參數(shù),可用于指示在執(zhí)行或準(zhǔn)備語句時(shí)應(yīng)返回任何自動(dòng)生成的值
Statement stmt = conn.createStatement();// indicate that the key generated is going to be returned
int rows = stmt.executeUpdate("INSERT INTO ORDERS " +
"(ISBN, CUSTOMERID) " +
"VALUES (195123018, ’BILLG’)",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
boolean b = rs.next();
if (b == true) {
// retrieve the new key value
...
}
其他方法允許將應(yīng)返回的列的序數(shù)或名稱指定為數(shù)組。 如果未指定列,則JDBC驅(qū)動(dòng)程序?qū)崿F(xiàn)將確定要返回的列或值
在代碼示例13-31中,使用兩個(gè)參數(shù)調(diào)用Statement方法executeUpdate,第一個(gè)是要執(zhí)行的SQL語句,第二個(gè)是String數(shù)組,該數(shù)組包含調(diào)用getGeneratedKeys時(shí)應(yīng)返回的列名稱
String keyColumn[] = {"ORDER_ID"};
...
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("INSERT INTO ORDERS " +
"(ISBN, CUSTOMERID) " +
"VALUES (966431502, ’BILLG’)",
keyColumn);
ResultSet rs = stmt.getGeneratedKeys();
在auto-commit為true時(shí)調(diào)用getGeneratedKeys方法的結(jié)果是實(shí)現(xiàn)定義的。 為了在檢索自動(dòng)生成的值時(shí)增加應(yīng)用程序可移植性,Connection auto-commit屬性應(yīng)設(shè)置為false
關(guān)于getGeneratedKeys是否將在調(diào)用executeBatch方法后返回生成的值是實(shí)現(xiàn)定義的
有關(guān)詳細(xì)信息,請參閱API規(guī)范
在getGeneratedKeys返回的ResultSet對象上調(diào)用ResultSet.getMetaData將生成一個(gè)ResultSetMetaData對象,該對象可用于確定生成值的數(shù)量,類型和屬性
在某些情況下,例如在insert select語句中,可能會返回多個(gè)值。 getGeneratedKeys返回的ResultSet對象將包含一條語句生成的每個(gè)值。 如果未生成任何值,則返回空結(jié)果集
getGeneratedKeys返回的ResultSet對象的并發(fā)性必須是CONCUR_READ_ONLY。 ResultSet對象的類型必須是TYPE_FORWARD_ONLY或TYPE_SCROLL_INSENSITIVE
如果JDBC驅(qū)動(dòng)程序和底層數(shù)據(jù)源支持檢索自動(dòng)生成的值,則DatabaseMetaData.supportsGetGeneratedKeys的方法返回true。 如果通過對supportsGetGeneratedKeys的調(diào)用返回true值,則JDBC驅(qū)動(dòng)程序必須支持為SQL INSERT語句返回自動(dòng)生成的值。 一些JDBC驅(qū)動(dòng)程序?qū)崿F(xiàn)也可能支持使用除INSERT之外的SQL語句的自動(dòng)生成的值。 有關(guān)詳細(xì)信息,請參閱JDBC驅(qū)動(dòng)程序文檔