Lad os starte med Table API. Dette er praksis med at formidle adgang til tabeller gennem en PL/SQL API. Så vi har en pakke pr. tabel, som skal genereres fra dataordbogen. Pakken præsenterer et standardsæt af procedurer til at udstede DML mod tabellen og nogle funktioner til at hente data.
Til sammenligning repræsenterer en Transactional API en Unit Of Work. Det afslører overhovedet ingen information om de underliggende databaseobjekter. Transaktionelle API'er tilbyder bedre indkapsling og en renere grænseflade.
Kontrasten er sådan her. Overvej disse forretningsregler for at oprette en ny afdeling:
- Den nye afdeling skal have et navn og en placering
- Den nye afdeling skal have en leder, som skal være en eksisterende medarbejder
- Andre eksisterende medarbejdere kan blive overført til den nye afdeling
- Nye medarbejdere kan blive tilknyttet den nye afdeling
- Den nye afdeling skal have tildelt mindst to medarbejdere (inklusive lederen)
Ved at bruge tabel-API'er kan transaktionen se nogenlunde sådan ud:
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;
/
Mens det med en Transactional API er meget enklere:
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;
/
Så hvorfor er forskellen i at hente data? Fordi Transactional API-tilgangen fraråder generisk get()
funktioner for at undgå tankeløs brug af ineffektive SELECT-sætninger.
For eksempel, hvis du bare vil have lønnen og provisionen til en medarbejder, skal du spørge dette ...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
... er bedre end at udføre dette ...
l_emprec := emp_utils.get_whole_row(p_eno);
...især hvis medarbejderposten har LOB-kolonner.
Det er også mere effektivt end:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... hvis hver af disse gettere udfører en separat SELECT-sætning. Hvilket ikke er ukendt:det er en dårlig OO-praksis, der fører til forfærdelig databaseydeevne.
Tilhængerne af Table API'er argumenterer for dem ud fra, at de beskytter udvikleren mod at skulle tænke på SQL. De mennesker, der forælder dem, kan ikke lide tabel-API'er af samme grund . Selv de bedste tabel-API'er har en tendens til at fremme RBAR-behandling. Hvis vi skriver vores egen SQL hver gang, er det mere sandsynligt, at vi vælger en sæt-baseret tilgang.
Brug af Transactional AP'er udelukker ikke nødvendigvis brugen af get_resultset()
funktioner. Der er stadig meget værdi i en forespørgende API. Men det er mere sandsynligt, at det er bygget ud af visninger og funktioner, der implementerer joins end SELECT'er på individuelle tabeller.
I øvrigt synes jeg, at det ikke er en god idé at bygge Transaktions-API'er oven på Table API'er:vi har stadig siled SQL-sætninger i stedet for omhyggeligt skrevne joins.
Som en illustration er her to forskellige implementeringer af en transaktions-API til at opdatere lønnen for hver medarbejder i en region (region er en storstilet del af organisationen; afdelinger er tildelt regioner).
Den første version har ingen ren SQL, kun Table API-kald, jeg tror ikke, dette er en stråmand:den bruger den slags funktionalitet, jeg har set i Table API-pakker (selvom nogle bruger dynamisk SQL frem for navngivne SET_XXX()-procedurer) .
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;
/
Den tilsvarende implementering i 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;
/
Dette er meget pænere end de indlejrede markørløkker og opdatering af en enkelt række i den tidligere version. Dette skyldes, at det i SQL er let at skrive den join, vi skal bruge for at vælge medarbejdere efter region. Det er meget sværere at bruge en Table API, fordi Region ikke er en nøgle for medarbejdere.
For at være retfærdig, hvis vi har en Table API, der understøtter dynamisk SQL, er tingene bedre, men stadig ikke ideelle:
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;
/
sidste ord
Når alt det er sagt, er der scenarier, hvor tabel-API'er kan være nyttige, situationer, hvor vi kun ønsker at interagere med enkelte tabeller på nogenlunde standardmåder. Et oplagt tilfælde kan være at producere eller forbruge datafeeds fra andre systemer, f.eks. ETL.
Hvis du vil undersøge brugen af Table API'er, er det bedste sted at starte Steven Feuersteins Quest CodeGen Utility (tidligere QNXO). Dette er omtrent lige så godt, som TAPI-generatorer bliver, og det er gratis.