Ispravno rukovanje transakcijama u bazama podataka vrlo je bitno za poslovne aplikacije. Nažalost, greške u rukovanju transakcijama obično se kod testiranja teže uoče, jer često ovise o spletu događaja, broju korisnika koji istovremeno rade i sl. Najčešće se puno lakše uoče npr. greške u korisničkom sučelju.
Transakcije, i u bazi (npr. Oracle), i u alatima (npr. ADF-u), mogu se koristiti na “standardan” (default) način, i često je tako i najbolje. No, u praksi se neminovno javljaju slučajevi kada treba posegnuti za nekim rješenjem koje nije standardno. A čak i ako se radi na standardan način, potrebno je dobro ga upoznati.
1. ADF – razvoj i arhitektura
Kako kaže Steve Munch, jedan od kreatora Oracle ADF-a (Application Development Framework), u predgovoru knjige svojih kolega iz Oraclea, već u ljeto 1996. godine, 6 mjeseci nakon što je Sun izdao verziju Jave 1.0, u Oracleu su odlučili raditi deklarativni, RAD (Rapid Application Development) alat temeljen na jeziku Java.
Prvo izdanje frameworka, koji se tada nije zvao ADF već JBO (Java Business Objects), uslijedilo je 1999. godine. Ubrzo mu je ime promijenjeno u BC4J (Business Components for Java). BC4J je pokrivao onaj dio koji danas pokriva ADF BC (Business Components), evolutivni nasljednik BC4J-a.
Uz BC4J, Oracle je počeo razvijati i odgovarajući IDE (Interactive Development Environment) imena JDeveloper, licencirajući 1998. tadašnji Borlandov alat JBuilder.
Vrlo brzo, 2001., Oracle je temeljito preradio JDeveloper, pri čemu ga je u potpunosti “prepisao” u Java kod (to je bila verzija 9i; verzije od 4 do 8 su preskočene, kako bi alat pratio verziju tadašnje baze 9i). Ubrzo se, u verziji 10g prvi put pojavio termin ADF. 2005. godine, u novoj verziji 10g (10.1.3), Oracle JDeveloper je postao besplatan softver (free software).
Za razliku od “nižeg” dijela, tj. ADF BC-a (bivšeg BC4J-a), “viši” dio, koji se odnosi na Controller i View dio MVC (Model View Controller) arhitekture, razvijao se manje evolutivno, sa velikim skokovima, što je pratilo uobičajena zbivanja u cijeloj softverskoj industriji vezanoj za Java web aplikacije. Poznato je da su se dinamičke stranice u JEE arhitekturi prvo radile u servlet tehnologiji. Na temelju nje nastala je JSP (Java Server Pages) tehnologija. Oracle je ubrzo uvidio da te tehnologije nisu dovoljno produktivne, jer ne omogućavaju odgovarajuće module ili komponente, pa je razvio svoju specijalnu tehnologiju UIX (User Interface XML), temeljenu na komponentama. UIX Components su služile za definiranje i za renderiranje (rendering, prikazivanje) stranica, a UIX Controller za upravljanje događajima i interakciju između stranica u aplikaciji. Na neki način, UIX su bili preteča standardne JSF (Java Server Faces) tehnologije, čija je (usavršena) varijanta i ADF JSF. Slika 1. prikazuje različite sastavne dijelove ADF frameworka, podijeljene u četiri sloja (sloj izvora podataka, npr. baze podataka, ne spada u ADF).
Slika 1. Sastavni dijelovi ADF frameworka
Kako se vidi na slici 1., uz tri MVC sloja postoji i Business Services sloj, čiji je jedan “predstavnik” ADF BC (zapravo se i Model sloj na slici dijeli na podslojeve ADF Data Control i ADF Binding). Iako se ADF framework može slagati od različitih “kockica” na pojedinom sloju, uobičajeni “izbor” (onaj koji je zapravo i Oracleov izbor za izradu Oracle Fusion aplikacija) označen je žutom bojom, a čine ga (od dolje prema gore) ADF BC, ADF Model, ADF Controller, ADF JSF.
2. Entity Object, View object, Application module
ADF BC Entity Object (ADF BC EO, ili samo EO) su jedna od tri najvažnije komponente ADF Business Service sloja. EO predstavlja tablicu, pogled (view), sinonim ili materijalizirani pogled (materialized view) na bazi. U nastavku ćemo (zbog kraćeg izražavanja) uvijek govoriti kao da EO predstavlja (samo) tablicu baze.
EO se zapravo sastoji od četiri potkomponente, a to su:
- EO XML definicija (EO definition XML metadata file): XML datoteka koja sadrži metapodatke za EO;
- EO klasa za definiciju (EO definition): to je klasa koja predstavlja definiciju za EO tokom izvođenja; podrazumijevana (default) klasa je oracle.jbo.server.EntityDefImpl, a može se napraviti podklasa te klase, što se rjeđe radi;
- EO klasa: ta klasa predstavlja instance danog entiteta, pa se često naziva i EO Instance (više termina istog značenja, te ponekad loše odabrani termini, sigurno ne olakšavaju učenje ADF-a); kako literatura često navodi: “pojednostavljeno rečeno, instanca te klase predstavlja redak u tablici baze” (no to nije baš najpreciznije, jer se redovi iz baze na kraju drže u entity cacheu); podrazumijevana (default) klasa je oracle.jbo.server.EntityImpl, a može se napraviti podklasa te klase, što se obično i radi;
- Entity collection (ili Entity cache) klasa: ta klasa predstavlja cache za instance (objekte) danog entiteta; podrazumijevana (default) klasa je oracle.jbo.server.EntityCache, a može se napraviti podklasa te klase, što se vrlo rijetko radi.
Dvije EO definicije (koje predstavljaju npr. dvije tablice na bazi) mogu biti povezane asocijacijom, koja je najčešće nastala iz FK veze među tablicama na bazi (ali to ne mora biti). Budući da EO nastaju na temelju EO definicija, tada su i odgovarajući EO međusobno povezani. Slika 2. prikazuje asocijaciju između dva EO, te entity cache.
Slika 2. Veza EO definicije i EO, asocijacija između dva EO, te entity cache
ADF BC View Object (ADF BC VO, ili samo VO) ima tri potkomponente koje su slične odgovarajućim potkomponentama za EO (prve tri navedene), te četvrtu koja je specifična za VO:
- VO XML definicija (VO definition XML metadata file): XML datoteka koja sadrži metapodatke za VO;
- VO klasa za definiciju (VO definition): to je klasa koja predstavlja definiciju za VO tokom izvođenja; podrazumijevana (default) klasa je oracle.jbo.server.ViewDefImpl, a može se napraviti podklasa te klase, što se rjeđe radi;
- VO klasa: ta klasa predstavlja instance danog viewa, pa se često naziva i VO Instance; podrazumijevana (default) klasa je oracle.jbo.server.ViewImpl; može se napraviti podklasa te klase, što se obično i radi;
- VO Row klasa: ta klasa predstavlja retke dobivene na temelju rezultata upita; može se napraviti podklasa te klase, što se obično i radi.
Slika 3. prikazuje VO (koji sadrži definiciju upita i definiciju atributa), koji ima tzv. Row set (na slici je prikazan samo jedan row set, ali VO ih može imati više). Row set ima Bind varijable i Row set iteratore (na slici je prikazan samo jedan row set iterator, ali row set ih može imati više). Kako slika prikazuje, row set sadrži retke, ali to su samo pokazivači, koji pokazuju na query collection. Budući da se pretpostavlja da je prikazani VO temeljen na EO (na slici je temeljen samo na jednom EO), query collection dalje sadrži pokazivače na entity cache. Kada VO nije temeljen na EO, onda query collection zaista sadrži retke.
Slika 3. Odnos između VO, row seta, query collection i entity cachea
ADF BC Application Module (ADF BC AM, ili samo AM) ima tri potkomponente koje su formalno slične odgovarajućim potkomponentama za EO i VO:
- AM XML definicija (AM definition XML metadata file): XML datoteka koja sadrži metapodatke za AM;
- AM klasa za definiciju (AM definition): to je klasa koja predstavlja definiciju za AM tokom izvođenja; podrazumijevana (default) klasa je oracle.jbo.server.ApplicationModuleDefImpl, a može se napraviti podklasa te klase, što se rjeđe radi;
- AM klasa: ta klasa predstavlja instance danog AM, pa se često naziva i AM Instance; podrazumijevana (default) klasa je oracle.jbo.server.ApplicationModuleImpl; može se napraviti podklasa te klase, što se obično i radi.
Slika 4. prikazuje glavne sastavne dijelove AM, a to su VO instance (zajedno s View link instancama), te drugi (ugniježđeni) AM. Slika daje i kompletan prikaz svih najvažnijih BC komponenti i njihovih odnosa. Vidi se da AM ne koristi EO na direktan način, već indirektno, preko VO.
Slika 4. Glavni sastavni dijelovi AM, te prikaz svih najvažnijih BC komponenti i njihovih odnosa
3. Application Module Pools i Connection Pools
ADF omogućava upravljanje aplikacijskim stanjem na dvije razine. Jedno je upravljanje na controller sloju, pomoću Save For Later mogućnosti u Task Flowu (TF), a drugo je upravljanje aplikacijskim modulima (AM) na model sloju. Za tu drugu razinu koriste se Application Module Pools i Connection Pools (pričuve AM instanci, pričuve konekcija). Application module pool (AM pool) je kolekcija AM instanci iste vrste (tj. iste AM definicije). AM pool omogućava da veći broj korisnika može (kvazi) istovremeno raditi na manjem broju AM instanci, time štedeći memoriju i procesne mogućnosti aplikacijskog servera (ili klijenta – AM pool, kao i cijeli ADF BC, može raditi i na klijentu, u klijent-server arhitekturi).
AM instanca u poolu može biti u jednom od tri stanja:
- bezuvjetno slobodna za korištenje (ili nereferencirana), bilo kom korisniku;
- slobodna za korištenje, ali referencirana na aplikacijsku sesiju koja ju je prethodno koristila i koja (aplikacijska sesija) još nije završila; u ovom slučaju AM instanca može se ipak predati drugom korisniku, ovisno o tzv. AM State Management Release Levelu, pri čemu će standardno doći do tzv. pasivacije (a kasnije aktivacije) AM instance, kako će kasnije biti prikazano;
- zauzeta – neki korisnik (odnosno njegova Java dretva na aplikacijskom serveru) trenutačno koristi tu AM instancu.
Osim AM poola, postoji i connection pool. Osnovno pravilo za ADF connection pool je: po jedan connection pool (dakle, skup konekcija, a ne jedna konekcija) se kreira za svaki par <JDBCURL, Username> na svakom JVM-u (aplikacija može raditi na više JVM instanci), pri čemu konekciju zahtijeva root AM instanca (ugniježđene AM instance koriste istu konekciju kao njihova root AM instanca).
Kako je prethodno spomenuto, postoje AM State Management Release Leveli, koji se (općenito, ili za određenu AM instancu) mogu birati deklarativno ili programski. Postoje tri mogućnosti:
- Managed release level: on se podrazumijeva (default); ADF pokušava dodijeliti istu AM instancu određenoj aplikacijskoj sesiji, dok to može; kad ne može, doći će do pasivacije AM instance, čime se sprema aplikacijsko stanje, a kasnije do aktivacije (u nastavku se ti procesi detaljnije prikazuju);
- Unmanaged release level: nikakvo stanje se ne sprema između više HTTP request-response parova iste aplikacijske sesije; to je potpuno stateless ponašanje;
- Reserved release level: to je 1:1 veza između AM instance i aplikacijske sesije (preko data controle); to je potpuno stateful ponašanje, koje se ne preporučuje kod web aplikacija, osim kad zatreba.
Prikažimo ukratko što se dešava kod pasivacije i aktivacije AM instance, a to se dešava onda kada se za AM koristi Managed release level (default):
- kod prvog HTTP request-response para nove aplikacijske sesije, aplikacijskoj sesiji dodjeljuje se neka nereferencirana (pretpostavimo da takva postoji) AM instanca iz AM poola;
- na kraju HTTP requesta, ta se AM instanca vraća u AM pool, ali sada kao referencirana (od strane data controla, koji je vezan za aplikacijsku sesiju); na taj način čuvaju se u memoriji npr. svi EO i VO cachevi, kursori baze i dr. za aplikacijsku sesiju, što je svakako brže nego da se oni pune svaki put kod novog HTTP request-response para iste aplikacijske sesije; treba napomenuti da se uobičajeno (default) čuva i veza između AM instance i konekcije (iz connection poola); dok god ne dođe do pasivacije, aplikacija se ponaša kao prava stetefull aplikacija;
- kod sljedećeg HTTP requesta, aplikacijskoj sesiji dodjeljuje se “njena” AM instanca, čime se uobičajeno (default) dodjeljuje i ista konekcija na bazu (što znači i ista sesija na bazi);
- kada neka druga aplikacijska sesija zatraži AM instancu iz AM poola, ali više nema slobodnih instanci, ADF runtime gleda da li ima referenciranih AM instanci koje trenutno nisu zauzete (rezervirane AM instance se nikada ne diraju); ako postoji referencirana i nezauzeta AM instanca, njeno stanje se prvo pasivira u XML oblik (XML se standardno sprema u BLOB polje tablice na bazi, ali može i u datoteku); AM instanca se onda “isprazni” i preda drugoj aplikacijskoj sesiji na korištenje;
- kod sljedećeg HTTP requesta od strane prve aplikacijske sesije, ADF runtime joj (kako je prethodno već prikazano) traži nereferenciranu AM instancu (ako takva postoji), ili radi postupak pasivacije nad AM instancom koja je referencirana (ali nezauzeta) od strane druge aplikacijske sesije; nakon toga izvodi se postupak aktivacije, tj. podaci koji su spremljeni u XML (kod pasivacije) sada služe za punjenje “prazne” AM instance.
Ukoliko se desi da neki zahtjev ne može biti zadovoljen (npr. nema nezauzetih instanci, ili su sve instance u rezerviranom modu), a AM pool je dostignuo maksimum, aplikacijska sesija se prekida.
Za Application Module Pools i Connection Pools postoje (brojni) parametri, koji se mogu podešavati i kroz tab Pooling and Scalability na dijaloškom prozoru Edit BC Configuration, kako prikazuje slika 5.
Slika 5. Application Module Pools i Connection Pools parametri
4. Task Flow i transakcije
Jedan od najvažnijih dodataka u JDeveloperu / ADF-u 11g, u odnosu na JDeveloper / ADF 10g, bila je pojava Task Flows (ADF TF, ili samo TF), koji spadaju u ADF Controller sloj. TF omogućavaju izradu web aplikacija koje su modularnije nego prije, a UI dijelovi aplikacije mogu se višestruko koristiti puno lakše nego prije. Postoje dvije vrste TF: unbounded TF i bounded TF.
Unbounded TF može imati više ulaznih i više izlaznih točaka. Često se koristi za definiranje menija. Bounded TF ima samo jednu ulaznu točku, a može imati više izlaznih točaka. Bounded TF može kod ulaza primati parametre od pozivatelja, te vraćati povratne vrijednosti (return values) kod izlaska. Može se temeljiti na kompletnim JSF stranicama ili fragmentima stranice (page fragments). Sa stanovišta transakcija, najvažnije je što za bounded TF postoji razrađen sustav deklarativnog ili programskog upravljanja transakcijama (na Controller sloju).
Kada se TF koriste zajedno s ADF BC donjim slojem (kako se najčešće radi), tada BC sloj, konkretno root AM, posjeduje konekciju na bazu, transakciju, te podatke (kolekcije) i metode – tj. posjeduje (jednom riječju) servise. Te servise BC sloj predaje View i Controller slojevima preko apstrakcija zvanih Data Control. ADF Controller i TF (kao dio Controllera) mogu više data controla grupirati zajedno, omogućavajući time da se nad tom grupom data controla napravi jedan “aplikacijski” commit ili rollback (napomena: to nije SQL COMMIT / ROLLBACK naredba na bazi; COMMIT / ROLLBACK naredba na bazi samo je mali dio tog procesiranja). Iako BC servisi i dalje posjeduju transakciju i izvršavaju svoj vlastiti djelomični commit ili rollback, ADF Controller definira granice ukupne transakcije (početak i kraj).
Svaki TF može imati svoj vlastiti tzv. Data Control Frame. Unbounded TF uvijek ima svoj vlastiti data control frame. Bounded TF može imati svoj vlastiti data control frame, ili ga dijeliti s TF-pozivateljem, a to se definira na pozvanom bounded TF (a ne na TF-pozivatelju), kroz stranicu Overview -> granu Behavior -> dio Transaction -> Share data controls with calling task flow. Ako se odabere, onda se u odgovarajuću XML datoteku zapisuje vrijednost Shared za svojstvo data-control-scope, a inače se zapisuje vrijednost Isolated.
Kada bounded TF na izlazu izvršava TF return aktivnost, provjerava se da li je taj TF konfiguriran za commit tekuće transakcije (to se određuje drugim svojstvom, o čemu će biti govora kasnije). Ako jeste, ADF runtime traži njegov data control frame – bilo njegov vlastiti, bilo onaj koji dijeli s (nekim) TF-pozivateljem. Data control frame tada delegira commit poziv rukovatelju transakcije (transaction handler instance) na daljnje procesiranje. Rukovatelj transakcije iterira kroz sve data controle koje postoje u data control frameu i poziva naredbu commitTransaction na svakom vršnom (root) data controlu. Data controle dalje delegiraju commit poziv transakcijskim objektima (transaction object) koji su povezani za AM. Napomena: ako TF-dijete participira u transakciji koju je startao pozivatelj, ili je AM ugniježđen u drugi AM, oni dijele zajednički transakcijski objekt. Commit poziv, koji je bio delegiran transakcijskom objektu, sada će napraviti commit svih promjena u svim AM-ovima koji su vezani za taj transakcijski objekt.
Slika 6. prikazuje jedan primjer koji objašnjava kada se kreiraju, odnosno kada se ne kreiraju data control frameovi.
Prvo unbounded TF UTF1 kreira novi data control frame #1, koji može dijeliti s drugim TF-ovima koje poziva, a koji imaju definirano data-control-scope = shared. Na slici su to bounded TF BTF2, BTF3 i BTF4. Za razliku od njih, BTF5 ima definirano data-control-scope = isolated, tj. kreira svoj vlastiti data control frame #2. Njega dijeli BTF6, dok BTF7 opet kreira svoj vlastiti data control frame #3, kojega dijeli i BTF8.
Slika 6. Kada se (ne) kreiraju data control frameovi – primjer
Osim navedenog svojstva Share data controls with calling task flow (kako piše na ekranu), odnosno data-control-scope (kako piše u XML datoteci), bounded TF ima još jedno važno svojstvo. Isto kao prethodno svojstvo, može se odabrati kroz stranicu Overview -> granu Behavior -> dio Transaction, ili kroz XML svojstvo Transaction. To svojstvo ima četiri vrijednosti, koje se malo drugačije zovu na ekranu i u XML datoteci. Vrijednosti na ekranu bolje objašnjavaju o čemu je riječ (nego one u XML datoteci):
- “Always Begin New Transaction” vrijednost određuje da BTF starta novu transakciju. Uobičajeno se ova vrijednost koristi zajedno sa isolated data control scope. Naime, ako se “Always Begin New Transaction” koristi sa shared data control scope, tada se data controle prethodnog TF koriste i u tekućem TF, i tada se, ako prethodni TF (odnosno njegove data controle) ima otvorenu transakciju (tj. isTransactionDirty() == true), kod izvođenja javlja greška “ADFC-00020 + Task flow ‘<name>’ requires a new transaction, but a transaction is already open on the frame”. BTF koji ima “Always Begin New Transaction” mora na kraju pozivati TF flow return activity, koja poziva commit() ili rollback(). Ti pozivi commit() ili rollback() operacija nad data control frame, zapravo pozivaju commit ili rollback na svim data controlama koje su povezane za taj frame. Drugačije je ponašanje kada se commit ili rollback operacije pozivaju iz Data Control Palette – one rade commit ili rollback samo na određenoj data controli, a ne nad svim data controlama koje su povezane za frame.
- “Always Use Existing Transaction” vrijednost određuje da pozvani BTF dijeli transakciju sa pozivajućim BTF. Ako se kod izvođenja desi da pozivajući BTF nije imao transakciju, pozvani BTF javlja grešku “ADFC-00006: Existing transaction is required when calling task flow <task flow name>”. Inače, BTF sa ovom opcijom ne može se tokom dizajna vezati za isolated data control scope. Također, takav BTF ne može tokom dizajna kod izlaska raditi commit ili rollback (JDeveloper nas sprečava u tome). A ako se commit ili rollback i pokrenu programski, tokom izvođenja će se te operacije jednostavno zanemariti (neće se desiti greška, ali se te operacije neće izvršiti).
- “Use Existing Transaction if Possible” je najfleksibilnija od svih opcija. Ona je kombinacija “Always Begin New Transaction” i “Always Use Existing Transaction” opcija. Ako se “Use Existing Transaction if Possible” koristi sa isolated data control scope, ponaša se na isti način kao “Always Begin New Transaction” sa isolated data control scope. Ako se “Use Existing Transaction if Possible” koristi sa shared data control scope, njeno ponašanje ovisi o tome da li je BTF-pozivatelj otvorio transakciju. Ako je BTF-pozivatelj otvorio transakciju, onda je ponašanje isto kao “Always Use Existing Transaction” sa shared data control scope. Ako BTF-pozivatelj nije otvorio transakciju, onda je ponašanje kao “Always Begin New Transaction” sa shared data controls scope. Ovu opciju odabiremo kad nismo sigurni kako će se BTF koristiti.
- “<No Controller Transaction>” je najkompleksnija za razumijevanje. Ta opcija ima sljedeće osobine: ne starta novu transakciju, ne provjerava i ne traži da je transakcija otvorena na data control frame, na kraju TF ne poziva finalizaciju za data control frame transakciju, tj. ne poziva DataControlFrame commit() ili rollback() operacije. No, prije spominjana pravila ipak vrijede i kod ove opcije. Ako se “<No Controller Transaction>” opcija kombinira sa isolated data control scope, ipak će se instancirati novi data control frame i nove data controle. Ako se kombinira sa shared data control scope, dijelit će data control frame i data controle iz prethodnog TF. Sa “<No Controller Transaction>” imamo veću slobodu programske (ne-deklarativne) intervencije, ali sa slobodom dolazi i odgovornost za ispravno ponašanje programskog koda.
Napomena: više materijala o ovoj temi može se naći u referatu “Transakcije i Oracle – baza, Forms, ADF” na stranicama HROUG-a.
Zlatko Sirotić, Istra informatički inženjering, Pula
“Mreža, 02 / 2014”