java調用存儲過程游標
import oracle.jdbc.*;
...
CallableStatement cstmt;
ResultSet cursor;
// Use a PL/sql block to open the cursor
//Additional Oracle Type Extensions
cstmt = conn.prepareCall
("begin open ? for select ename from emp; end;";
cstmt.registerOutParameter(1, OracleTypes.CURSOR);
cstmt.execute();
cursor = ((OracleCallableStatement)cstmt).getCursor(1);
// Use the cursor like a normal ResultSet
while (cursor.next ())
{System.out.println (cursor.getString(1));}
2. java調用存儲過程返回遊標的結果集無法關閉游標的問題
在過程里判斷很簡單呀
CREATE OR REPLACE PROCEDURE TESTPROC(CUR OUT SYS_REFCURSOR)
AS
default_c SYS_REFCURSOR;
BEGIN
OPEN CUR FOR SELECT * FROM AA;
if CUR%rowcount = 0 then
CUR:=default_c;
end if;
END;
定義一個默認空游標,沒數據就返回它唄
3. Java資料庫程序中的存儲過程設計
本文闡述了怎麼使用DBMS存儲過程 闡述了使用存儲過程的基本的和高級特性 比如返回ResultSet 本文假設你對DBMS和JDBC已經非常熟悉 也假設你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言) 但是 並不要求你有任何存儲過程的編程經歷
存儲過程是指保存在數據豎拿庫並在資料庫端執行的程序 你可以使用特殊的語法在Java類中調用存儲過程 在調用時 存儲過程的名稱及指定的參數通過JDBC連接發送給DBMS 執行存儲過程並通過連接(如果有)返回結果
使用存儲過程擁有和使用基於EJB或CORBA這樣的應用伺服器一樣的好處 區別是存儲過程可以從很多流行的DBMS中免費使用 而應用伺服器大都非常昂貴 這並不只是許可證費用的問題 使用應用伺服器所需要花費的管理 編寫代碼的費用 以及客戶程序所增加的復雜性 都可以通過DBMS中的存儲過程所整個地替代
你可以使用Java Python Perl或C編寫存儲過程 但是通常使用你的DBMS所指定的特定語言 Oracle使用PL/SQL PostgreSQL使用pl/pgsql DB 使用Proceral SQL 這些語言都非常相似 在它們之間移植存儲過程並不比在Sun的EJB規范不同實現版本之間移植Session Bean困難 並且 存儲過程是為嵌入SQL所設計 這使得它們比Java或C等語言更加友好地方式表達資料庫的機制
因為存儲過程運行在DBMS自身 這可以幫助減少應用程序中的等待時間 不是在Java代碼中執行 個或 個SQL語句 而只需要在伺服器端執行 個存儲過程 網路上的數據往返次數的減少可以戲劇性地優化性能
使用存儲過程
簡單的老的JDBC通過CallableStatement類支持存儲過程的調用 該類實際上是PreparedStatement的一個子類 假設我們有一個poets資料庫 資料庫中有一個設置詩人逝世年齡的存儲過程 下面是對老酒鬼Dylan Thomas(old soak Dylan Thomas 不指定是否有關典故跡灶 文化 請批評指正 譯注)進行調用的詳細代碼
try{int age = ;String poetName = dylan thomas ;CallableStatement proc = connection prepareCall( { call set_death_age(? ?) } );proc setString( poetName);proc setInt( 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 procere set_death_age(poet VARCHAR poet_age NUMBER)poet_id NUMBER;beginSELECT 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( dyingBard getName());proc setInt( 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 declarepoet_id NUMBER;poet_age NUMBER;begin first get the id associated with the poet SELECT id INTO poet_id FROM poets WHERE name = $ ; 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( Types INTEGER);proc setString( poetName);cs execute();int age = proc getInt( );
如果指定了錯誤的返回值類型會怎樣?那麼 當調用存儲過程時將拋出一個RuntimeException 正如你在ResultSet操作中使用了一個錯誤的類型所碰到的一樣
復雜的返回值
關於存儲過程的知識 很多人好像就熟悉我們所討論的這些 如果這是存儲過程的全部功能 那麼存儲過程就不是其它遠程執行機制的替換方案了 存儲過程的功能比這強大得多
當你執行一個SQL查詢時 DBMS創建一個叫做cursor(游標)的資料庫對象 用於在返回結果中迭代每一行 ResultSet是當前時間點的游標的一個表示 這就是為什麼沒有緩存或者特定資料庫的支持 你只能在ResultSet中向前移動
某些DBMS允許從存儲過程中返回遊標的一個引用 JDBC並不支持這個功能 但是Oracle PostgreSQL和DB 的JDBC驅動器都支持在ResultSet上打開到游標的指針(pointer)
設想列出所有沒有活到退休年齡的詩人 下面是完成這個功能的存儲過程 返回一個打開的游標 同樣也使用PostgreSQL的pl/pgsql語言
create procere list_early_deaths () return refcursor as declaretoesup refcursor;beginopen toesup forSELECT poets name deaths ageFROM poets deaths all entries in deaths are for poets but the table might bee generic WHERE poets id = deaths mort_idAND deaths age < ;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( Types OTHER);getResults execute();
ResultSet rs = (ResultSet) getResults getObject( );while (rs next()){String name = rs getString( );int age = rs getInt( );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( Types OTHER);getResults execute();
ResultSet rs = (ResultSet) getResults getObject( );while (rs next()){String name = rs getString( );int age = rs getInt( );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結合使用 是一個非常強大的工具
結論
存儲過程可以幫助你在代碼中分離邏輯 這基本上總是有益的 這個分離的好處有
快速創建應用 使用和應用一起改變和改善的資料庫模式
資料庫模式可以在以後改變而不影響Java對象 當我們完成應用後 可以重新設計更好的模式
存儲過程通過更好的SQL嵌入使得復雜的SQL更容易理解
編寫存儲過程比在Java中編寫嵌入的SQL擁有更好的工具——大部分編輯器都提供語法高亮!
存儲過程可以在任何SQL命令行中測試 這使得調試更加容易
並不是所有的資料庫都支持存儲過程 但是存在許多很棒的實現 包括免費/開源的和非免費的 所以移植並不是一個問題 Oracle PostgreSQL和DB 都有類似的存儲過程語言 並且有在線的社區很好地支持
存儲過程工具很多 有像TOAD或TORA這樣的編輯器 調試器和IDE 提供了編寫 維護PL/SQL或pl/pgsql的強大的環境
lishixin/Article/program/Java/hx/201311/25906
4. java調用的存儲過程,能否傳入游標參數
java調用的存儲過程,不能傳入游標參數,但是存儲過程調用的內部存儲過程,可以存入游標參數。未完待續
5. java調用存儲過程,打開結果集報遞歸SQL級別1出現錯誤且超出打開游標的最大數 這是怎麼回事
在命令窗口執行show parameter open_cursors;
看看你給資料庫配置的游標數是多大,估計是150
需要調整的話,網路搜一下游標設置。