讓我們從表格API開始。這是通過PL/SQL API調解對錶的訪問的做法。所以,我們爲每個表提供一個包,它應該從數據字典中生成。該軟件包提供了一套針對表格發佈DML的標準程序和用於檢索數據的一些功能。
通過比較,Transactional API表示一個Unit Of Work。它根本不公開有關底層數據庫對象的任何信息。事務性API提供了更好的封裝和更清晰的接口。
對比就是這樣。考慮到這些業務規則用於創建一個新的部門:
- 新的部門必須有一個名稱和位置
- 新的部門必須有一個經理,誰必須是現有員工
- 其他現有員工可能轉移到新的部門
- 新員工可能被分配到新部門
- 新的部門必須指派至少兩名員工(包括管理者)
使用表API中的事務可能是這個樣子:
DECLARE
dno pls_integer;
emp_count pls_integer;
BEGIN
dept_utils.insert_one_rec(:new_name, :new_loc, dno);
emp_utils.update_one_rec(:new_mgr_no ,p_job=>'MGR’ ,p_deptno=>dno);
emp_utils.update_multi_recs(:transfer_emp_array, p_deptno=>dno);
FOR idx IN :new_hires_array.FIRST..:new_hires_array.LAST LOOP
:new_hires_array(idx).deptno := dno;
END LOOP;
emp_utils.insert_multi_recs(:new_hires_array);
emp_count := emp_utils.get_count(p_deptno=>dno);
IF emp_count < 2 THEN
raise_application_error(-20000, ‘Not enough employees’);
END IF;
END;
/
而與事務API就簡單得多:
那麼,爲什麼在檢索數據的區別?由於事務性API方法不鼓勵使用通用的get()
函數,以避免無意識地使用低效的SELECT語句。
例如,如果你只是想爲員工,查詢這個工資和提成......
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
...比執行這更好的...
l_emprec := emp_utils.get_whole_row(p_eno);
。特別是如果員工記錄具有LOB列。
它也比更高效:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
...如果每個這些getter執行一個單獨的SELECT語句。這不是未知數:這是一個糟糕的OO操作,導致可怕的數據庫性能。
表API的支持者認爲,他們的基礎是他們屏蔽了開發人員不需要考慮SQL。貶低他們的人不喜歡錶API 出於同樣的原因。即使是最好的Table API也傾向於鼓勵RBAR處理。如果我們每次編寫自己的SQL都更有可能選擇基於集合的方法。
使用交易型AP並不一定排除使用get_resultset()
函數。在查詢API中仍然有很多價值。但它更可能是由實現聯接的視圖和函數構建的,而不是個別表上的SELECT。順便提一句,我認爲在表API之上構建事務性API並不是一個好主意:我們仍然有孤立的SQL語句,而不是仔細編寫的連接。
舉例來說,下面是一個事務性API的兩種不同實現,用於更新區域中每個員工的薪水(區域是組織的大規模部分;部門分配給區域)。
第一個版本沒有純粹的SQL只是Table API調用,我不認爲這是一個稻草人:它使用了我在Table API包中看到的那種功能(儘管有些使用動態SQL而不是命名爲SET_XXX ()程序)。
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number)
as
emps_rc sys_refcursor;
emp_rec emp%rowtype;
depts_rc sys_refcursor;
dept_rec dept%rowtype;
begin
depts_rc := dept_utils.get_depts_by_region(p_region);
<<depts>>
loop
fetch depts_rc into dept_rec;
exit when depts_rc%notfound;
emps_rc := emp_utils.get_emps_by_dept(dept_rec.deptno);
<<emps>>
loop
fetch emps_rc into emp_rec;
exit when emps_rc%notfound;
emp_rec.sal := emp_rec.sal * p_sal_adjustment;
emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
end loop emps;
end loop depts;
end adjust_sal_by_region;
/
在SQL等效實現:
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number)
as
begin
update emp e
set e.sal = e.sal * p_sal_adjustment
where e.deptno in (select d.deptno
from dept d
where d.region = p_region);
end adjust_sal_by_region;
/
這比嵌套循環光標和以前版本的單行更新更好。這是因爲在SQL中,編寫我們需要按區域選擇Employees的連接是一件很簡單的事情。使用Table API要困難得多,因爲Region不是Employees的關鍵。
爲了公平起見,如果我們有一個表API,它支持動態SQL,事情更好,但仍不理想:
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number)
as
emps_rc sys_refcursor;
emp_rec emp%rowtype;
begin
emps_rc := emp_utils.get_all_emps(
p_where_clause=>'deptno in (select d.deptno
from dept d where d.region = '||p_region||')');
<<emps>>
loop
fetch emps_rc into emp_rec;
exit when emps_rc%notfound;
emp_rec.sal := emp_rec.sal * p_sal_adjustment;
emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
end loop emps;
end adjust_sal_by_region;
/
硬道理
說了這麼多,有些情況下表API可以是有用的,當我們只想以相當標準的方式與單個表交互時。一個明顯的例子可能是產生或使用其他系統的數據饋送,例如ETL。
如果你想調查表API的使用,最好的開始是Steven Feuerstein的Quest CodeGen Utility(以前稱爲QNXO)。這與TAPI生成器一樣好,而且它是免費的。
+1非常好! – 2010-06-22 11:26:17
@APC感謝您的全面而清晰的迴應。我可以請你擴大你最後一段的內容嗎? 「順便說一下,我認爲通過Table API構建事務API並不是一個好主意:我們仍然擁有孤立的SQL語句,而不是仔細編寫的連接。」我已經讀了兩遍,得出了兩個不同的結論(這是我的錯,不是你的),我只是想澄清這一點! – 2010-06-22 12:33:50
+1優秀答案 – 2010-06-22 12:38:20