日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

callablestatement存储过程

發布時間:2025/3/16 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 callablestatement存储过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述?
CallableStatement 對象為所有的 DBMS 提供了一種以標準形式調用已儲存過程的方法。已儲存過程儲存在數據庫中。對已儲存過程的調用是 CallableStatement 對象所含的內容。這種調用是用一種換碼語法來寫的,有兩種形式:一種形式帶結果參數,另一種形式不帶結果參數(有關換碼語法的信息,參見第 4 節“語句”)。結果參數是一種輸出 (OUT) 參數,是已儲存過程的返回值。兩種形式都可帶有數量可變的輸入(IN 參數)、輸出(OUT 參數)或輸入和輸出(INOUT 參數)的參數。問號將用作參數的占位符。

在 JDBC 中調用已儲存過程的語法如下所示。注意,方括號表示其間的內容是可選項;方括號本身并不是語法的組成部份。

{call 過程名[(?, ?, ...)]}

返回結果參數的過程的語法為:

{? = call 過程名[(?, ?, ...)]}

不帶參數的已儲存過程的語法類似:

{call 過程名}

通常,創建 CallableStatement 對象的人應當知道所用的 DBMS 是支持已儲存過程的,并且知道這些過程都是些什么。然而,如果需要檢查,多種 DatabaseMetaData 方法都可以提供這樣的信息。例如,如果 DBMS 支持已儲存過程的調用,則 supportsStoredProcedures 方法將返回 true,而 getProcedures 方法將返回對已儲存過程的描述。

CallableStatement 繼承 Statement 的方法(它們用于處理一般的 SQL 語句),還繼承了 PreparedStatement 的方法(它們用于處理 IN 參數)。CallableStatement 中定義的所有方法都用于處理 OUT 參數或 INOUT 參數的輸出部分:注冊 OUT 參數的 JDBC 類型(一般 SQL 類型)、從這些參數中檢索結果,或者檢查所返回的值是否為 JDBC NULL。


1 創建 CallableStatement 對象?
CallableStatement 對象是用 Connection 方法 prepareCall 創建的。下例創建 CallableStatement 的實例,其中含有對已儲存過程 getTestData 調用。該過程有兩個變量,但不含結果參數:

CallableStatement cstmt = con.prepareCall(?
"{call getTestData(?, ?)}");

其中 ? 占位符為 IN、 OUT 還是 INOUT 參數,取決于已儲存過程 getTestData。


2 IN 和 OUT 參數?
將 IN 參數傳給 CallableStatement 對象是通過 setXXX 方法完成的。該方法繼承自 PreparedStatement。所傳入參數的類型決定了所用的 setXXX 方法(例如,用 setFloat 來傳入 float 值等)。

如果已儲存過程返回 OUT 參數,則在執行 CallableStatement 對象以前必須先注冊每個 OUT 參數的 JDBC 類型(這是必需的,因為某些 DBMS 要求 JDBC 類型)。注冊 JDBC 類型是用 registerOutParameter 方法來完成的。語句執行完后,CallableStatement 的 getXXX 方法將取回參數值。正確的 getXXX 方法是為各參數所注冊的 JDBC 類型所對應的 Java 類型(從 JDBC 類型到 Java 類型的標準映射見 8.6.1 節中的表)。換言之, registerOutParameter 使用的是 JDBC 類型(因此它與數據庫返回的 JDBC 類型匹配),而 getXXX 將之轉換為 Java 類型。

作為示例,下述代碼先注冊 OUT 參數,執行由 cstmt 所調用的已儲存過程,然后檢索在 OUT 參數中返回的值。方法 getByte 從第一個 OUT 參數中取出一個 Java 字節,而 getBigDecimal 從第二個 OUT 參數中取出一個 BigDecimal 對象(小數點后面帶三位數):

CallableStatement cstmt = con.prepareCall(?
"{call getTestData(?, ?)}");?
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);?
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);?
cstmt.executeQuery();?
byte x = cstmt.getByte(1);?
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);

CallableStatement 與 ResultSet 不同,它不提供用增量方式檢索大 OUT 值的特殊機制。


3 INOUT 參數?
既支持輸入又接受輸出的參數(INOUT 參數)除了調用 registerOutParameter 方法外,還要求調用適當的 setXXX 方法(該方法是從 PreparedStatement 繼承來的)。setXXX 方法將參數值設置為輸入參數,而 registerOutParameter 方法將它的 JDBC 類型注冊為輸出參數。setXXX 方法提供一個 Java 值,而驅動程序先把這個值轉換為 JDBC 值,然后將它送到數據庫中。

這種 IN 值的 JDBC 類型和提供給 registerOutParameter 方法的 JDBC 類型應該相同。然后,要檢索輸出值,就要用對應的 getXXX 方法。例如,Java 類型為 byte 的參數應該使用方法 setByte 來賦輸入值。應該給 registerOutParameter 提供類型為 TINYINT 的 JDBC 類型,同時應使用 getByte 來檢索輸出值 (第 8 節“JDBC 和 Java 類型之間的映射”將給出詳細信息和類型映射表)。

下例假設有一個已儲存過程 reviseTotal,其唯一參數是 INOUT 參數。方法 setByte 把此參數設為 25,驅動程序將把它作為 JDBC TINYINT 類型送到數據庫中。接著,registerOutParameter 將該參數注冊為 JDBC TINYINT。執行完該已儲存過程后,將返回一個新的 JDBC TINYINT 值。方法 getByte 將把這個新值作為 Java byte 類型檢索。

CallableStatement cstmt = con.prepareCall(?
"{call reviseTotal(?)}");?
cstmt.setByte(1, 25);?
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);?
cstmt.executeUpdate();?
byte x = cstmt.getByte(1);

4 先檢索結果,再檢索 OUT 參數?

由于某些 DBMS 的限制,為了實現最大的可移植性,建議先檢索由執行 CallableStatement 對象所產生的結果,然后再用 CallableStatement.getXXX 方法來檢索 OUT 參數。

如果 CallableStatement 對象返回多個 ResultSet 對象(通過調用 execute 方法),在檢索 OUT 參數前應先檢索所有的結果。這種情況下,為確保對所有的結果都進行了訪問,必須對 Statement 方法 getResultSet、getUpdateCount 和 getMoreResults 進行調用,直到不再有結果為止。

檢索完所有的結果后,就可用 CallableStatement.getXXX 方法來檢索 OUT 參數中的值。


5 檢索作為 OUT 參數的 NULL 值?

返回到 OUT 參數中的值可能會是 JDBC NULL。當出現這種情形時,將對 JDBC NULL 值進行轉換以使 getXXX 方法所返回的值為 null、0 或 false,這取決于 getXXX 方法類型。對于 ResultSet 對象,要知道 0 或 false 是否源于 JDBC NULL 的唯一方法,是用方法 wasNull 進行檢測。如果 getXXX 方法讀取的最后一個值是 JDBC NULL,則該方法返回 true,否則返回 flase。第 5 節“ResultSet”將給出詳細信息。??

?

本文闡述了怎么使用DBMS存儲過程。我闡述了使用存儲過程的基本的和高級特性,比如返回ResultSet。本文假設你對DBMS和JDBC已經非常熟 悉,也假設你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言),但是,并不要求你有任何存儲過程的編程經歷。?
存儲過程是指保存在數據庫并在數據庫端執行的程序。你可以使用特殊的語法在Java類中調用存儲過程。在調用時,存儲過程的名稱及指定的參數通過JDBC連接發送給DBMS,執行存儲過程并通過連接(如果有)返回結果。?
使用存儲過程擁有和使用基于EJB或CORBA這樣的應用服務器一樣的好處。區別是存儲過程可以從很多流行的DBMS中免費使用,而應用服務器大都非常昂 貴。這并不只是許可證費用的問題。使用應用服務器所需要花費的管理、編寫代碼的費用,以及客戶程序所增加的復雜性,都可以通過DBMS中的存儲過程所整個 地替代。?
你可以使用Java,Python,Perl或C編寫存儲過程,但是通常使用你的DBMS所指定的特定語言。Oracle使用 PL/SQL,PostgreSQL使用pl/pgsql,DB2使用Procedural SQL。這些語言都非常相似。在它們之間移植存儲過程并不比在Sun的EJB規范不同實現版本之間移植Session Bean困難。并且,存儲過程是為嵌入SQL所設計,這使得它們比Java或C等語言更加友好地方式表達數據庫的機制。?
因為存儲過程運行在DBMS自身,這可以幫助減少應用程序中的等待時間。不是在Java代碼中執行4個或5個SQL語句,而只需要在服務器端執行1個存儲過程。網絡上的數據往返次數的減少可以戲劇性地優化性能。

使用存儲過程

簡單的老的JDBC通過CallableStatement類支持存儲過程的調用。該類實際上是PreparedStatement的一個子類。假設我們 有一個poets數據庫。數據庫中有一個設置詩人逝世年齡的存儲過程。下面是對老酒鬼Dylan Thomas(old soak Dylan Thomas,不指定是否有關典故、文化,請批評指正。譯注)進行調用的詳細代碼:

try
{
??? int age = 39;
??? String poetName = "dylan thomas";
??? CallableStatement proc =
??????? connection.prepareCall("{ call set_death_age(?, ?) }");
??? proc.setString(1, poetName);
??? proc.setInt(2, age);
??? cs.execute();
}
catch (SQLException e)
{
??? // ....
}

傳給prepareCall方法的字串是存儲過程調用的書寫規范。它指定了存儲過程的名稱,?代表了你需要指定的參數。?
和JDBC集成是存儲過程的一個很大的便利:為了從應用中調用存儲過程,不需要存根(stub)類或者配置文件,除了你的DBMS的JDBC驅動程序外什么也不需要。?
當 這段代碼執行時,數據庫的存儲過程就被調用。我們沒有去獲取結果,因為該存儲過程并不返回結果。執行成功或失敗將通過例外得知。失敗可能意味著調用存儲過 程時的失敗(比如提供的一個參數的類型不正確),或者一個應用程序的失敗(比如拋出一個例外指示在poets數據庫中并不存在“Dylan Thomas”)

結合SQL操作與存儲過程

映射Java對象到SQL表中的行相當簡單,但是通常需要執行幾個 SQL語句;可能是一個SELECT查找ID,然后一個INSERT插入指定ID的數據。在高度規格化(符合更高的范式,譯注)的數據庫模式中,可能需要 多個表的更新,因此需要更多的語句。Java代碼會很快地膨脹,每一個語句的網絡開銷也迅速增加。?
將這些SQL語句轉移到一個存儲過程中將大 大簡化代碼,僅涉及一次網絡調用。所有關聯的SQL操作都可以在數據庫內部發生。并且,存儲過程語言,例如PL/SQL,允許使用SQL語法,這比 Java代碼更加自然。下面是我們早期的存儲過程,使用Oracle的PL/SQL語言編寫:

create procedure set_death_age(poet VARCHAR2, poet_age NUMBER)
??? poet_id NUMBER;
begin
SELECT id INTO poet_id FROM poets WHERE name = poet;
INSERT INTO deaths (mort_id, age) VALUES (poet_id, poet_age);
end set_death_age;

很獨特?不。我打賭你一定期待看到一個poets表上的UPDATE。這也暗示了使用存儲過程實現是多么容易的一件事情。set_death_age幾乎 可以肯定是一個很爛的實現。我們應該在poets表中添加一列來存儲逝世年齡。Java代碼中并不關心數據庫模式是怎么實現的,因為它僅調用存儲過程。我 們以后可以改變數據庫模式以提高性能,但是我們不必修改我們代碼。?
下面是調用上面存儲過程的Java代碼:

public static void setDeathAge(Poet dyingBard, int age)
??? throws SQLException
{
?? Connection con = null;
?? CallableStatement proc = null;

?? try
?? {
????? con = connectionPool.getConnection();
????? proc = con.prepareCall("{ call set_death_age(?, ?) }");
????? proc.setString(1, dyingBard.getName());
????? proc.setInt(2, age);
????? proc.execute();
?? }
?? finally
?? {
????? try
????? {
???????? proc.close();
????? }
????? catch (SQLException e) {}
????? con.close();
?? }
}

為了確保可維護性,建議使用像這兒這樣的static方法。這也使得調用存儲過程的代碼集中在一個簡單的模版代碼中。如果你用到許多存儲過程,就會發現僅需要拷貝、粘貼就可以創建新的方法。因為代碼的模版化,甚至也可以通過腳本自動生產調用存儲過程的代碼。

Functions

存儲過程可以有返回值,所以CallableStatement類有類似getResultSet這樣的方法來獲取返回值。當存儲過程返回一個值時,你必 須使用registerOutParameter方法告訴JDBC驅動器該值的SQL類型是什么。你也必須調整存儲過程調用來指示該過程返回一個值。?
下面接著上面的例子。這次我們查詢Dylan Thomas逝世時的年齡。這次的存儲過程使用PostgreSQL的pl/pgsql:

create function snuffed_it_when (VARCHAR) returns integer '
declare
??? poet_id NUMBER;
??? poet_age NUMBER;
begin
??? -- first get the id associated with the poet.
??? SELECT id INTO poet_id FROM poets WHERE name = $1;
??? -- get and return the age.
??? SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;
??? return age;
end;
' language 'pl/pgsql';

另外,注意pl/pgsql參數名通過Unix和DOS腳本的$n語法引用。同時,也注意嵌入的注釋,這是和Java代碼相比的另一個優越性。在Java中寫這樣的注釋當然是可以的,但是看起來很凌亂,并且和SQL語句脫節,必須嵌入到Java String中。?
下面是調用這個存儲過程的Java代碼:

connection.setAutoCommit(false);
CallableStatement proc =
??? connection.prepareCall("{ ? = call snuffed_it_when(?) }");
proc.registerOutParameter(1, Types.INTEGER);
proc.setString(2, poetName);
cs.execute();
int age = proc.getInt(2);

如果指定了錯誤的返回值類型會怎樣?那么,當調用存儲過程時將拋出一個RuntimeException,正如你在ResultSet操作中使用了一個錯誤的類型所碰到的一樣。

復雜的返回值

關于存儲過程的知識,很多人好像就熟悉我們所討論的這些。如果這是存儲過程的全部功能,那么存儲過程就不是其它遠程執行機制的替換方案了。存儲過程的功能比這強大得多。?
當你執行一個SQL查詢時,DBMS創建一個叫做cursor(游標)的數據庫對象,用于在返回結果中迭代每一行。ResultSet是當前時間點的游標的一個表示。這就是為什么沒有緩存或者特定數據庫的支持,你只能在ResultSet中向前移動。?
某些DBMS允許從存儲過程中返回游標的一個引用。JDBC并不支持這個功能,但是Oracle、PostgreSQL和DB2的JDBC驅動器都支持在ResultSet上打開到游標的指針(pointer)。?
設想列出所有沒有活到退休年齡的詩人,下面是完成這個功能的存儲過程,返回一個打開的游標,同樣也使用PostgreSQL的pl/pgsql語言:

create procedure list_early_deaths () return refcursor as '
declare
??? toesup refcursor;
begin
??? open toesup for
??????? SELECT poets.name, deaths.age
??????? FROM poets, deaths
??????? -- all entries in deaths are for poets.
??????? -- but the table might become generic.
??????? WHERE poets.id = deaths.mort_id
??????????? AND deaths.age < 60;
??? return toesup;
end;
' language 'plpgsql';

下面是調用該存儲過程的Java方法,將結果輸出到PrintWriter:?
PrintWriter:

static void sendEarlyDeaths(PrintWriter out)
{
??? Connection con = null;
??? CallableStatement toesUp = null;
??? try
??? {
??????? con = ConnectionPool.getConnection();

??????? // PostgreSQL needs a transaction to do this...
??????? con.setAutoCommit(false);

??????? // Setup the call.
??????? CallableStatement toesUp
??????????? = connection.prepareCall("{ ? = call list_early_deaths () }");
??????? toesUp.registerOutParameter(1, Types.OTHER);
??????? getResults.execute();

??????? ResultSet rs = (ResultSet) getResults.getObject(1);
??????? while (rs.next())
??????? {
??????????? String name = rs.getString(1);
??????????? int age = rs.getInt(2);
??????????? out.println(name + " was " + age + " years old.");
??????? }
??????? rs.close();
??? }
??? catch (SQLException e)
??? {
??????? // We should protect these calls.
??????? toesUp.close();
??????? con.close();
??? }
}

因為JDBC并不直接支持從存儲過程中返回游標,我們使用Types.OTHER來指示存儲過程的返回類型,然后調用getObject()方法并對返回值進行強制類型轉換。?
這個調用存儲過程的Java方法是mapping的一個好例子。Mapping是對一個集上的操作進行抽象的方法。不是在這個過程上返回一個集,我們可以 把操作傳送進去執行。本例中,操作就是把ResultSet打印到一個輸出流。這是一個值得舉例的很常用的例子,下面是調用同一個存儲過程的另外一個方法 實現:

public class ProcessPoetDeaths
{
??? public abstract void sendDeath(String name, int age);
}

static void mapEarlyDeaths(ProcessPoetDeaths mapper)
{
??? Connection con = null;
??? CallableStatement toesUp = null;
??? try
??? {
??????? con = ConnectionPool.getConnection();
??????? con.setAutoCommit(false);

??????? CallableStatement toesUp
??????????? = connection.prepareCall("{ ? = call list_early_deaths () }");
??????? toesUp.registerOutParameter(1, Types.OTHER);
??????? getResults.execute();

??????? ResultSet rs = (ResultSet) getResults.getObject(1);
??????? while (rs.next())
??????? {
??????????? String name = rs.getString(1);
??????????? int age = rs.getInt(2);
??????????? mapper.sendDeath(name, age);
??????? }
??????? rs.close();
??? }
??? catch (SQLException e)
??? {
??????? // We should protect these calls.
??????? toesUp.close();
??????? con.close();
??? }
}

這允許在ResultSet數據上執行任意的處理,而不需要改變或者復制獲取ResultSet的方法:

static void sendEarlyDeaths(final PrintWriter out)
{
??? ProcessPoetDeaths myMapper = new ProcessPoetDeaths()
??? {
??????? public void sendDeath(String name, int age)
??????? {
??????????? out.println(name + " was " + age + " years old.");
??????? }
??? };
??? mapEarlyDeaths(myMapper);
}

這 個方法使用ProcessPoetDeaths的一個匿名實例調用mapEarlyDeaths。該實例擁有sendDeath方法的一個實現,和我們上 面的例子一樣的方式把結果寫入到輸出流。當然,這個技巧并不是存儲過程特有的,但是和存儲過程中返回的ResultSet結合使用,是一個非常強大的工 具。

結論

存儲過程可以幫助你在代碼中分離邏輯,這基本上總是有益的。這個分離的好處有:?
&#8226; 快速創建應用,使用和應用一起改變和改善的數據庫模式。?
&#8226; 數據庫模式可以在以后改變而不影響Java對象,當我們完成應用后,可以重新設計更好的模式。?
&#8226; 存儲過程通過更好的SQL嵌入使得復雜的SQL更容易理解。?
&#8226; 編寫存儲過程比在Java中編寫嵌入的SQL擁有更好的工具--大部分編輯器都提供語法高亮!?
&#8226; 存儲過程可以在任何SQL命令行中測試,這使得調試更加容易。

并不是所有的數據庫都支持存儲過程,但是存在許多很棒的實現,包括免費/開源的和非免費的,所以移植并不是一個問題。Oracle、PostgreSQL和DB2都有類似的存儲過程語言,并且有在線的社區很好地支持。?
存儲過程工具很多,有像TOAD或TORA這樣的編輯器、調試器和IDE,提供了編寫、維護PL/SQL或pl/pgsql的強大的環境。?
存儲過程確實增加了你的代碼的開銷,但是它們和大多數的應用服務器相比,開銷小得多。如果你的代碼復雜到需要使用DBMS,我建議整個采用存儲過程的方式。

總結

以上是生活随笔為你收集整理的callablestatement存储过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。