Začněme s Table API. Toto je praxe zprostředkování přístupu k tabulkám prostřednictvím PL/SQL API. Máme tedy balíček na tabulku, který by měl být vygenerován z datového slovníku. Balíček představuje standardní sadu procedur pro vydávání DML proti tabulce a některé funkce pro získávání dat.
Pro srovnání transakční API představuje jednotku práce. Nevystavuje vůbec žádné informace o základních databázových objektech. Transakční API nabízejí lepší zapouzdření a čistší rozhraní.
Kontrast je takový. Zvažte tato obchodní pravidla pro vytvoření nového oddělení:
- Nové oddělení musí mít název a umístění
- Nové oddělení musí mít manažera, kterým musí být stávající zaměstnanec
- Ostatní stávající zaměstnanci mohou být převedeni do nového oddělení
- Do nového oddělení mohou být přiřazeni noví zaměstnanci
- Nové oddělení musí mít přidělené alespoň dva zaměstnance (včetně manažera)
Při použití rozhraní API tabulky může transakce vypadat nějak takto:
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;
/
Zatímco s transakčním API je to mnohem jednodušší:
DECLARE
dno subtype_pkg.deptno;
BEGIN
dept_txns.create_new_dept(:new_name
, :new_loc
, :new_mgr_no
, :transfer_emps_array
, :new_hires_array
, dno);
END;
/
Proč je tedy rozdíl v získávání dat? Protože přístup Transakční API odrazuje od obecného get()
funkcí, abyste se vyhnuli bezduchému používání neefektivních příkazů SELECT.
Pokud například chcete pouze plat a provizi pro zaměstnance, dotazujte se na toto ...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
... je lepší než provést toto ...
l_emprec := emp_utils.get_whole_row(p_eno);
...zejména pokud má záznam Zaměstnanec sloupce LOB.
Je také efektivnější než:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... pokud každý z těchto getterů provede samostatný příkaz SELECT. Což není neznámé:je to špatná OO praxe, která vede k hroznému výkonu databáze.
Zastánci Table API pro ně argumentují tím, že chrání vývojáře před nutností přemýšlet o SQL. Lidé, kteří je zavrhují, nemají rádi Table API ze stejného důvodu . Dokonce i nejlepší rozhraní API pro tabulky mají tendenci podporovat zpracování RBAR. Pokud pokaždé píšeme vlastní SQL, je pravděpodobnější, že zvolíme přístup založený na množinách.
Použití transakčního APis nutně nevylučuje použití get_resultset()
funkcí. Dotazovací API má stále velkou hodnotu. Ale je pravděpodobnější, že bude vytvořen z pohledů a funkcí implementujících spojení než SELECT na jednotlivých tabulkách.
Mimochodem, myslím si, že stavět transakční rozhraní API nad tabulková rozhraní API není dobrý nápad:stále máme umlčené SQL příkazy namísto pečlivě napsaných spojení.
Pro ilustraci uvádíme dvě různé implementace transakčního rozhraní API pro aktualizaci platu každého zaměstnance v regionu (region je rozsáhlá část organizace; oddělení jsou přiřazena regionům).
První verze nemá žádné čisté SQL, pouze volání Table API, nemyslím si, že je to slaměný muž:používá funkcionalitu, kterou jsem viděl v balíčcích Table API (ačkoli některé používají dynamické SQL spíše než pojmenované procedury 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;
/
Ekvivalentní implementace v 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;
/
To je mnohem hezčí než vnořené smyčky kurzoru a aktualizace jednoho řádku předchozí verze. Je to proto, že v SQL je snadné napsat spojení, které potřebujeme k výběru Zaměstnanci podle regionu. Je to mnohem těžší používat Table API, protože Region není klíčem pro zaměstnance.
Abychom byli spravedliví, pokud máme Table API, které podporuje dynamické SQL, věci jsou lepší, ale stále ne ideální:
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;
/
poslední slovo
Po tom všem, existují scénáře, kde mohou být tabulková API užitečná, tedy situace, kdy chceme komunikovat pouze s jednotlivými tabulkami poměrně standardními způsoby. Zřejmým případem může být produkce nebo spotřeba datových zdrojů z jiných systémů, např. ETL.
Pokud chcete prozkoumat použití Table API, nejlepším místem, kde začít, je Quest CodeGen Utility Stevena Feuersteina (dříve QNXO). To je asi tak dobré, jak dostávají generátory TAPI, a je to zdarma.