1. 程式人生 > >Oracle儲存過程和自定義函式

Oracle儲存過程和自定義函式

概述

PL/SQL中的過程和函式(通常稱為子程式)是PL/SQL塊的一種特殊的型別,這種型別的子程式可以以編譯的形式存放在資料庫中,併為後續的程式塊呼叫。

相同點: 完成特定功能的程式
不同點:是否用return語句返回值。

舉個例子:

create or replace procedure PrintStudents(p_staffName in xgj_test.username%type) as

  cursor c_testData is
    select t.sal, t.comm from xgj_test t where t.username = p_staffName
;
begin for v_info in c_testData loop DBMS_OUTPUT.PUT_LINE(v_info.sal || ' ' || v_info.comm); end loop; end PrintStudents;

一旦建立了改程式並將其儲存在資料庫中,就可以使用如下的方式呼叫該過程

begin
    PrintStudents('Computer Science');
    PrintStudents('Match');
end;
/

或者

exec PrintStudents('Computer Science');
exec
PrintStudents('Match');

在命令視窗中:
這裡寫圖片描述

在pl/sql工具的sql視窗中:
這裡寫圖片描述

儲存過程的建立和呼叫

基本語法

create [ or replace] procedure procedure_name
[( argument [ {IN | OUT | IN OUT }] type,
......
argument [ {IN | OUT | IN OUT }] type ) ] { IS | AS}
procedure_body

無參的儲存過程

/**
 無引數的存過
 列印hello world

 呼叫儲存過程:
 1. exec sayhelloworld();
 2 begin 
   sayhelloworld();
   end;
   /

*/
create or replace procedure sayhelloworld as --說明部分 begin dbms_output.put_line('hello world'); end sayhelloworld;

呼叫過程:

SQL> set serveroutput on ;
SQL> exec sayhelloworld();

hello world

PL/SQL procedure successfully completed

SQL> begin
  2  sayhelloworld();
  3  sayhelloworld();
  4  end;
  5  /

hello world
hello world

PL/SQL procedure successfully completed

帶引數的儲存過程

/**
建立一個帶引數的儲存過程

給指定的員工增加工資,並列印增長前後的工資

*/
create or replace procedure addSalary(staffName  in  xgj_test.username%type )
as
--定義一個變數儲存調整之前的薪水
oldSalary  xgj_test.sal%type;

begin
  --查詢員工漲之前的薪水
  select t.sal into oldSalary  from xgj_test t where t.username=staffName; 

  --調整薪水
  update  xgj_test  t set t.sal = sal+1000 where t.username=staffName ;

  --輸出
  dbms_output.put_line('調整之前的薪水:'|| oldSalary || ' ,調整之後的薪水:' || (oldSalary + 1000));

end addSalary;

可以看到,update語句之後並沒有commit的操作。
一般來講為了保證事務的一致性,由呼叫者來提交比較合適,當然了是需要區分具體的業務需求的~

begin 
addSalary('xiao');
addSalary('gong');
commit ;
end ;
/

儲存函式

基本語法

create [ or replace] function function_name
[( argument [ {IN | OUT | IN OUT }] type,
......
argument [ {IN | OUT | IN OUT }] type ) ] 
RETURN { IS | AS}
function_body

其中 return子句是必須存在的,一個函式如果沒有執行return就結束將發生錯誤,這一點和存過有說不同。

儲存函式

準備的資料如下:
這裡寫圖片描述

/**
查詢員工的年薪 (月工資*12 + 獎金)
*/

create or replace function querySalaryInCome(staffName in varchar2)

 return number as
  --定義變數儲存員工的工資和獎金
  pSalary xgj_test.sal%type;
  pComm   xgj_test.comm%type;

begin
  --查詢員工的工資和獎金
  select t.sal, t.comm
    into pSalary, pComm
    from xgj_test t
   where t.username = staffName;
  --直接返回年薪
  return pSalary * 12 + pComm;
end querySalaryInCome;

這裡寫圖片描述

存在一個問題,當獎金為空的時候,算出來的年收入竟然是空的。
因為 如果一個表示式中有空值,那麼這個表示式的結果即為空值。

所以我們需要對空值進行處理, 使用nvl函式即可。

最後修改後的function為

create or replace function querySalaryInCome(staffName in varchar2)

 return number as
  --定義變數儲存員工的工資和獎金
  pSalary xgj_test.sal%type;
  pComm   xgj_test.comm%type;

begin
  --查詢員工的工資和獎金
  select t.sal, t.comm
    into pSalary, pComm
    from xgj_test t
   where t.username = staffName;
  --直接返回年薪
  return pSalary * 12 + nvl(pComm,0);
end querySalaryInCome;

out引數

out引數

一般來講,儲存過程和儲存函式的區別在於儲存函式可以有一個返回值,而儲存過程沒有返回值。

  • 儲存過程和儲存函式都可以有out引數
  • 儲存過程和儲存函式都可以有多個out引數
  • 儲存過程可以通過out引數實現返回值

那我們如何選擇儲存過程和儲存函式呢?

原則:

如果只有一個返回值,用儲存函式,否則(即沒有返回值或者有多個返回值)使用儲存過程。

/**
根據員工姓名,查詢員工的全部資訊
*/
create or replace procedure QueryStaffInfo(staffName in xgj_test.username%type,
                                           pSal out number,
                                           pComm out xgj_test.comm%type,
                                           pJob out xgj_test.job%type) 

is

begin
  --查詢該員工的薪資,獎金和職位
  select t.sal,t.comm,t.job into pSal,pComm,pJob from xgj_test t where t.username=staffName;
end QueryStaffInfo;

這裡寫圖片描述

先丟擲兩個思考問題:

  • 查詢員工的所有資訊–> out引數太多怎麼辦?
  • 查詢某個部門中所有員工的資訊–> out中返回集合?

後面會講到如何解決? 總不能一個個的寫out吧~

在應用中訪問儲存過程和儲存函式

概述

我們使用JAVA程式連線ORACLE資料庫。

使用jar: ojdbc14.jar

關於oracle官方提供的幾個jar的區別

classes12.jar (1,600,090 bytes) - for use with JDK 1.2 and JDK 1.3

classes12_g.jar (2,044,594 bytes) - same as classes12.jar, except that classes were compiled with “javac -g” and contain some tracing information.

classes12dms.jar (1,607,745 bytes) - same as classes12.jar, except that it contains additional code`to support Oracle Dynamic Monitoring Service.

classes12dms_g.jar (2,052,968 bytes) - same as classes12dms.jar except that classes were compiled with “javac -g” and contain some tracing information.

ojdbc14.jar (1,545,954 bytes) - classes for use with JDK 1.4 and 1.5

ojdbc14_g.jar (1,938,906 bytes) - same as ojdbc14.jar, except that classes were compiled with “javac -g” and contain some tracing information.

ojdbc14dms.jar (1,553,561 bytes) - same as ojdbc14.jar, except that it contains additional code`to support Oracle Dynamic Monitoring Service.

ojdbc14dms_g.jar (1,947,136 bytes) - same as ojdbc14dms.jar, except that classes were compiled with “javac -g” and contain some tracing information.

工程目錄如下:
這裡寫圖片描述

簡單的寫下獲取資料庫連線的工具類


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBUtils {

    // 設定資料庫驅動,資料庫連線地址埠名稱,使用者名稱,密碼
    private static final String driver = "oracle.jdbc.driver.OracleDriver";
    private static final String url = "jdbc:oracle:thin:@ip:xxxx";
    private static final String username = "xxxx";
    private static final String password = "xxxx";

    /**
     * 註冊資料庫驅動
     */
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e.getMessage());
        }
    }

    /**
     * 獲取資料庫連線
     */
    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            // 成功,返回connection
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 獲取失敗,返回null
        return null;
    }

    /**
     * 釋放連線
     */
    public static void cleanup(Connection conn, Statement st, ResultSet rs) {

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                rs = null;
            }
        }

        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                st = null;
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                conn = null;
            }
        }

    }
}

在應用程式中訪問儲存過程

根據官方提供的API,我們可以看到:
這裡寫圖片描述


import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;

import org.junit.Test;

import com.turing.oracle.dbutil.DBUtils;

import oracle.jdbc.OracleTypes;


public class TestProcedure {

    @Test
    public void callProcedure(){
        //  {call <procedure-name>[(<arg1>,<arg2>, ...)]}

        Connection conn = null ;
        CallableStatement callableStatement = null ;

        /**
         *
            根據員工姓名,查詢員工的全部資訊
            create or replace procedure QueryStaffInfo(staffName in xgj_test.username%type,
                                                       pSal out number,
                                                       pComm out xgj_test.comm%type,
                                                       pJob out xgj_test.job%type) 
            is
            begin
              --查詢該員工的薪資,獎金和職位
              select t.sal,t.comm,t.job into pSal,pComm,pJob from xgj_test t where t.username=staffName;
            end QueryStaffInfo;
         */
        // 我們可以看到該存過 4個引數 1個入參 3個出參
        String sql = "{call QueryStaffInfo(?,?,?,?)}";

        try {
            // 獲取連線
            conn = DBUtils.getConnection();
            // 通過連接獲取到CallableStatement
            callableStatement = conn.prepareCall(sql);

            // 對於in 引數,需要賦值
            callableStatement.setString(1, "xiao");
            // 對於out 引數,需要宣告
            callableStatement.registerOutParameter(2, OracleTypes.NUMBER); // 第二個 ?
            callableStatement.registerOutParameter(3, OracleTypes.NUMBER);// 第三個 ?
            callableStatement.registerOutParameter(4, OracleTypes.VARCHAR);// 第四個 ?

            // 執行呼叫
            callableStatement.execute();

            // 取出結果
            int salary = callableStatement.getInt(2);
            int comm = callableStatement.getInt(3);
            String job = callableStatement.getString(3);

            System.out.println(salary + "\t" + comm + "\t" + job);

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.cleanup(conn, callableStatement, null);
        }


    }
}

在應用程式中訪問儲存函式


import java.sql.CallableStatement;
import java.sql.Connection;

import org.junit.Test;

import com.turing.oracle.dbutil.DBUtils;

import oracle.jdbc.OracleTypes;

public class TestFuction {

    @Test
    public void  callFuction(){
        //{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
        Connection conn = null;
        CallableStatement call = null;
        /**
         * create or replace function querySalaryInCome(staffName in varchar2)
                 return number as
                  --定義變數儲存員工的工資和獎金
                  pSalary xgj_test.sal%type;
                  pComm   xgj_test.comm%type;

                begin
                  --查詢員工的工資和獎金
                  select t.sal, t.comm
                    into pSalary, pComm
                    from xgj_test t
                   where t.username = staffName;
                  --直接返回年薪
                  return pSalary * 12 + nvl(pComm,0);
                end querySalaryInCome;
         */

        String sql = "{?=call querySalaryInCome(?)}";

        try {
            // 獲取連線
            conn = DBUtils.getConnection();
            // 通過conn獲取CallableStatement
            call = conn.prepareCall(sql);

            // out 引數,需要宣告
            call.registerOutParameter(1, OracleTypes.NUMBER);
            // in 引數,需要賦值
            call.setString(2, "gong");

            // 執行
            call.execute();
            // 取出返回值  第一個?的值
            double income = call.getDouble(1);
            System.out.println("該員工的年收入:" + income);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils.cleanup(conn, call, null);
        }
    }

}

在out引數中訪問游標

在out引數中使用游標

我們之前丟擲的兩個思考問題:

  • 查詢員工的所有資訊–> out引數太多怎麼辦?
  • 查詢某個部門中所有員工的資訊–> out中返回集合?

我們可以通過返回Cursor的方式來實現。

在out引數中使用游標 的步驟:

  • 申明包結構
  • 包頭
  • 包體

包頭:

create or replace package MyPackage is

  -- Author  : ADMINISTRATOR
  -- Created : 2016-6-4 18:10:42
  -- Purpose : 

  -- 使用type關鍵字  is ref cursor說明是cursor型別
  type staffCursor is ref cursor;

  procedure queryStaffJob(pJob         in xgj_test.job%type,
                          jobStaffList out staffCursor);

end MyPackage;

建立完包頭之後,建立包體,包體需要實現包頭中宣告的所有方法。

包體

create or replace package body MyPackage is

  procedure queryStaffJob(pJob         in xgj_test.job%type,
                          jobStaffList out staffCursor)

   as
  begin
       open jobStaffList for select * from xgj_test t where t.job=pJob;
  end queryStaffJob;

end MyPackage;

事實上,通過plsql工具建立包頭,編譯後,包體的框架就會自動的生成了。

這裡寫圖片描述

在應用程式中訪問包下的儲存過程

在應用程式中訪問包下的儲存過程

在應用程式中訪問包下的儲存過程 ,需要帶包名


import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;

import org.junit.Test;

import com.turing.oracle.dbutil.DBUtils;

import oracle.jdbc.OracleTypes;
import oracle.jdbc.driver.OracleCallableStatement;

public class TestCursor {

    @Test
    public void testCursor(){
        /**
         * 
         * create or replace package MyPackage is
                  type staffCursor is ref cursor;

                  procedure queryStaffJob(pJob         in xgj_test.job%type,
                                          jobStaffList out staffCursor);

                end MyPackage;
         */
        String sql = "{call MyPackage.queryStaffJob(?,?)}" ;

        Connection conn = null;
        CallableStatement call = null ;
        ResultSet rs = null;

        try {
            // 獲取資料庫連線
            conn = DBUtils.getConnection();
            // 通過conn建立CallableStatemet
            call = conn.prepareCall(sql);

            // in 引數 需要賦值
            call.setString(1, "Staff");
            // out 引數需要宣告
            call.registerOutParameter(2, OracleTypes.CURSOR);

            // 執行呼叫
            call.execute();

            // 獲取返回值
            rs = ((OracleCallableStatement)call).getCursor(2);
            while(rs.next()){
                // 取出值
                String username = rs.getString("username");
                double sal = rs.getDouble("sal");
                double comm = rs.getDouble("comm");

                System.out.println("username:" + username + "\t sal:" + sal + "\t comm:" + comm);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils.cleanup(conn, call, rs);
        }
    }

}