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);
}
}
}