1. 程式人生 > >使用java呼叫儲存過程的通用封裝

使用java呼叫儲存過程的通用封裝

對於在java中呼叫儲存過程,我一直因為是一件比較頭疼的事情,因為各個資料庫的實現往往不相同,這樣就很難保證同一套程式在sqlserver和oracle上都能執行.儘管兩個資料庫中的儲存過程名字和引數已經基本相同,以前用spring封裝過一個,用了幾個抽象類做了簡單的設計,但是總感覺不是很好,而且最新的spring貌似已經廢棄那幾個類了,看來是有問題,週末花了些時間重新寫了一個比較簡單赤裸的封裝.程式碼雖然不多但是就不一一講解了.這裡先把儲存過程發出來.

sqlsever版

/*返回2個結果集,外加輸出out引數*/createprocedure p1
@outputParamint output,
@idintasselect@outputParam=@idselect@outputParam+@outputParamselect@outputParam/*直接返回值*/createprocedure p2
@outputParamint output,
@idintasselect@outputParam=@idreturn@outputParam/*返回一個結果集*/createprocedure p3
@outputParamint output,
@idintasselect@outputParam=@idselect@outputParam/*最簡單的形式*/createprocedure p4
as
print'helloword'

對應的oracle版,oracle中儲存過程好像不能直接返回值.至少我還不知道怎麼返回.目前依靠out引數返回遊標作為結果集。

CREATEORREPLACEPROCEDURE p1(
       outputParam OUT 
int,
       id 
INint,
       CURSOR1 out sys_refcursor,
       CURSOR2 out sys_refcursor
ASBEGINselect id into p1.outputParam from dual;
    
OPEN CURSOR1 FORselect'syj1
'as id from dual unionallselect'syj2'as id from dual;
    
OPEN CURSOR2 FORselect'abc1'as id from dual unionallselect'abc2'as id from dual;
END p1;

CREATEORREPLACEPROCEDURE p3(
       outputParam 
IN OUT int,
       id 
INint,
       CURSOR1 out sys_refcursor
ASBEGINselect id into P3.outputParam from dual;
   
OPEN CURSOR1 FORselect'syj1'as id from dual unionallselect'syj2'as id from dual;
END P3;

CREATEORREPLACEPROCEDURE p4
 
ASBEGIN
  dbms_output.put_line(
'helloword');
END P4;

 因為mssql和oracle對結果集的返回形式不相同所以就很難在java程式碼中保證一致的呼叫方式。解決這個方法的唯一途徑就是分析他們的相同和不同之處,想辦法在設計上解決。經典的jdbc呼叫,在這裡就不說了,百度一下有很多資料,下面說下經過我這次封裝後的呼叫方式,無論是oracle還是mssql都可以使用下面程式碼執行。只要把ORACLE過程中的遊標引數放置到最後宣告就可以了。

publicvoid test1() {
  ProcedureResult result 
= getSimpleJdbcTemplate().execProcedure("p1",
    
new ProcedureCallBack() {//執行返回結果集的過程public Object mapRow(ResultSet rs, int rowNum)
       
throws SQLException {
      
return rs.getObject(1);
     }

     
publicvoid registerParameter() throws SQLException {
      registerOutParameter(
1, Types.INTEGER);
      setInt(
29);
      addOracleCursor(
3);
      addOracleCursor(
4);
     }
    });
  TestUtil.println(result);
 }
   
 
publicvoid test2() {//執行直接返回值的過程   ProcedureResult result = getSimpleJdbcTemplate().execProcedure("p2",
    Types.INTEGER, 
new ProcedureCallBack() {
publicvoid registerParameter() throws SQLException {
      registerOutParameter(
1, Types.INTEGER);
      setInt(
29);
      addOracleCursor(
3);
     }

    });
  TestUtil.println(result);
 }
  
 
publicvoid test4() {//執行普通的簡單過程  ProcedureResult result = getSimpleJdbcTemplate().execProcedure("p4",
    
new ProcedureCallBack() {
    });
  TestUtil.println(result);
 }

 

更多使用方法可以參考junit測試程式碼包

核心程式碼部分摘要如下關鍵地方我都加了註釋

/**
     * 執行儲存過程
     * 
     * 
@param procedure過程名
     * 
@param sqlType
     *            返回值型別,無返回值傳null
     * 
@param procedureCallBack
     *            回撥物件
     * 
@return*/private ProcedureResult execProc(String procedure, Integer sqlType,
            ProcedureCallBack procedureCallBack) {
        Connection conn 
=null;
        Statement stmt 
=null;
        ResultSet rs 
=null;
        ProcedureResult procResult 
=new ProcedureResult();
        
try {
            procedureCallBack.jt 
=this;
            conn 
= getConnection();// 取連線            procedureCallBack.registerParameter();// 計算引數的個數int parameterSize = procedureCallBack.parametersCount;// 取引數的個數            procedureCallBack.parametersCount =0;
            
if (parameterSize >0)
                procedure 
= procedure + getProcParameters(parameterSize, "?");// 根據引數的個數拼裝sql            String sql ="{"+ (sqlType !=null?"?=" : ""+"call "+ procedure +"}";
            logger.debug(
"exec "+ sql);
            CallableStatement cstm 
= conn.prepareCall(sql);// 預編譯過程            procedureCallBack.cstm = cstm;
            procedureCallBack.startParametersIdx 
= sqlType !=null?1 : 0;// 對外界忽略帶返回值過程的引數下標if (sqlType !=null)
                procedureCallBack.registerOutParameter(
0, sqlType.intValue());// 註冊帶返回值過程的返回型別oracle不支援mssql支援            procedureCallBack.registerParameter();// 註冊引數boolean results = cstm.execute();// 執行過程int rsIndex =1;// 結果集個數計數器for (; results; rsIndex++) {
                rs 
= cstm.getResultSet();// 取當前結果集                List list =new ArrayList();// 為當前結果集建立一個容器while (rs.next())
                    list.add(procedureCallBack.mapRow(rs, rsIndex));
                procResult.addRs(list);
                results 
= cstm.getMoreResults();// 取下一個結果集            }
            Map map 
= procedureCallBack.getOutParameters();// 取宣告的out引數,            Iterator iterator = map.keySet().iterator();// 遍歷宣告的out引數while (iterator.hasNext()) {
                String key 
= iterator.next().toString();// out引數型別int idx = Integer.valueOf(key).intValue();
                
if (new Integer(-10).equals(map.get(key))) {// oracle結果集列表是通過遊標的形式利用out引數輸出的oracle.jdbc.OracleTypes.CURSOR的值是-10                    closeResultSet(rs);// 關閉上次使用的結果集                    rs = (ResultSet) cstm.getObject(Integer.valueOf(key)
                            .intValue());
// 從out引數中取結果集                    List list =new ArrayList();// 為當前結果集建立一個容器while (rs.next())
                        list.add(procedureCallBack.mapRow(rs, rsIndex));
                    rsIndex
++;// 結果集計數器累加                    procResult.addRs(list);
                } 
elseif (idx ==1&& sqlType !=null)// 如果過程有返回值,從第一個out引數中取得這個返回值                    procResult.setValue(JdbcUtils.getCallableStatementValue(
                            cstm, idx));
                
else
                    procResult.getOutput().add(
                            JdbcUtils.getCallableStatementValue(cstm, idx));
// 取的普通的out引數輸出到out輸出容器            }
            
if (sqlType ==null&& procResult.getOutput().size() >0)
                
// 如果過程沒用返回值,例如oracle過程根本不支援返回值,將第一個非遊標的out引數返回值當作過程返回值                procResult.setValue(procResult.getOutput().get(0));
        } 
catch (SQLException e) {
            
thrownew DataAccessException(e);
        } 
finally {
            closeResultSet(rs);
            closeStatement(stmt);
            closeConnection(conn);
        }
        
return procResult;
    }
    
publicclass ProcedureResult {
    
private List rs =new ArrayList();//過程返回的n個結果集列表private List output =new ArrayList();//過程的out引數輸出private Object value;//過程本身的返回值    略
    }

上面程式碼基本上保證了在oracle和sqlserver兩個資料庫中儲存過程名稱相同引數順序相同(oracle中的遊標置於最後)的的情況下,使用同樣的java程式碼進行呼叫。不知道是否有遺漏之處,如果有那個地方還沒有覆蓋到或者有bug請來訪朋友指正,共同學習,分析快樂。