Låt oss börja med Table API. Detta är praxis att förmedla åtkomst till tabeller genom ett PL/SQL API. Så vi har ett paket per tabell, som bör genereras från dataordboken. Paketet presenterar en standarduppsättning procedurer för att utfärda DML mot tabellen och några funktioner för att hämta data.
Som jämförelse representerar ett Transactional API en Unit Of Work. Det visar inte någon information om de underliggande databasobjekten alls. Transaktionella API:er erbjuder bättre inkapsling och ett renare gränssnitt.
Kontrasten är så här. Tänk på dessa affärsregler för att skapa en ny avdelning:
- Den nya avdelningen måste ha ett namn och en plats
- Den nya avdelningen måste ha en chef som måste vara en befintlig anställd
- Andra befintliga anställda kan flyttas till den nya avdelningen
- Nya medarbetare kan tilldelas den nya avdelningen
- Den nya avdelningen måste ha minst två anställda tilldelade (inklusive chefen)
Med tabell-API:er kan transaktionen se ut ungefär så här:
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;
/
Medan det är mycket enklare med ett Transactional API:
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å varför är skillnaden i att hämta data? Eftersom Transactional API-metoden avskräcker generisk get()
funktioner för att undvika tanklös användning av ineffektiva SELECT-satser.
Till exempel, om du bara vill ha lön och provision för en anställd, fråga detta ...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
... är bättre än att utföra detta ...
l_emprec := emp_utils.get_whole_row(p_eno);
...speciellt om anställningsposten har LOB-kolumner.
Det är också mer effektivt än:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... om var och en av dessa getters kör en separat SELECT-sats. Vilket inte är okänt:det är en dålig OO-praxis som leder till fruktansvärda databasprestanda.
Förespråkarna för tabell-API:er argumenterar för dem utifrån att de skyddar utvecklaren från att behöva tänka på SQL. Människorna som fasar ut dem ogillar tabell-API:er av samma anledning . Även de bästa tabell-API:erna tenderar att uppmuntra RBAR-bearbetning. Om vi skriver vår egen SQL varje gång är det mer sannolikt att vi väljer ett set-baserat tillvägagångssätt.
Att använda Transactional APis utesluter inte nödvändigtvis användningen av get_resultset()
funktioner. Det finns fortfarande mycket värde i ett förfrågnings-API. Men det är mer sannolikt att det bygger på vyer och funktioner som implementerar kopplingar än SELECT på enskilda tabeller.
För övrigt tror jag att det inte är en bra idé att bygga Transaktions-API:er ovanpå Tabell-API:er:vi har fortfarande silade SQL-satser istället för noggrant skrivna kopplingar.
Som en illustration, här är två olika implementeringar av ett transaktions-API för att uppdatera lönen för varje anställd i en region (regionen är en storskalig del av organisationen; avdelningar är tilldelade regioner).
Den första versionen har ingen ren SQL bara Table API-anrop, jag tror inte att det här är en halmgubbe:den använder den typ av funktionalitet som jag har sett i Table API-paket (även om vissa använder dynamisk SQL snarare än 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;
/
Motsvarande 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;
/
Detta är mycket trevligare än de kapslade markörslingorna och enradsuppdateringen av den tidigare versionen. Detta beror på att det i SQL är enkelt att skriva anslutningen som vi behöver för att välja anställda per region. Det är mycket svårare att använda ett tabell-API, eftersom Region inte är en nyckel för anställda.
För att vara rättvis, om vi har ett tabell-API som stöder dynamisk SQL, är saker och ting bättre men fortfarande inte idealiska:
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;
/
sista ordet
Med det sagt finns det scenarier där tabell-API:er kan vara användbara, situationer då vi bara vill interagera med enstaka tabeller på ganska standardmässiga sätt. Ett uppenbart fall kan vara att producera eller konsumera dataflöden från andra system t.ex. ETL.
Om du vill undersöka användningen av tabell-API:er är det bästa stället att börja med Steven Feuersteins Quest CodeGen Utility (tidigare QNXO). Det här är ungefär lika bra som TAPI-generatorer blir, och det är gratis.