kako sprijeČiti zaČarani krug (rješavanje određenog tipa ... fileposlovne logike). danas postoje...

22
KAKO SPRIJEČITI "ZAČARANI KRUG" (rješavanje određenog tipa poslovnih pravila u Oracle bazi podataka) SAŽETAK U radu je prikazano kako se u Oracle bazi podataka može spriječiti pojava "začaranog kruga" (zatvorene petlje) u podacima koji imaju stablastu strukturu, i to bez pomoći klijentskog programa ili programa na aplikacijskom serveru (dakle, rješenje je u cijelosti na strani baze). Prikazano je rješenje za jednokorisnički rad i za višekorisnički rad. Za rješenje u višekorisničkom radu koristi se (naša) simulacija "ROLLBACK TO SAVEPOINT ponašanja" u okidaču baze, koja (simulacija) koristi kvazi-udaljene procedure (lokalne procedure koje se pozivaju kao da se nalaze na drugoj bazi). ABSTRACT In this article, it is shown how to avoid the problem of a so-called "vicious cycle" (closed loop) in data with tree structure by doing that completely on the data base side, without writing any client software or application server software. Single user and multi user solution is given. In multi user solution, our simulation of "ROLLBACK TO SAVEPOINT feature" is used in data base trigger, which involve quasi-remote procedures (it means that local procedures has been called like they are from some other data base). 1. UVOD Često želimo spriječiti pojavu "začaranog kruga" (zatvorene petlje) u podacima koji imaju višestruku stablastu strukturu (gdje svaki čvor stabla može imati više čvorova-djece i najviše jedan čvor-roditelj) ili jednostruku stablastu strukturu (gdje točno jedan čvor nema roditelja, a svi ostali čvorovi imaju točno jednog roditelja). Npr. u tablici "djelatnici" (poznata Oracle tablica "emp") želimo spriječiti da jedan djelatnik bude šef drugom djelatniku, a da istovremeno taj drugi djelatnik bude (direktno ili indirektno) šef prvom djelatniku. Ovaj zahtjev je, očito, relativno lako definirati, a dosta je čest u praksi. Međutim, nije ga lako realizirati (isključivo) u bazi podataka, tj. bez pomoći klijentske strane. Ovdje se prikazuje rješenje tog zahtjeva u Oracle bazi podataka, bez pomoći programa na klijentu ili aplikacijskom serveru (dakle, rješenje je u cijelosti na strani baze). Rješenje u jednokorisničkom radu je relativno jednostavno (komplicira ga problem mutirajućih tablica). Međutim, za rješenje u višekorisničkome radu morali smo primijeniti simulaciju "ROLLBACK TO SAVEPOINT ponašanja" u okidaču baze, koristeći kvazi -udaljene procedure (procedure koje pozivamo kao da se nalaze na drugoj bazi, iako se nalaze u lokalnoj bazi).

Upload: others

Post on 07-Sep-2019

1 views

Category:

Documents


0 download

TRANSCRIPT

KAKO SPRIJEČITI "ZAČARANI KRUG"

(rješavanje određenog tipa poslovnih pravila u Oracle bazi podataka)

SAŽETAK

U radu je prikazano kako se u Oracle bazi podataka može spriječiti pojava "začaranog kruga"

(zatvorene petlje) u podacima koji imaju stablastu strukturu, i to bez pomoći klijentskog programa ili

programa na aplikacijskom serveru (dakle, rješenje je u cijelosti na strani baze).

Prikazano je rješenje za jednokorisnički rad i za višekorisnički rad. Za rješenje u višekorisničkom radu

koristi se (naša) simulacija "ROLLBACK TO SAVEPOINT ponašanja" u okidaču baze, koja

(simulacija) koristi kvazi-udaljene procedure (lokalne procedure koje se pozivaju kao da se nalaze na

drugoj bazi).

ABSTRACT

In this article, it is shown how to avoid the problem of a so-called "vicious cycle" (closed loop) in data

with tree structure by doing that completely on the data base side, without writing any client software

or application server software.

Single user and multi user solution is given. In multi user solution, our simulation of "ROLLBACK TO

SAVEPOINT feature" is used in data base trigger, which involve quasi-remote procedures (it means

that local procedures has been called like they are from some other data base).

1. UVOD

Često želimo spriječiti pojavu "začaranog kruga" (zatvorene petlje) u podacima koji imaju višestruku

stablastu strukturu (gdje svaki čvor stabla može imati više čvorova-djece i najviše jedan čvor-roditelj)

ili jednostruku stablastu strukturu (gdje točno jedan čvor nema roditelja, a svi ostali čvorovi imaju

točno jednog roditelja). Npr. u tablici "djelatnici" (poznata Oracle tablica "emp") želimo spriječiti da

jedan djelatnik bude šef drugom djelatniku, a da istovremeno taj drugi djelatnik bude (direktno ili

indirektno) šef prvom djelatniku.

Ovaj zahtjev je, očito, relativno lako definirati, a dosta je čest u praksi. Međutim, nije ga lako realizirati

(isključivo) u bazi podataka, tj. bez pomoći klijentske strane. Ovdje se prikazuje rješenje tog zahtjeva

u Oracle bazi podataka, bez pomoći programa na klijentu ili aplikacijskom serveru (dakle, rješenje je u

cijelosti na strani baze).

Rješenje u jednokorisničkom radu je relativno jednostavno (komplicira ga problem mutirajućih tablica).

Međutim, za rješenje u višekorisničkome radu morali smo primijeniti simulaciju "ROLLBACK TO

SAVEPOINT ponašanja" u okidaču baze, koristeći kvazi-udaljene procedure (procedure koje

pozivamo kao da se nalaze na drugoj bazi, iako se nalaze u lokalnoj bazi).

2. POSLOVNA PRAVILA

Već krajem 70-tih godina pojavili su se značajni opisi slojevite arhitekture informacijskih sustava, tzv.

troslojne arhitekture (three-tier architecture). Kako se navodi u [Larman 2002], u to je vrijeme pojam

"tier" imao poprilično drugačije značenje od onoga kojeg (pretežno) ima danas. Danas se "tier"

(obično) upotrebljava kao oznaka za fizički čvor (node), dok je nekad to bila oznaka za logički sloj.

Kad se govori o logičkim slojevima, danas se obično upotrebljava termin "layer". Troslojna arhitektura

poprimila je veliku popularnost tek 90-tih godina, dobrim dijelom zahvaljujući promociji koju je

napravila Gartner grupa.

"Klasičan" opis troslojne arhitekture navodio je tri sloja: sloj korisničkog sučelja (User Interface), sloj

aplikacijske logike (Application Logic) i podatkovni sloj (Storage). Tijekom vremena se broj (logičkih)

slojeva povećavao, pa npr. J2EE specifikacija navodi četiri sloja: sloj korisničkog sučelja (Client Tier),

sloj prezentacijske logike na serveru (Web Tier), sloj poslovne logike, tj. poslovnih objekata i

poslovnih pravila (EJB Tier) i podatkovni sloj (Data Tier). Neke klasifikacije navode pet ili više slojeva.

U svakom slučaju, poslovna pravila (business rules) čine značajan dio aplikacijskog sloja (ili sloja

poslovne logike).

Danas postoje i takva mišljenja da je pristup razvoju aplikacija na temelju poslovnih pravila (Business

Rules Approach to Application Development) sljedeći glavni evolucijski korak u razvoju aplikacija, koji

slijedi iza objektno-orijentiranog pristupa (Object-Oriented Approach). Prethodnici objektno-

orijentiranom pristupu su strukturna analiza sustava (Structured Systems Analysis) i informacijski

inženjering (Information Engineering). Jedan od zastupnika takvog mišljenja je i autoritet na području

relacijskih baza podataka i pisac brojnih knjiga sa tog područja C.J.Date, koji je popularizirao relacijski

model čiji je autor E.F.Codd. Date naglašava da su današnji sustavi za upravljanje relacijskim

bazama podataka (RDBMS) još poprilično daleko od teorijskog modela koji je postavio Codd, i to

naročito u jednoj od tri glavne komponente relacijskog modela – komponenti integriteta podataka

(Data Integrity), dok su komponenta za definiranje strukture podataka (Data Structures) i komponenta

za manipulaciju podacima (Data Manipulation) puno potpunije realizirane.

Postoje brojne definicije pojma "poslovno pravilo". Npr. u [von Halle 2002] može se naći nekoliko

definicija različitih autora. Za potrebe rada sa Oracle bazom, nama se čini dobra (što ne znači da su

druge loše) definicija (i klasifikacija) iz Oracle CDM (Custom Development Method) metodike:

"Poslovna pravila su ograničenja koja se primjenjuju na stanje sustava ili na promjenu stanja sustava

(Constraint Rules), autorizacijska pravila, koja određuju koji korisnici mogu raditi i što mogu raditi u

sustavu (Authorization Rules), ili akcije koje se automatski pokreću nakon promjene stanja sustava

(Change Event Rules)".

Oracle navodi da se njihova klasifikacija može sagledati i u terminima UML modeliranja. Invarijantama

(Invariant) se mogu nazvati ograničenja na stanje sustava, pretkondicijama (PreCondition) se mogu

zajedno nazvati ograničenja na promjenu stanja sustava i autorizacijska pravila, a postkondicijama

(PostCondition) se mogu nazvati akcije koje se automatski pokreću nakon promjene stanja sustava.

Ova (alternativna) Oracle klasifikacija je vrlo bliska konceptu Design By Contract (DBC) čiji je autor

B.Meyer, autor OOPL jezika Eiffel [Meyer 1997].

Oracle CDM klasifikacija dalje razlaže navedena tri (glavna) tipa poslovnih pravila. Poslovno pravilo

čije rješavanje ovdje prikazujemo spada po Oracle klasifikaciji u tip Constraint Rules, podtip Entity

Rules i vrstu Other Entity Rules. Riječ je o tome da često želimo zabraniti "začarani krug", tj. pojavu

zatvorene petlje u jednostablastim ili višestablastim rekurzivnim strukturama podataka.

Jednostablasta rekurzivna struktura je ona u kojoj točno jedan objekt nema "roditelja" (on je "vrh"

stabla), a svi ostali imaju jednog "roditelja". Višestablasta rekurzivna struktura je sastavljena od više

stabala, tj. barem dva objekta nemaju "roditelja", a svi ostali objekti imaju točno jednog "roditelja".

Jedan primjer (jedno)stablaste strukture je dat poznatom Oracle tablicom djelatnika – "emp"

(employes). Najjednostavniji opis tablice "emp" je:

CREATE OR REPLACE TABLE emp (

empno NUMBER (4),

ename VARCHAR2 (20)

mgr NUMBER (4))

/

Želimo da baza spriječi "začarani krug" u tablici "emp", odnosno želimo onemogućiti da dobijemo

podatke u kojima bi neki djelatnik bio menadžer drugom djelatniku, a drugi djelatnik bi (direktno ili

indirektno) bio menadžer prvom djelatniku.

Za potrebe izlaganja, napunit ćemo tablicu "emp" sa 7 redaka. Djelatnik sa brojem 1 bit će "glavni

šef", djelatnici sa brojevima 2 i 3 bit će "šefovi" (podređeni "glavnom šefu"), djelatnici 4 i 5, odnosno 6

i 7, bit će podređeni "šefu 2", odnosno "šefu 3":

INSERT INTO emp (empno, ename, mgr) VALUES (1, 'EMP 1', NULL);

INSERT INTO emp (empno, ename, mgr) VALUES (2, 'EMP 2', 1);

INSERT INTO emp (empno, ename, mgr) VALUES (3, 'EMP 3', 1);

INSERT INTO emp (empno, ename, mgr) VALUES (4, 'EMP 4', 2);

INSERT INTO emp (empno, ename, mgr) VALUES (5, 'EMP 5', 2);

INSERT INTO emp (empno, ename, mgr) VALUES (6, 'EMP 6', 3);

INSERT INTO emp (empno, ename, mgr) VALUES (7, 'EMP 7', 3);

3. SPREČAVANJE "ZAČARANOG KRUGA" U JEDNOKORISNIČKOM RADU

Rješenje u jednokorisničkom radu je relativno jednostavno. Zapravo, rješenje bi bilo vrlo jednostavno

kad ne bi dolazilo do jednog problema - problema mutirajućih tablica (mutating tables).

Mutirajuća tablica je ona tablica koja se trenutačno modificira pomoću naredbe INSERT, UPDATE ili

DELETE, ili ona tablica koja bi trebala biti ažurirana zbog efekta DELETE CASCADE deklarativnog

ograničenja. Oracle ne dozvoljava da se mutirajuće tablice čitaju (niti ažuriraju) u "row" okidačima

baze (database triggers), tj. okidačima koji se okidaju za svaki redak tablice, jer bismo kao rezultat

(čitanja) mogli dobiti neku neočekivanu vrijednost. Pojednostavljeno rečeno, Oracle ne dozvoljava da

čitamo tablicu dok traje proces njene izmjene u istoj sesiji baze.

Međutim, za razliku od "row" okidača, čitanje se može raditi u "statement" okidačima, tj. okidačima

koji se okidaju jedanput za svaku naredbu INSERT, UPDATE ili DELETE. Klasično rješenje problema

mutirajućih tablica jeste da se u "row" okidaču zapamti (npr. u PL/SQL memorijsku tablicu) koji su

redovi ažurirani, a onda se u "after statement" okidačima čita PL/SQL tablica i primjenjuje se provjera

poslovnog pravila na retke koji su zapamćeni u PL/SQL tablici. Obično želimo da okidači sadrže što

manje programskog koda, tako da okidači najčešće samo pozivaju pohranjene (a najčešće i pakirane)

procedure ili funkcije.

Napravit ćemo okidače navedene u nastavku (naravno, ako ih propustimo na bazu prije nego

propustimo pakirane procedure koje oni koriste, okidači će biti u stanju "INVALID").

Okidač "bus_emp" ("before update statement" nad tablicom "emp" - okida se jedanput prije naredbe

UPDATE) poziva (pakiranu) proceduru za čišćenje PL/SQL tablice:

CREATE OR REPLACE TRIGGER bus_emp

BEFORE UPDATE ON emp

BEGIN

emp_closed_loop.clear_plsql_tab;

END;

/

Okidač "bir_emp" ("before insert row" - okida se jedanput za svaki uneseni redak) provjerava da li su

u stupcima "empno" i "mgr" različite vrijednosti (inače javlja grešku):

CREATE OR REPLACE TRIGGER bir_emp

BEFORE INSERT ON emp

FOR EACH ROW

BEGIN

IF :NEW.empno = :NEW.mgr THEN

RAISE_APPLICATION_ERROR

(-20002, 'Djelatnik ne može biti nadređen samome sebi!');

END IF;

END;

/

Okidač "bur_emp" ("before update row") zabranjuje mijenjanje šifra djelatnika, zabranjuje da djelatnik

bude nadređen samome sebi i poziva proceduru koja pamti redak u PL/SQL tablicu:

CREATE OR REPLACE TRIGGER bur_emp

BEFORE UPDATE ON emp

FOR EACH ROW

BEGIN

IF :NEW.empno <> :OLD.empno THEN

RAISE_APPLICATION_ERROR (-20001, 'NEW EMPNO <> OLD EMPNO');

END IF;

IF :NEW.empno = :NEW.mgr THEN

RAISE_APPLICATION_ERROR

(-20002, 'Djelatnik ne može biti nadređen samome sebi!');

END IF;

IF :NEW.mgr IS NOT NULL AND :NEW.mgr <> NVL (:OLD.mgr, 0) THEN

emp_closed_loop.write_plsql_tab (

p_empno => :OLD.empno,

p_mgr => :NEW.mgr);

END IF;

END;

/

Okidač "aus_emp" ("after update statement" nad tablicom "emp" - okida se jedanput nakon naredbe

UPDATE) poziva (pakiranu) proceduru za provjeru poslovnog pravila (ta je procedura krucijalni dio

programskog koda):

CREATE OR REPLACE TRIGGER aus_emp

AFTER UPDATE ON emp

BEGIN

emp_closed_loop.test;

END;

/

Slijedi paket "emp_closed_loop", sa tri (već navedene) procedure:

CREATE OR REPLACE PACKAGE emp_closed_loop IS

PROCEDURE clear_plsql_tab;

PROCEDURE write_plsql_tab (

p_empno emp.empno%TYPE,

p_mgr emp.mgr%TYPE);

PROCEDURE test;

END emp_closed_loop;

/

CREATE OR REPLACE PACKAGE BODY emp_closed_loop IS

TYPE rec_t IS RECORD (

empno emp.empno%TYPE,

mgr emp.mgr%TYPE);

TYPE plsql_tab_t IS TABLE OF rec_t INDEX BY BINARY_INTEGER;

m_plsql_tab plsql_tab_t;

m_rows BINARY_INTEGER;

PROCEDURE clear_plsql_tab IS

BEGIN

m_rows := 0;

END;

PROCEDURE write_plsql_tab (

p_empno emp.empno%TYPE,

p_mgr emp.mgr%TYPE)

IS

BEGIN

m_rows := m_rows + 1;

m_plsql_tab (m_rows).empno := p_empno;

m_plsql_tab (m_rows).mgr := p_mgr;

END;

PROCEDURE test IS

l_mgr emp.mgr%TYPE;

l_empno emp.empno%TYPE;

BEGIN

FOR i IN 1..m_rows LOOP

l_empno := m_plsql_tab (i).empno;

l_mgr := m_plsql_tab (i).mgr;

WHILE l_mgr IS NOT NULL LOOP

SELECT mgr INTO l_mgr

FROM emp

WHERE empno = l_mgr;

IF l_mgr = l_empno THEN

RAISE_APPLICATION_ERROR

(-20003, 'Greška - zatvorena petlja!');

END IF;

END LOOP;

END LOOP;

END;

END emp_closed_loop;

/

Vidljivo je da procedura "test" čita tablicu "emp", pa tu proceduru nismo mogli pozvati u "row" okidaču

"bur_emp", već smo to mogli napraviti samo u "statement" okidaču "aus_emp".

Primijetimo da smo proceduru "test" mogli napisati i drugačije (konciznije), tako da koristimo klauzulu

CONNECT BY naredbe SELECT (međutim, u nastavku ćemo dograđivati prethodnu verziju, bez

klauzule CONNECT BY):

PROCEDURE test IS

closed_loop EXCEPTION;

-- ORA-01436: CONNECT BY loop in user data

PRAGMA EXCEPTION_INIT (closed_loop, -1436);

l_mgr emp.mgr%TYPE;

l_empno emp.empno%TYPE;

l_dummy NUMBER;

BEGIN

FOR i IN 1..m_rows LOOP

l_empno := m_plsql_tab (i).empno;

l_mgr := m_plsql_tab (i).mgr;

SELECT COUNT (*) INTO l_dummy

FROM emp

WHERE empno = l_empno

START WITH empno = l_mgr CONNECT BY PRIOR mgr = empno;

END LOOP;

EXCEPTION

WHEN closed_loop THEN

RAISE_APPLICATION_ERROR

(-20003, 'Greška - zatvorena petlja!');

END;

Sada možemo testirati rješenje. Ako nad početnim podacima primijenimo sljedeću UPDATE naredbu,

dobit ćemo poruku o grešci (i to je u redu):

UPDATE emp SET mgr = 4 WHERE empno = 1

/

ERROR at line 1:

ORA-20003: Greška - zatvorena petlja!

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 40

ORA-06512: at "SCOTT.AUS_EMP", line 2

ORA-04088: error during execution of trigger 'SCOTT.AUS_EMP'

Nažalost, navedeno rješenje radi dobro samo u jednokorisničkom radu! U višekorisničkome radu (ili,

što je isto, u jednokorisničkom radu u kojem korisnik ima više sesija baze), može se desiti greška, kao

u sljedećem primjeru:

-- 1. SESIJA

UPDATE emp SET mgr = 3 WHERE empno = 2

/

-- 2. SESIJA

UPDATE emp SET mgr = 2 WHERE empno = 3

/

Obje naredbe su prošle, pa smo dobili zatvorenu petlju!

Prije nastavka, nemojmo zaboraviti da vratimo podatke na početno stanje (pomoću ROLLBACK) u

obje sesije. Pretpostavit ćemo da to ubuduće uvijek radimo.

4. POKUŠAJ SPREČAVANJA "ZAČARANOG KRUGA" U VIŠEKORISNIČKOM RADU

POMOĆU AUTONOMNE TRANSAKCIJE

Glavna ideja za sprečavanje "začaranog kruga" u višekorisničkom radu je da, istovremeno dok

provjeravamo da li je došlo do zatvorene petlje, gledamo da li je tekući redak (tj. redak koji trenutačno

provjeravamo) zaključan. Ako je zaključan, možemo pretpostaviti da bi moglo doći do zatvorene

petlje, te javiti grešku.

Međutim, kako provjeriti da li je redak (koji provjeravamo) zaključan? Ako za tu namjenu koristimo

SELECT FOR UPDATE, zaključat ćemo redak sve do kraja transakcije, zato što u okidaču Oracle

baze ne možemo koristiti naredbu ROLLBACK TO SAVEPOINT (napomenimo da ovo ograničenje,

kao ni ograničenje vezano za mutirajuće tablice, nije mana Oracle baze, već prednost, jer ta

ograničenja sprečavaju da dođe do programskih grešaka koje bi se vrlo teško mogle otkriti). No, ako

redak ostane zaključan sve do kraja transakcije, to će spriječiti druge da rade sa takvim retkom, što je

neprihvatljivo.

Budući da od verzije 8i Oracle baza podržava autonomne transakcije, možemo razmišljati da ih

primijenimo za rješenje problema zaključavanja. Naime, u autonomnoj transakciji možemo koristiti

ROLLBACK (zapravo, autonomna transakcija i mora na kraju imati ROLLBACK ili COMMIT). U (tijelo)

paketa "emp_closed_loop" dodat ćemo novu autonomnu proceduru "test_lock":

PROCEDURE test_lock (p_mgr emp.mgr%TYPE) IS

PRAGMA AUTONOMOUS_TRANSACTION;

l_dummy NUMBER;

BEGIN

SELECT 1 INTO l_dummy

FROM emp

WHERE empno = p_mgr

FOR UPDATE NOWAIT;

ROLLBACK;

EXCEPTION

WHEN OTHERS THEN

-- ORA-00054: resource busy and acquire with NOWAIT specified

IF SQLCODE = -54 THEN

RAISE_APPLICATION_ERROR

(-20004, 'Greška - moguća zatvorena petlja!');

ELSE

RAISE;

END IF;

END;

Novu proceduru pozvat ćemo iz mijenjane procedure "test":

PROCEDURE test IS

l_mgr emp.mgr%TYPE;

l_empno emp.empno%TYPE;

BEGIN

FOR i IN 1..m_rows LOOP

l_empno := m_plsql_tab (i).empno;

l_mgr := m_plsql_tab (i).mgr;

WHILE l_mgr IS NOT NULL LOOP

test_lock (l_mgr);

SELECT mgr INTO l_mgr

FROM emp

WHERE empno = l_mgr;

IF l_mgr = l_empno THEN

RAISE_APPLICATION_ERROR

(-20003, 'Greška - zatvorena petlja!');

END IF;

END LOOP;

END LOOP;

END;

Naredbe koje su uzrokovale grešku u točci 2 sad neće uspjeti, jer će baza upozoriti da bi moglo doći

do zatvorene petlje ("moguća greška" a ne "sigurna greška", jer to što je redak zaključan ne znači da

bi do greške sigurno došlo):

-- 1. SESIJA

UPDATE emp SET mgr = 3 WHERE empno = 2

/

-- 2. SESIJA

UPDATE emp SET mgr = 2 WHERE empno = 3

/

ERROR at line 1:

ORA-20004: Greška - moguća zatvorena petlja!

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 64

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 37

ORA-06512: at "SCOTT.AUS_EMP", line 2

ORA-04088: error during execution of trigger 'SCOTT.AUS_EMP'

Nažalost, rješenje sa autonomnom transakcijom općenito ne radi dobro, zato što su autonomnoj

transakciji (baš zato što je autonomna, tj. nezavisna od "glavne" transakcije) zaključani oni redovi koje

je zaključala "glavna" transakcija. Zato autonomna procedura "zaključuje" da je došlo do zatvorene

petlje i onda kad je očito da nije došlo do zatvorene petlje. Evo takvog slučaja, u kojem autonomna

transakcija "pogrešno zaključuje":

-- dvije UPDATE naredbe u istoj sesiji

UPDATE emp SET mgr = 2 WHERE empno = 6

/

UPDATE emp SET mgr = 6 WHERE empno = 7

/

ERROR at line 1:

ORA-20004: Greška - moguća zatvorena petlja!

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 64

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 37

ORA-06512: at "SCOTT.AUS_EMP", line 2

ORA-04088: error during execution of trigger 'SCOTT.AUS_EMP'

Iako bi bilo sasvim u redu da djelatnik broj 6 postane (direktno) nadređen djelatniku 7, druga naredba

UPDATE javlja grešku zato jer autonomna procedura "test_lock" nalazi da je djelatnik 6 zaključan

(zaključala ga je "glavna" transakcija, kroz prvu naredbu UPDATE).

5. SIMULACIJA "ROLLBACK TO SAVEPOINT PONAŠANJA" U OKIDAČU BAZE

Vidjeli smo da autonomna transakcija nije dobra za rješavanje problema zatvorene petlje u

višekorisničkom radu. Nažalost (kako smo već rekli u točci 3.) naredbe SAVEPOINT / ROLLBACK TO

SAVEPOINT ne možemo koristiti u okidaču baze, jer se javljaju greške:

ORA-04092: cannot SET SAVEPOINT in a trigger

ORA-04092: cannot ROLLBACK in a trigger

Međutim, našli smo da je u Oracle bazi moguće simulirati SAVEPOINT / ROLLBACK TO SAVEPOINT

naredbe u okidaču baze. Pokažimo to na jednom jednostavnom primjeru (napomena: budući da i ovaj

primjer koristi tablicu "emp", poželjno je probati ga na nekoj drugoj shemi, a ne na onoj na kojoj

rješavamo problem zatvorene petlje).

Pretpostavimo da želimo imati transakciju koja se sastoji od 3. dijela:

(1) unos jednog DEPT

(2) unos dva EMP sa job = MANAGER (koji pripadaju prethodno unesenom DEPT)

(3) unos dva EMP sa job = PROGRAMER (koji pripadaju prethodno unesenom DEPT).

Pretpostavimo dalje da želimo da transakcija uspije i ako 3.dio ne uspije, ali samo tako da se poništi

ono što je 3.dio djelomično napravio (tj. unos samo jednog EMP). Napravimo prvo paket koji ne radi

dobro:

CREATE OR REPLACE PACKAGE example_pkg IS

PROCEDURE insert_emps_for_dept (p_deptno dept.deptno%TYPE);

END;

/

CREATE OR REPLACE PACKAGE BODY example_pkg IS

PROCEDURE insert_managers (p_deptno dept.deptno%TYPE) IS

BEGIN

INSERT INTO emp (empno, ename, job, mgr, sal, deptno)

VALUES (1, 'EMP 1', 'MANAGER', NULL, 5000, p_deptno);

INSERT INTO emp (empno, ename, job, mgr, sal, deptno)

VALUES (2, 'EMP 2', 'MANAGER', 1, 4000, p_deptno);

END;

PROCEDURE insert_programmers (p_deptno dept.deptno%TYPE) IS

BEGIN

INSERT INTO emp (empno, ename, job, mgr, sal, deptno)

VALUES (3, 'EMP 3', 'PROGRAMER', 1, 1000, p_deptno);

RAISE_APPLICATION_ERROR

(-20001, 'Simulirana greška u sredini 3.dijela transakcije');

INSERT INTO emp (empno, ename, job, mgr, sal, deptno)

VALUES (4, 'EMP 4', 'PROGRAMER', 1, 1000, p_deptno);

END;

PROCEDURE insert_emps_for_dept (p_deptno dept.deptno%TYPE) IS

BEGIN

-- 2. dio transakcije

insert_managers (p_deptno);

-- 3. dio transakcije

BEGIN

insert_programmers (p_deptno);

EXCEPTION

WHEN OTHERS THEN NULL;

END;

END;

END;

/

Pozovimo sada proceduru iz neimenovanog PL/SQL bloka (bez okidača baze), sa:

BEGIN

INSERT INTO dept (deptno, dname) VALUES (1, 'DEPT 1');

example_pkg.insert_emps_for_dept (1);

END;

/

Pogledajmo što smo dobili, sa SELECT upitom:

SELECT emp.empno, emp.ename, dept.deptno, dept.dname

FROM emp, dept

WHERE empno BETWEEN 1 AND 4

AND emp.deptno = dept.deptno

ORDER BY empno

/

EMPNO ENAME DEPTNO DNAME

---------- --------------- ---------- --------------

1 EMP 1 1 DEPT 1

2 EMP 2 1 DEPT 1

3 EMP 3 1 DEPT 1

Naravno, transakcija nije dobra, jer je ostao upisan EMP 3!

Napravimo ROLLBACK i mijenjajmo proceduru "insert_emps_for_dept" tako da dodamo

naredbe SAVEPOINT / ROLLBACK TO SAVEPOINT:

PROCEDURE insert_emps_for_dept (p_deptno dept.deptno%TYPE) IS

BEGIN

-- 2. dio transakcije

insert_managers (p_deptno);

-- 3. dio transakcije

BEGIN

SAVEPOINT before_insert_programmers;

insert_programmers (p_deptno);

EXCEPTION

WHEN OTHERS THEN ROLLBACK TO before_insert_programmers;

END;

END;

Ako sad ponovimo prethodni SELECT, dobit ćemo ispravan rezultat.

Napravimo opet ROLLBACK i kreirajmo okidač:

CREATE OR REPLACE TRIGGER air_dept

AFTER INSERT ON dept

FOR EACH ROW

BEGIN

example_pkg.insert_emps_for_dept (:NEW.deptno);

END;

/

te pokušajmo izvesti naredbu:

INSERT INTO dept (deptno, dname) VALUES (1, 'DEPT 1')

/

Naravno, dešava se greška ORA-04092: cannot ROLLBACK in a trigger.

Napravimo opet ROLLBACK. Sada ćemo (konačno) primijeniti trik. On se temelji na činjenici da ako

pozivamo udaljenu proceduru (pomoću database linka) i ako se u njoj desi neobrađena greška, njeni

se efekti u cijelosti poništavaju (za razliku od lokalne procedure). Nama ne treba udaljena procedura,

ali napravit ćemo kvazi-udaljenu proceduru, koristeći "lokalni" database link (link baze na sebe samu):

CREATE DATABASE LINK local_db_link

CONNECT TO scott IDENTIFIED BY tiger

USING 'local_alias' -- alias na lokalnu bazu

/

Mijenjajmo sada proceduru "insert_emps_for_dept" tako da poziva "insert_programmers" kao

udaljenu proceduru (pomoću database linka):

CREATE OR REPLACE PACKAGE example_pkg IS

PROCEDURE insert_emps_for_dept (p_deptno dept.deptno%TYPE);

-- mora biti u specifikaciji (poziva se pomoću database linka)

PROCEDURE insert_programmers (p_deptno dept.deptno%TYPE);

END;

/

CREATE OR REPLACE PACKAGE BODY example_pkg IS

...

PROCEDURE insert_emps_for_dept (p_deptno dept.deptno%TYPE) IS

BEGIN

-- 2. dio transakcije

insert_managers (p_deptno);

-- 3. dio transakcije

BEGIN

example_pkg.insert_programmers@local_db_link (p_deptno);

EXCEPTION

WHEN OTHERS THEN NULL;

END;

END;

END;

/

Ako sada ponovno izvedemo (već navedenu) naredbu INSERT, naredba SELECT dat će (ispravno)

samo dva retka:

EMPNO ENAME DEPTNO DNAME

---------- --------------- ---------- --------------

1 EMP 1 1 DEPT 1

2 EMP 2 1 DEPT 1

Primijetimo i to da se ova simulacija još u nečemu ponaša kao "pravi" ROLLBACK TO SAVEPOINT.

Naime, ako druga transakcija pokuša zaključati redak koji je već zaključala prva transakcija, i ako

prva transakcija otključa taj redak sa ROLLBACK TO SAVEPOINT, redak i dalje ostaje zaključan za

drugu transakciju (međutim, neka treća transakcija bi sad mogla bez problema zaključati otključani

redak).

6. SPREČAVANJE "ZAČARANOG KRUGA" U VIŠEKORISNIČKOM RADU

Dakle, kako smo vidjeli prethodnoj točci, prvo ćemo napraviti "lokalni" database link:

CREATE DATABASE LINK local_db_link

CONNECT TO scott IDENTIFIED BY tiger

USING 'local_alias' -- alias na lokalnu bazu

/

Sada možemo mijenjati paket "emp_closed_loop". U odnosu na verziju iz točke 4, paket sada ima

proceduru "test_lock" navedenu (i) u specifikaciji, zato jer se procedura "test_lock" poziva iz

procedure "test" kao udaljena procedura. Procedura "test_lock" koristi naredbu

"RAISE_APPLICATION_ERROR (-20999, ...)" (koju procedura "test" ignorira, tj. ne smatra ju

greškom), da bi otključala redak koji je prethodno zaključala (sa SELECT ... FOR UPDATE):

CREATE OR REPLACE PACKAGE emp_closed_loop IS

PROCEDURE clear_plsql_tab;

PROCEDURE write_plsql_tab (

p_empno emp.empno%TYPE,

p_mgr emp.mgr%TYPE);

PROCEDURE test;

PROCEDURE test_lock (p_mgr emp.mgr%TYPE);

END emp_closed_loop;

/

CREATE OR REPLACE PACKAGE BODY emp_closed_loop IS

... KAO PRIJE ...

PROCEDURE test IS

l_mgr emp.mgr%TYPE;

l_empno emp.empno%TYPE;

BEGIN

FOR i IN 1..m_rows LOOP

l_empno := m_plsql_tab (i).empno;

l_mgr := m_plsql_tab (i).mgr;

WHILE l_mgr IS NOT NULL LOOP

BEGIN

emp_closed_loop.test_lock@local_db_link (l_mgr);

EXCEPTION

WHEN OTHERS THEN

IF SQLCODE = -20999 THEN NULL;

ELSE RAISE;

END IF;

END;

SELECT mgr INTO l_mgr

FROM emp

WHERE empno = l_mgr;

IF l_mgr = l_empno THEN

RAISE_APPLICATION_ERROR

(-20004, 'Greška - zatvorena petlja!');

END IF;

END LOOP;

END LOOP;

END;

PROCEDURE test_lock (p_mgr emp.mgr%TYPE) IS

l_dummy NUMBER;

BEGIN

SELECT 1 INTO l_dummy

FROM emp

WHERE empno = p_mgr

FOR UPDATE NOWAIT;

RAISE_APPLICATION_ERROR (-20999, 'Nije važno');

EXCEPTION

WHEN OTHERS THEN

-- ORA-00054: resource busy and acquire with NOWAIT specified

IF SQLCODE = -54 THEN

RAISE_APPLICATION_ERROR

(-20004, 'Greška - moguća zatvorena petlja!');

ELSE

RAISE;

END IF;

END;

END emp_closed_loop;

/

No, moramo reći da i ovo rješenje ponekad može javiti "lažnu uzbunu", tj. javiti da je (možda) došlo do

zatvorene petlje, iako do toga nije došlo, kao npr. u sljedećem primjeru:

-- 1.SESIJA

UPDATE emp SET mgr = 6 WHERE empno = 2

/

-- 2.SESIJA

UPDATE emp SET mgr = 5 WHERE empno = 7

/

ERROR at line 1:

ORA-20004: Greška - moguća zatvorena petlja!

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 70

ORA-06512: at "SCOTT.EMP_CLOSED_LOOP", line 42

ORA-06512: at "SCOTT.AUS_EMP", line 2

ORA-04088: error during execution of trigger 'SCOTT.AUS_EMP'

Nažalost, ovakva "lažna uzbuna" ne može se spriječiti, jer sesija baze ne može točno "znati" što

druge sesije baze rade.

7. ZAKLJUČAK

Vidjeli smo da je sprečavanje pojave zatvorene petlje (u podacima koji imaju stablastu strukturu)

isključivo na strani Oracle baze (tj. bez pomoći programa na klijentu ili aplikacijskom serveru) "sve

samo ne jednostavno". Za neke bi to bio dovoljan razlog da odustanu od realizacije poslovnih pravila

na bazi i da, umjesto toga, realizaciju poslovnih pravila naprave na klijentu ili (još bolje) na

aplikacijskom serveru. Mi smo, međutim, mišljenja da treba pokušati realizirati poslovna pravila na

strani baze, jer se na taj način osigurava da su podaci u bazi konzistentni, neovisno od "vanjskih"

programa (programa na klijentu ili aplikacijskom serveru).

Takvo mišljenje zastupamo onda kad aplikacija radi samo sa jednom bazom podataka (npr. Oracle).

Ukoliko naša aplikacija mora raditi sa više različitih baza podataka, tada je vjerojatno bolji pristup da

se sloj poslovnih pravila implementira na aplikacijskom serveru, jer je (danas) teško napisati rješenja

koja bi bila primjenljiva za različite baze. Rješenje prikazano u ovom radu je strogo vezano za Oracle

bazu. Ako bi se o tom rješenju govorilo kao o predlošku ili uzorku (pattern), moglo bi se reći da ono ne

spada u arhitekturne predloške ili predloške za dizajniranje (architectural pattern, design pattern), već

u tzv. idiome (idioms), tj. rješenja koja su ovisna o određenom programskom jeziku [Larman 2002].

No, ako vjerujemo u tvrdnje i vizije koje navodi C.J.Date, možemo se nadati da će proizvođači

sustava za upravljanje bazama podataka u budućnosti veću pažnju posvetiti poslovnim pravilima,

odnosno donekle zanemarenoj komponenti relacijskog modela – komponenti integriteta podataka.

Ako se to ostvari, nama koji radimo aplikacije nad bazama podataka bit će olakšana realizacija

poslovnih pravila. Kako kaže Date (naslov jedne njegove knjige): "What Not How: The Business Rules

Approach to Application Development".

Literatura:

1 Standardni Oracle priručnici za baze 7, 8i, 9i i 10g

2 Steven Feuerstein: Oracle PL/SQL Programming, O'Reilly, 2002.

3 Barbara von Halle: Business Rules Applied, John Wiley & Sons, 2002.

4 Craig Larman: Applying UML and Patterns, 2. izdanje, Prentice Hall, 2002.

5 Bertrand Meyer: Object-Oriented Software Construction, 2.izdanje, Prentice Hall, 1997.

Neke web stranice vezane za poslovna pravila:

otn.oracle.com/consulting/idelivery/cdma/index.html (Oracle CDM RuleFrame)

www.brcommunity.com (uređuje Ron Ross, autoritet na području poslovnih pravila)

www.dulcian.com (produkt BRIM)

Podaci o autoru:

Zlatko Sirotić (Istra informatički inženjering d.o.o., Pula, e-mail: [email protected])