PHP i Oracle. Tworzenie
aplikacji webowych:
od przetwarzania danych
po Ajaksa
Autor: Yuli Vasiliev
T³umaczenie: Robert Górczyñski, Artur Przyby³a
ISBN: 978-83-246-1974-0
Tytu³ orygina³u:
PHP Oracle Web Development:
Data processing, Security, Caching, XML,
Web Services, and Ajax
Format: 170x230, stron: 392
Poznaj niezwyk³e mo¿liwoœci duetu Oracle–PHP i twórz niezawodne aplikacje!
•
Jak po³¹czyæ PHP i Oracle w celu uzyskania optymalnej wydajnoœci
i niezawodnoœci?
•
Jak wykorzystywaæ funkcje XML w PHP i Oracle?
•
Jak poprawiæ wydajnoœæ dziêki zastosowaniu buforowania?
Baza Danych Oracle nie ma sobie równych pod wzglêdem wydajnoœci, niezawodnoœci
oraz skalowalnoœci. Natomiast skryptowy jêzyk PHP dziêki niezwyk³ej prostocie
stosowania stanowi jedno z najpopularniejszych narzêdzi budowania aplikacji
sieciowych — nawet dla niezbyt doœwiadczonych programistów. Budowanie i wdra¿anie
aplikacji PHP opartych na Oracle pozwala wiêc na optymalne po³¹czenie potê¿nych
mo¿liwoœci i solidnoœci z ³atwoœci¹ u¿ycia i krótkim czasem programowania.
Ksi¹¿ka
„
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych
po Ajaksa
”
zawiera zilustrowany praktycznymi przyk³adami opis technologii oraz
wszystkich narzêdzi potrzebnych, aby optymalnie wykorzystaæ mo¿liwoœci duetu
Oracle–PHP. Dziêki temu podrêcznikowi poznasz nowe funkcje PHP i bazy danych
Oracle; dowiesz siê tak¿e, na czym polega programowanie procedur sk³adowanych
i obs³uga transakcji. Nauczysz siê tworzyæ niezawodne aplikacje i zapewniaæ im wy¿sz¹
wydajnoœæ dziêki mechanizmom buforowania, a tak¿e u¿ywaæ technologii Ajax
z technologiami Oracle Database i funkcjami PHP w celu usprawnienia reakcji aplikacji
na dzia³ania u¿ytkownika.
•
Po³¹czenie PHP i Oracle
•
Przetwarzanie danych
•
Tworzenie i wywo³ywanie wyzwalaczy
•
U¿ywanie podprogramów sk³adowanych
•
Podejœcie zorientowane obiektowo
•
Obs³uga wyj¹tków
•
Bezpieczeñstwo
•
Buforowanie
•
Aplikacje oparte na XML
•
Us³ugi sieciowe
•
Aplikacje oparte na Ajaksie
Po³¹cz wydajnoœæ, skalowalnoœæ i niezawodnoœæ z ³atwoœci¹ u¿ycia
i krótkim czasem programowania!
Spis tre
Ăci
O autorze
11
O recenzencie
13
Wprowadzenie
15
Jakie tematy zosta
ïy poruszone w ksiÈĝce?
16
Dla kogo przeznaczona jest ta ksi
Èĝka?
17
Konwencje zastosowane w ksi
Èĝce
17
U
ĝycie przykïadowych kodów
18
Rozdzia
ï 1. RozpoczÚcie pracy z PHP i Oracle
19
Dlaczego PHP i Oracle?
20
Prostota i elastycznoĂÊ
20
WydajnoĂÊ
21
NiezawodnoĂÊ
21
Co zamiast PHP i Oracle?
22
PHP i MySQL
22
JSF i Oracle
23
Co b
Údzie potrzebne, aby rozpoczÈÊ pracÚ?
23
Wymagane komponenty oprogramowania
23
Rozwaĝania dotyczÈce produktu Oracle Database
25
Zrozumienie Oracle Database
25
Wybór miÚdzy wydaniami oprogramowania Oracle Database
25
Pobieranie oprogramowania Oracle Database
26
Rozwaĝania dotyczÈce PHP
27
Serwer WWW Apache
28
Dlaczego PHP 5?
28
Pobieranie PHP 5
29
Zmuszenie PHP i Oracle do wspóïpracy
30
Oracle Instant Client
30
Zend Core for Oracle
31
Spis tre
Ğci
4
Uĝywanie Oracle SQL*Plus
31
Dlaczego warto uĝywaÊ SQL*Plus w programowaniu PHP/Oracle?
31
NawiÈzywanie poïÈczenia z bazÈ danych za pomocÈ SQL*Plus
32
Wykonywanie skryptów z poziomu SQL*Plus
34
PoïÈczenie wszystkiego razem
35
Utworzenie pierwszej aplikacji PHP/Oracle
37
NawiÈzywanie poïÈczenia z bazÈ danych
40
Uĝywanie metody Local Naming
40
Uĝywanie metody Easy Connect
41
Wykonywanie poleceñ SQL wzglÚdem bazy danych
42
Pobieranie i wyĂwietlanie wyników
42
Podsumowanie
43
Rozdzia
ï 2. PoïÈczenie PHP i Oracle
45
Przedstawienie rozszerzenia PHP OCI8
45
Dlaczego warto uĝywaÊ rozszerzenia OCI8?
46
Przetwarzanie poleceñ za pomocÈ rozszerzenia OCI8
46
Nawi
Èzywanie poïÈczenia z Oracle za pomocÈ rozszerzenia OCI8
51
Definiowanie ciÈgu tekstowego poïÈczenia
51
Funkcje rozszerzenia OCI8, które sïuĝÈ do nawiÈzywania poïÈczenia z Oracle
52
Analizowanie i wykonywanie polece
ñ SQL za pomocÈ rozszerzenia OCI8
53
Przygotowywanie poleceñ SQL do wykonania
54
Uĝywanie zmiennych wiÈzanych
54
Wykonywanie poleceñ SQL
56
Obsïuga bïÚdów
56
Uĝywanie funkcji oci_error()
57
Uĝywanie funkcji trigger_error()
57
Uĝywanie wyjÈtków
58
Pobieranie wyników za pomoc
È funkcji rozszerzenia OCI8
59
Funkcje rozszerzenia OCI8, które sïuĝÈ do pobierania wyników
59
Pobieranie kolejnego rekordu
60
Pobranie wszystkich rekordów
61
Alternatywy dla rozszerzenia PHP OCI8
63
Uĝywanie PEAR DB
63
Uĝywanie ADOdb
65
Uĝywanie PDO
66
Tworzenie wïasnej biblioteki na bazie rozszerzenia OCI8
67
Podsumowanie
68
Rozdzia
ï 3. Przetwarzanie danych
71
Implementacja logiki biznesowej aplikacji PHP/Oracle
72
Kiedy przenosiÊ dane do miejsca dziaïania procesu przetwarzania?
72
Zalety przeniesienia procesu przetwarzania danych do samych danych
73
Sposoby implementacji logiki biznesowej wewnÈtrz bazy danych
74
Wspóïpraca miÚdzy komponentami implementujÈcymi logikÚ biznesowÈ
75
Spis tre
Ğci
5
U
ĝywanie skomplikowanych poleceñ SQL
76
Uĝywanie funkcji Oracle SQL w zapytaniach
76
Funkcje Oracle SQL kontra przetwarzanie danych w PHP
77
Funkcje agregujÈce
79
Klauzula GROUP BY
80
Uĝywanie zïÈczeñ
80
Wykorzystanie zalet widoków
83
Kluczowe korzyĂci pïynÈce z uĝywania widoków
83
Ukrywanie zïoĝonoĂci danych za pomocÈ widoków
84
Uĝywanie klauzuli WHERE
85
U
ĝywanie podprogramów skïadowanych
87
Czym sÈ podprogramy skïadowane?
87
Zalety podprogramów skïadowanych
89
Przykïad uĝycia podprogramu skïadowanego
90
Tworzenie podprogramów skïadowanych
94
Wywoïywanie podprogramów skïadowanych z poziomu PHP
95
U
ĝywanie wyzwalaczy
97
Tworzenie wyzwalaczy
98
Wywoïywanie wyzwalaczy
99
Wywoïywanie procedur skïadowanych z poziomu wyzwalacza
99
Podsumowanie
100
Rozdzia
ï 4. Transakcje
103
Ogólny opis transakcji
104
Czym jest transakcja?
104
Czym sÈ reguïy ACID?
105
W jaki sposób transakcje dziaïajÈ w Oracle?
106
Uĝywanie transakcji w aplikacjach PHP/Oracle
107
Strukturyzacja aplikacji PHP/Oracle w celu nadzorowania transakcji
110
Tworzenie kodu transakcyjnego
113
Nadzorowanie transakcji z poziomu PHP
113
Przenoszenie kodu transakcyjnego do bazy danych
119
Uĝywanie wyzwalaczy
119
Wycofanie na poziomie polecenia
120
Rozwa
ĝania dotyczÈce izolacji transakcji
123
KtórÈ funkcjÚ rozszerzenia OCI8 sïuĝÈcÈ do nawiÈzywania poïÈczenia naleĝy wybraÊ?
123
Kwestie zwiÈzane z wspóïbieĝnym uaktualnianiem
127
Kwestie zwiÈzane z nakïadaniem blokad
127
Utracone uaktualnienia
129
Transakcje autonomiczne
132
Podsumowanie
135
Rozdzia
ï 5. PodejĂcie zorientowane obiektowo
137
Implementacja klas PHP, które pozwalaj
È na wspóïpracÚ z Oracle
138
Bloki budulcowe aplikacji
138
Tworzenie zupeïnie od poczÈtku wïasnej klasy PHP
139
Testowanie nowo utworzonej klasy
141
Wykorzystanie zalet funkcji programowania zorientowanego obiektowo w PHP 5
142
Spis tre
Ğci
6
FunkcjonalnoĂÊ i implementacja
144
Ponowne uĝywanie kodu
146
Obsïuga wyjÈtków
146
Modyfikacja istniejÈcej klasy w celu uĝycia wyjÈtków
147
Rozróĝnienie miÚdzy odmiennymi rodzajami bïÚdów
149
Czy wyjÈtki koniecznie oznaczajÈ bïÚdy?
152
Rozszerzanie istniej
Ècych klas
152
Uĝywanie klas standardowych
152
Pakiet PEAR::Auth w dziaïaniu
153
Zabezpieczanie stron za pomocÈ PEAR::Auth
155
Dostosowanie klas standardowych do wïasnych potrzeb
157
Dostosowanie do wïasnych potrzeb PEAR::Auth
157
Budowanie mniejszego kodu klienta
160
Oddzia
ïywania miÚdzy obiektami
161
Kompozycja
161
Agregacja
164
Komunikacja bazuj
Èca na zdarzeniach
168
U
ĝywanie wïaĂciwoĂci obiektowych Oracle
170
Uĝywanie typów obiektowych w Oracle
170
Implementacja logiki biznesowej za pomocÈ metod obiektów Oracle
171
Uĝywanie obiektów Oracle w celu uproszczenia tworzenia aplikacji
174
Podsumowanie
175
Rozdzia
ï 6. Bezpieczeñstwo
177
Zabezpieczanie aplikacji PHP/Oracle
178
Uwierzytelnianie uĝytkowników
178
Oddzielenie zarzÈdzania bezpieczeñstwem od danych
179
Uĝywanie dwóch schematów bazy danych w celu zwiÚkszenia bezpieczeñstwa
180
Uĝywanie trzech schematów bazy danych w celu zwiÚkszenia bezpieczeñstwa
182
Uĝywanie pakietów PL/SQL i funkcji tabelarycznych
w celu zapewnienia bezpiecznego dostÚpu do danych bazy danych
183
Uĝywanie atrybutu %ROWTYPE
187
Budowanie wïasnego magazynu dla klasy PEAR::Auth
189
Testowanie systemu uwierzytelniania
190
Przeprowadzanie uwierzytelniania na podstawie toĝsamoĂci uĝytkownika
192
Uĝywanie sesji do przechowywania informacji o uwierzytelnionym uĝytkownika
192
Przechowywanie informacji o uĝytkowniku w zmiennych pakietowych
193
Ochrona zasobów na podstawie informacji dotyczÈcych uwierzytelnionego uĝytkownika
195
U
ĝywanie skrótów
199
Tworzenie skrótów haseï
200
Modyfikacja systemu uwierzytelniania
w celu przeprowadzenia operacji tworzenia skrótu
202
Implementacja dok
ïadnej kontroli dostÚpu za pomocÈ widoków bazy danych
204
Implementacja bezpieczeñstwa na poziomie kolumny za pomocÈ widoków
205
Maskowanie wartoĂci kolumn zwracanych aplikacji
208
Uĝywanie funkcji DECODE()
208
Implementacja bezpieczeñstwa na poziomie rekordu za pomocÈ widoków
211
Bezpiecze
ñstwo na poziomie rekordu przy uĝyciu funkcji VPD
214
Podsumowanie
217
Spis tre
Ğci
7
Rozdzia
ï 7. Buforowanie
219
Buforowanie danych za pomoc
È Oracle i PHP
220
Buforowanie zapytañ w serwerze bazy danych
220
Przetwarzanie poleceñ SQL
220
Stosowanie zmiennych wiÈzanych
w celu zwiÚkszenia prawdopodobieñstwa uĝycia bufora puli wspóïdzielonej
222
Uĝywanie kontekstu Oracle podczas buforowania
224
Tworzenie kontekstu globalnego aplikacji
226
Manipulowanie danymi znajdujÈcymi siÚ w kontekĂcie globalnym
228
Zerowanie wartoĂci w kontekĂcie globalnym
232
Mechanizmy buforowania dostÚpne w PHP
236
Wybór strategii buforowania
236
Wywoïywanie funkcji buforowania za pomocÈ pakietu PEAR::Cache_Lite
237
Uaktualnianie buforowanych danych
240
Implementacja buforowania bazuj
Ècego na powiadamianiu
242
Uĝywanie funkcji bazy danych powiadamiania o zmianach
244
Kontrola komunikatów powiadamiania
244
Budowanie procedury PL/SQL, która wysyïa powiadomienia serwerowi WWW
245
Przeprowadzenie kroków konfiguracyjnych wymaganych
przez mechanizm powiadamiania
246
Budowa uchwytu powiadamiania
247
Utworzenie zapytania rejestrujÈcego dla uchwytu powiadamiania
249
Szybki test
250
Implementacja buforowania bazujÈcego na powiadomieniach
za pomocÈ PEAR::Cache_Lite
251
Podsumowanie
253
Rozdzia
ï 8. Aplikacje bazujÈce na XML-u
255
Przetwarzanie danych XML w aplikacjach PHP/Oracle
256
Przetwarzanie danych XML za pomocÈ PHP
256
Tworzenie danych XML za pomocÈ rozszerzenia PHP DOM
257
Wykonywanie zapytañ do dokumentu DOM za pomocÈ XPath
259
Transformacja i przetwarzanie danych XML za pomocÈ XSLT
260
Wykonywanie przetwarzania danych XML wewnÈtrz bazy danych
265
Uĝywanie funkcji generowania SQL/XML w Oracle
265
Przeniesienie caïego procesu przetwarzania danych XML do bazy danych
268
Przechowywanie danych XML w bazie danych
269
Przeprowadzanie transformacji XSLT wewnÈtrz bazy danych
271
Budowanie aplikacji PHP na podstawie Oracle XML DB
272
Uĝywanie bazy danych Oracle do przechowywania,
modyfikowania i pobierania danych XML
273
DostÚpne opcje przechowywania danych XML w bazie danych Oracle
273
Uĝywanie XMLType do obsïugi danych XML w bazie danych
275
Uĝywanie schematów XML
277
Pobieranie danych XML
281
Uzyskanie dostÚpu do danych relacyjnych za pomocÈ widoków XMLType
285
Uĝywanie widoków XMLType
285
Tworzenie widoków XMLType bazujÈcych na schemacie XML
286
Przeprowadzanie operacji DML w widoku XMLType bazujÈcym na schemacie XML
289
Spis tre
Ğci
8
Uĝywanie repozytorium Oracle XML DB
293
Manipulowanie zasobami repozytorium za pomocÈ kodu PL/SQL
294
Uzyskanie dostÚpu do zasobów repozytorium za pomocÈ SQL
294
Wykorzystanie zalet standardowych protokoïów internetowych
295
Obsïuga transakcji
297
Pobieranie danych za pomoc
È Oracle XQuery
298
Uĝywanie silnika XQuery do budowania danych XML
na podstawie danych relacyjnych
299
Rozïoĝenie danych XML na postaÊ danych relacyjnych
301
Podsumowanie
302
Rozdzia
ï 9. Usïugi sieciowe
303
Udost
Úpnienie aplikacji PHP/Oracle jako usïugi sieciowej za pomocÈ rozszerzenia PHP SOAP 304
Komunikacja za pomocÈ SOAP
304
Co jest wymagane do zbudowania usïugi sieciowej SOAP?
305
Budowanie usïugi sieciowej SOAP na podstawie aplikacji PHP/Oracle
307
Budowanie logiki biznesowej usïugi sieciowej wewnÈtrz bazy danych
308
Tworzenie schematu XML przeznaczonego do weryfikacji nadchodzÈcych dokumentów
308
Generowanie unikalnych identyfikatorów dla przekazywanych dokumentów
311
Tworzenie podprogramów PL/SQL implementujÈcych logikÚ biznesowÈ usïugi sieciowej
313
Budowanie uchwytu klasy PHP
317
Uĝywanie WSDL
319
Tworzenie serwera SOAP za pomocÈ rozszerzenia PHP SOAP
322
Budowanie klienta SOAP w celu przetestowania serwera SOAP
323
Bezpiecze
ñstwo
326
Implementacja logiki autoryzacji wewnÈtrz bazy danych
327
Tworzenie uchwytu klasy PHP
329
Tworzenie dokumentu WSDL
330
Tworzenie skryptu klienta
332
Podsumowanie
333
Rozdzia
ï 10. Aplikacje oparte na Ajaksie
335
Budowanie aplikacji PHP/Oracle opartych na Ajaksie
336
Ajax — zasada dziaïania
336
Projekt aplikacji monitorujÈcej opartej na Ajaksie/PHP/Oracle
337
RozwiÈzanie oparte na Ajaksie
339
Tworzenie struktur danych
339
Tworzenie skryptu PHP przetwarzajÈcego ĝÈdania Ajaksa
340
Uĝywanie obiektu JavaScript — XMLHttpRequest
341
Zïoĝenie aplikacji w caïoĂÊ
345
Uĝycie pamiÚci podrÚcznej w celu zwiÚkszenia szybkoĂci pracy aplikacji
347
Implementacja rozwi
Èzañ Master/Detail z uĝyciem metodologii Ajax
348
Projektowanie rozwiÈzania Master/Detail wykorzystujÈcego Ajaksa
348
Opis dziaïania przykïadowej aplikacji
349
Tworzenie struktur danych
351
Generowanie kodu HTML za pomocÈ Oracle XQuery
353
Wysyïanie ĝÈdañ POST za pomocÈ Ajaksa
354
Tworzenie stylów CSS
356
Zïoĝenie aplikacji w caïoĂÊ
357
Podsumowanie
358
Spis tre
Ğci
9
Dodatek A Instalacja oprogramowania PHP i Oracle
359
Instalacja oprogramowania Oracle Database
360
Instalacja wydañ Oracle Database Enterprise/Standard
360
Instalacja wydania Oracle Database Express Edition
363
Instalacja wydania Oracle Database XE w systemie Windows
363
Instalacja wydania Oracle Database XE w systemie Linux
365
Instalacja serwera WWW Apache
365
Instalacja PHP
367
Instalacja PHP w systemie Windows
367
Instalacja PHP w systemie z rodziny Unix
368
Testowanie PHP
369
Zbudowanie mostu mi
Údzy Oracle i PHP
369
Biblioteki Oracle Instant Client
369
WïÈczenie rozszerzenia OCI8 w istniejÈcej instalacji PHP
371
Instalacja narzÚdzia SQL*Plus Instant Client
372
Instalacja Zend Core for Oracle
373
Instalacja Zend Core for Oracle w systemie Windows
373
Instalacja Zend Core for Oracle w systemie Linux
374
Skorowidz
375
4
Transakcje
Aby uzyska
Ê pewnoĂÊ, ĝe uĝywane dane zawsze bÚdÈ prawidïowe, naleĝy stosowaÊ transakcje.
W skrócie: dostarczaj
È one mechanizm pozwalajÈcy na bezpieczne modyfikowanie danych prze-
chowywanych w bazie danych poprzez przeniesienie bazy danych z jednego spójnego stanu
do kolejnego.
Klasycznym przyk
ïadem wykorzystania transakcji jest operacja bankowa, taka jak przelew
Ărodków pieniÚĝnych z jednego konta bankowego na inne. Zaïóĝmy, ĝe zachodzi potrzeba
przelania
Ărodków pieniÚĝnych z konta oszczÚdnoĂciowego na konto bieĝÈce. W celu wyko-
nania tej operacji trzeba b
Údzie przeprowadziÊ przynajmniej dwa kroki: zmniejszyÊ wartoĂci
Ărodków na koncie oszczÚdnoĂciowym i zwiÚkszyÊ wartoĂÊ Ărodków na koncie bieĝÈcym.
Oczywiste jest,
ĝe w tego rodzaju sytuacji konieczne bÚdzie potraktowanie obu operacji jako
pojedynczej, aby zachowa
Ê saldo miÚdzy kontami. Dlatego teĝ ĝadna z wymienionych operacji nie
mo
ĝe zostaÊ przeprowadzona oddzielnie — muszÈ byÊ zakoñczone obie lub ĝadna z nich —
programista musi zagwarantowa
Ê, ĝe albo obie operacje zakoñczÈ siÚ powodzeniem, albo
ĝadna z nich nie bÚdzie przeprowadzona. W takiej sytuacji doskonaïym rozwiÈzaniem jest za-
stosowanie transakcji.
W rozdziale zosta
ïy omówione róĝne mechanizmy, które mogÈ byÊ uĝyte do przeprowadzania
transakcji za pomoc
È technologii PHP i Oracle. Zaczniemy od ogólnego omówienia transakcji,
poniewa
ĝ te informacje sÈ bardzo waĝne w celu dokïadnego zrozumienia sposobu dziaïania
transakcji. Nast
Úpnie zostaïy przedstawione szczegóïowy dotyczÈce stosowania na róĝne spo-
soby transakcji w aplikacjach PHP/Oracle.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
104
Ogólny opis transakcji
Przed rozpocz
Úciem budowania wïasnych aplikacji PHP/Oracle wykorzystujÈcych transakcje
nale
ĝy zapoznaÊ siÚ z podstawowymi informacjami, które dotyczÈ transakcji, i przekonaÊ siÚ,
jak mog
È byÊ przeprowadzane z poziomu PHP i Oracle. W podrozdziale zaprezentowano
ogólny opis transakcji oraz poruszono nast
ÚpujÈce zagadnienia:
Q
Czym s
È transakcje i kiedy programista moĝe chcieÊ je stosowaÊ?
Q
Jak przeprowadza
Ê transakcje za pomocÈ PHP i Oracle?
Q
Jak zorganizowa
Ê aplikacjÚ PHP/Oracle, aby efektywnie kontrolowaÊ transakcje?
Poniewa
ĝ wymienione powyĝej zagadnienia najlepiej moĝna zrozumieÊ za pomocÈ przykïa-
dów, w podrozdziale znajdzie si
Ú kilka prostych przykïadów pokazujÈcych moĝliwy sposób
wykorzystania transakcji w aplikacjach PHP/Oracle.
Czym jest transakcja?
Ogólnie rzecz bior
Èc, transakcja jest czynnoĂciÈ lub seriÈ czynnoĂci, które przenoszÈ system
z jednego stanu spójno
Ăci do kolejnego. Z punktu widzenia programisty budujÈcego aplikacje
oparte na bazie danych transakcja mo
ĝe byÊ uznawana za niewidzialny zestaw operacji, które
przenosz
È bazÚ danych z jednego stanu spójnoĂci do kolejnego.
Transakcja jest jednostk
È logicznÈ pracy, zawierajÈcÈ jedno lub wiÚkszÈ liczbÚ poleceñ SQL, które mogÈ
by
Ê w caïoĂci albo zatwierdzone, albo wycofane.
Oznacza to,
ĝe wszystkie polecenia SQL zawarte w transakcji muszÈ byÊ z powodzeniem za-
ko
ñczone, aby caïa transakcja mogïa zostaÊ zatwierdzona, dziÚki czemu zmiany przeprowa-
dzone przez wszystkie operacje DML zostaj
È trwale przeprowadzone. Graficznie zostaïo to
pokazane na rysunku 4.1.
Rysunek 4.1. Graficzne przedstawienie sposobu dzia
ïania transakcji
Rozdzia
á 4. • Transakcje
105
Jak mo
ĝna zobaczyÊ na powyĝszym rysunku, polecenia SQL skïadajÈce siÚ na transakcjÚ przeno-
sz
È dane, na których operujÈ z jednego stanu spójnoĂci do kolejnego. Transakcja musi zostaÊ
zatwierdzona, aby wprowadzone przez ni
È zmiany zostaïy zastosowane w bazie danych i tym
samym przenios
ïy dane do kolejnego stanu spójnoĂci. W przeciwnym razie wszystkie polece-
nia SQL wykonane przez transakcj
Ú zostanÈ wycofane, a dane pozostanÈ w stanie, w którym
znajdowa
ïy siÚ w chwili rozpoczÚcia transakcji.
Je
ĝeli w trakcie wykonywania transakcji wystÈpi bïÈd serwera, na przykïad awaria sprzÚtu
komputerowego, efekty transakcji zostan
È automatycznie wycofane. Jednak w pewnych sytu-
acjach programista mo
ĝe chcieÊ rÚcznie wycofaÊ ukoñczonÈ (ale jeszcze nie zatwierdzonÈ)
transakcj
Ú, w zaleĝnoĂci od ustalonego warunku. Taka sytuacja zostaïa pokazana graficznie na
rysunku 4.2.
Rysunek 4.2. Graficzne przedstawienie warunkowego zatwierdzenia transakcji
Jak wida
Ê na powyĝszym rysunku, po zakoñczeniu wykonywania wszystkich poleceñ transak-
cji programista ma mo
ĝliwoĂÊ albo jej zatwierdzenia, albo wycofania.
Czym s
È reguïy ACID?
ACID to akronim oznaczaj
Ècy Atomicity (niepodzielnoĂÊ), Consistency (spójnoĂÊ), Isolation
(izolacja) oraz Durability (trwa
ïoĂÊ). Kaĝdy system zarzÈdzania bazÈ danych obsïugujÈcy transakcje
musi spe
ïniaÊ powyĝsze cechy charakterystyczne. Ich podsumowanie znalazïo siÚ w pierwszej
tabeli na nast
Úpnej stronie.
Baza danych Oracle obs
ïuguje wszystkie wïaĂciwoĂci ACID wymienione w powyĝszej tabeli.
Dlatego podczas budowania aplikacji transakcyjnych na Oracle nie trzeba projektowa
Ê wïa-
snych schematów gwarantuj
Ècych spójnoĂÊ i integralnoĂÊ danych. Zamiast tego zawsze lep-
szym rozwi
Èzaniem bÚdzie wykorzystanie transakcji Oracle i zrzucenie obsïugi tego rodzaju
problemów na baz
Ú danych.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
106
W
ïaĂciwoĂÊ
Opis
Niepodzielno
ĂÊ
Transakcja stanowi niepodzieln
È jednostkÚ pracy. Oznacza to, ĝe albo wszystkie operacje
w ramach transakcji zostan
È wykonane, albo nie bÚdzie wykonana ĝadna z nich.
Spójno
ĂÊ
Transakcja przenosi baz
Ú danych z jednego stanu spójnoĂci do kolejnego. Oznacza to,
ĝe podczas przeprowadzania transakcji nie mogÈ byÊ zïamane ĝadne czynniki wpïywajÈce
na spójno
ĂÊ bazy danych. Jeĝeli transakcja zïamie jakiekolwiek reguïy spójnoĂci,
zostanie wycofana.
Izolacja
Do chwili zatwierdzenia transakcji zmiany wprowadzane przez operacje sk
ïadajÈce siÚ
na transakcj
Ú nie powinny byÊ widoczne dla innych, równoczeĂnie przeprowadzanych
transakcji.
Trwa
ïoĂÊ
Gdy transakcja zostanie zatwierdzona, wszystkie modyfikacje wprowadzone przez
transakcj
Ú stanÈ siÚ trwaïe i widoczne dla innych transakcji. TrwaïoĂÊ gwarantuje,
ĝe jeĂli zatwierdzenie transakcji zakoñczy siÚ powodzeniem, w przypadku awarii
systemu nie b
ÚdÈ one wycofane.
W jaki sposób transakcje dzia
ïajÈ w Oracle?
W podrozdziale zostanie pokrótce przedstawiony ogólny opis dzia
ïania transakcji w Oracle.
Szczegó
ïowe informacje dotyczÈce sposobu dziaïania transakcji w bazie danych Oracle moĝna
znale
ěÊ w dokumentacji Oracle: w rozdziale „Transaction Management” w podrÚczniku uĝyt-
kownika Oracle Database Concepts.
W Oracle transakcja nie rozpoczyna si
Ú jawnie, lecz niejawnie podczas uruchamiania pierw-
szego wykonywalnego polecenia SQL. Jednak istnieje kilka sytuacji powoduj
Ècych zakoñcze-
nie transakcji. Przedstawiono w poni
ĝszej tabeli:
Sytuacja
Opis
Wydanie
polecenia
COMMIT
Po wydaniu polecenia
COMMIT
nast
Úpuje zakoñczenie wykonywania bieĝÈcej transakcji.
Polecenie powoduje,
ĝe zmiany wprowadzone przez polecenia SQL transakcji stajÈ siÚ
trwa
ïe.
Wydanie
polecenia
ROLLBACK
Po wydaniu polecenia
ROLLBACK
nast
Úpuje zakoñczenie wykonywania bieĝÈcej
transakcji. Polecenie to powoduje,
ĝe zmiany wprowadzone przez polecenia SQL
transakcji zostaj
È wycofane.
Wydanie
polecenia
DDL
Je
ĝeli zostanie wydane polecenie
DDL
, Oracle w pierwszej kolejno
Ăci zatwierdzi bieĝÈcÈ
transakcj
Ú, a nastÚpnie wykona i zatwierdzi polecenie
DDL
w nowej transakcji, która
sk
ïada siÚ z pojedynczego polecenia.
Zamkni
Úcie
po
ïÈczenia
Kiedy po
ïÈczenie zostanie zamkniÚte, Oracle automatycznie zatwierdzi bieĝÈcÈ
transakcj
Ú w tym poïÈczeniu.
Nieprawid
ïowe
przerwanie
wykonywania
programu
Je
ĝeli wykonywanie programu zostanie nieprawidïowo przerwane, Oracle
automatycznie wycofa bie
ĝÈcÈ transakcjÚ.
Rozdzia
á 4. • Transakcje
107
Jak mo
ĝna siÚ przekonaÊ na podstawie powyĝszej tabeli, transakcja zawsze bÚdzie albo zatwier-
dzona, albo wycofana, niezale
ĝnie od tego, czy zostanie wyraěnie zatwierdzona, czy wycofana.
Warto jednak zwróci
Ê uwagÚ, ĝe zawsze dobrÈ praktykÈ jest wyraěne zatwierdzanie lub wy-
cofywanie transakcji zamiast polegania na zachowaniu domy
Ălnym Oracle. W rzeczywistoĂci
zachowanie domy
Ălne aplikacji transakcyjnej moĝe byÊ róĝne w zaleĝnoĂci od narzÚdzia sto-
sowanego przez aplikacj
Ú do nawiÈzywania poïÈczenia z bazÈ danych Oracle.
Na przyk
ïad jeĝeli skrypt PHP wspóïpracuje z bazÈ danych Oracle za pomocÈ rozszerzenia
OCI8, nie mo
ĝna liczyÊ na to, ĝe aktywna transakcja w poïÈczeniu zostanie automatycznie
zatwierdzona po zamkni
Úciu tego poïÈczenia. W takim przypadku po zamkniÚciu poïÈczenia
lub zako
ñczeniu wykonywania skryptu aktywna transakcja zostanie wycofana.
Natomiast je
ĝeli rozïÈczenie z bazÈ danych nastÈpi po wydaniu polecenia
DISCONNECT
z poziomu
narz
Údzia SQL*Plus lub nastÈpi poïÈczenie z bazÈ jako inny uĝytkownik uĝywajÈcy polecenia
CONNECT
b
Èdě sesja SQL*Plus zostanie zamkniÚta w wyniku wydania polecenia
EXIT
, aktywna
transakcja w po
ïÈczeniu bÚdzie zatwierdzona.
Aby unikn
ÈÊ nieoczekiwanego zachowania w aplikacji, zawsze dobrze jest wyraěnie zatwierdzaÊ bÈdě
wycofywa
Ê transakcje, zamiast polegaÊ na zachowaniu domyĂlnym aplikacji transakcyjnej.
U
ĝywanie transakcji w aplikacjach PHP/Oracle
Jak wspomniano w poprzednim podrozdziale, w Oracle mo
ĝna wyraěnie albo zatwierdziÊ, albo
wycofa
Ê transakcjÚ, uĝywajÈc polecenia odpowiednio
COMMIT
lub
ROLLBACK
. W celu wykonania po-
wy
ĝszych poleceñ z poziomu kodu PHP nie trzeba uĝywaÊ funkcji
oci_parse()
i
oci_execute()
,
jak podczas wykonywania innych polece
ñ SQL, takich jak
SELECT
lub
INSERT
. Zamiast tego
u
ĝywa siÚ funkcji rozszerzenia OCI8 o nazwach
oci_commit()
i
oci_rollback()
.
Przedstawiony poni
ĝej skrypt PHP demonstruje sposób wyraěnego zatwierdzenia lub wyco-
fania transakcji z poziomu kodu PHP podczas u
ĝywania operacji DML. Zadaniem skryptu
jest próba uaktualnienia rekordów tabeli
employees
, reprezentuj
Ècych pracowników, których
identyfikator stanowiska wynosi
ST_MAN
(mened
ĝer magazynu). Uaktualnienie polega na
zwi
Úkszeniu wysokoĂci pensji o 10%. Jeĝeli uaktualnienie jednego lub wiÚkszej liczby rekordów
zako
ñczy siÚ niepowodzeniem, caïa transakcja zostanie wycofana, a uaktualnionym polom
pensji b
ÚdÈ przywrócone ich wartoĂci poczÈtkowe. Caïy proces podsumowujÈ poniĝsze kroki:
Q
Krok 1.: wykonanie zapytania wzgl
Údem tabeli
employees
w celu uzyskania liczby
rekordów przedstawiaj
Ècych pracowników na wskazanym stanowisku (menedĝerów
magazynu).
Q
Krok 2.: rozpocz
Úcie transakcji i wykonanie operacji
UPDATE
wzgl
Údem tabeli
employees
w celu przeprowadzenia próby zwi
Úkszenia pensji menedĝerów
magazynu o 10%.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
108
Q
Krok 3.: wycofanie transakcji, je
Ăli liczba rekordów zmodyfikowanych przez
operacj
Ú
UPDATE
b
Údzie mniejsza niĝ liczba rekordów przedstawiajÈcych
mened
ĝerów. W przeciwnym razie transakcja zostanie zatwierdzona.
Teraz warto zapozna
Ê siÚ z kodem ěródïowym skryptu, aby przekonaÊ siÚ, jak powyĝsze kroki
mog
È byÊ zaimplementowane w PHP za pomocÈ funkcji rozszerzenia OCI8.
<?php
// Plik: trans.php.
if(!$dbConn = oci_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$query = "SELECT count(*) num_rows FROM employees
WHERE job_id='ST_MAN'";
$stmt = oci_parse($dbConn,$query);
if (!oci_execute($stmt)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
oci_fetch($stmt);
$numrows = oci_result($stmt, 'NUM_ROWS');
oci_free_statement($stmt);
$query = "UPDATE employees e
SET salary = salary*1.1
WHERE e.job_id='ST_MAN' AND salary*1.1
BETWEEN (SELECT min_salary FROM jobs j WHERE j.job_id=e.job_id)
AND (SELECT max_salary FROM jobs j WHERE j.job_id=e.job_id)";
$stmt = oci_parse($dbConn,$query);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Operacja uaktualnienia zakoñczyïa siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
}
$updrows = oci_num_rows($stmt);
print "Próba uaktualnienia ".$numrows." rekordów.<br />";
print "Udaïo siÚ uaktualniÊ ".$updrows." rekordów.<br />";
if ($updrows<$numrows) {
if (!oci_rollback($dbConn)) {
$err = oci_error($dbConn);
trigger_error('Nie udaïo siÚ wycofaÊ transakcji: '
.$err['message'], E_USER_ERROR);
}
print "Transakcja zostaïa wycofana.";
} else {
if (!oci_commit($dbConn)) {
$err = oci_error($dbConn);
Rozdzia
á 4. • Transakcje
109
trigger_error('Nie udaïo siÚ zatwierdziÊ transakcji: '
.$err['message'], E_USER_ERROR);
}
print "Transakcja zostaïa zatwierdzona.";
}
?>
W powy
ĝszym skrypcie zdefiniowano zapytanie zwracajÈce liczbÚ rekordów, które przedsta-
wiaj
È pracowników zatrudnionych jako menedĝerowie magazynu. W zapytaniu uĝyto funkcji
count()
w celu obliczenia liczby rekordów spe
ïniajÈcych kryteria zdefiniowane w klauzuli
WHERE
zapytania. W omawianym przypadku warto
ĂciÈ zwrotnÈ funkcji
count(*)
jest liczba rekor-
dów reprezentuj
Ècych pracowników, dla których wartoĂÊ pola
job_id
wynosi
ST_MAN
.
W skrypcie liczba rekordów przedstawiaj
Ècych menedĝerów magazynu jest pobierana z bufo-
ra wynikowego za pomoc
È poïÈczenia funkcji
oci_fetch()
i
oci_result()
. Nie trzeba w tym
przypadku stosowa
Ê pÚtli, poniewaĝ zapytanie zwraca tylko jeden rekord zawierajÈcy poje-
dyncze pole o nazwie
num_rows
.
Nast
Úpnie wykonywane jest zapytanie uaktualniajÈce kolumnÚ
salary
tabeli
employees
. Uak-
tualnienie polega na zwi
Úkszeniu pensji menedĝerów magazynu o 10%. Zapytanie uaktualnia
wysoko
ĂÊ pensji tylko wtedy, gdy nowa wysokoĂÊ pensji bÚdzie mieĂciïa siÚ miÚdzy minimal-
n
È i maksymalnÈ wysokoĂciÈ pensji ustalonymi dla menedĝerów magazynu i zdefiniowanymi
w tabeli
jobs
.
W omawianym przyk
ïadzie polecenie
UPDATE
jest wykonywane w trybie wykonywania
OCI_
´DEFAULT
. W ten sposób nast
Úpuje rozpoczÚcie transakcji, która pozwala programiĂcie w dalszej
cz
ÚĂci skryptu na wyraěne zatwierdzenie lub wycofanie zmian wprowadzonych przez polece-
nie
UPDATE
. Interesuj
ÈcÈ kwestiÈ, na którÈ warto zwróciÊ uwagÚ, jest fakt, ĝe domyĂlny tryb
wykonywania to
OCI_COMMIT_ON_SUCCESS
, w którym polecenie jest zatwierdzane automatycz-
nie, je
Ăli jego wykonywanie zakoñczy siÚ powodzeniem.
Wed
ïug dokumentacji Oracle aplikacja zawsze powinna wyraěnie zatwierdzaÊ bÈdě wycofywaÊ transak-
cj
Ú przed zakoñczeniem dziaïania programu. Jednak podczas uĝywania rozszerzenia PHP OCI8 nie trzeba
tego robi
Ê, gdy polecenia SQL sÈ wykonywane w trybie OCI_COMMIT_ON_SUCCESS. W wymienionym
trybie polecenie SQL jest zatwierdzane automatycznie, je
Ăli jego wykonanie zakoñczy siÚ powodzeniem
(czyli w sytuacji podobnej do wyra
ěnego zatwierdzenia natychmiast po wykonaniu polecenia). Jeĝeli
b
ïÈd serwera uniemoĝliwi zakoñczenie powodzeniem wykonywania polecenia SQL, Oracle automatycznie
wycofa wszystkie zmiany wprowadzone przez nie.
W omawianym skrypcie funkcja
oci_num_rows()
jest wywo
ïywana w celu uzyskania liczby re-
kordów zmodyfikowanych przez operacj
Ú
UPDATE
. Po poznaniu liczby rekordów przedstawia-
j
Ècych menedĝerów magazynu oraz liczby faktycznie uaktualnionych moĝna porównaÊ te
warto
Ăci i sprawdziÊ, czy sÈ identyczne.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
110
W powy
ĝszym skrypcie wycofanie transakcji nastÚpuje, gdy liczba uaktualnionych rekordów
jest mniejsza ni
ĝ caïkowita liczba rekordów przedstawiajÈcych menedĝerów magazynu. Ma to
sens, poniewa
ĝ niedopuszczalna jest sytuacja, w której uaktualnione zostanÈ rekordy tylko
niektórych mened
ĝerów magazynu.
Mo
ĝliwoĂÊ wycofania zmian w przypadku wystÈpienia takiej sytuacji ma znaczenie krytyczne
— pozwala wówczas na u
ĝycie innego skryptu, który bÚdzie mógï prawidïowo uaktualniÊ
wszystkie rekordy przedstawiaj
Èce menedĝerów magazynu. Na przykïad w rzeczywistej sytu-
acji prawdopodobnie po
ĝÈdanym rozwiÈzaniem bÚdzie zwiÚkszenie pensji menedĝera maga-
zynu do maksymalnej dopuszczalnej wysoko
Ăci, jeĂli jej podwyĝka o 10% przekroczy to do-
zwolone maksimum.
Je
ĝeli operacja
UPDATE
zmodyfikuje wszystkie rekordy przedstawiaj
Èce menedĝerów magazy-
nu, transakcj
Ú moĝna zatwierdziÊ za pomocÈ funkcji
oci_commit()
, dzi
Úki czemu wprowadzo-
ne zmiany b
ÚdÈ trwaïe.
Innym elementem, na który warto zwróci
Ê uwagÚ w omawianym skrypcie, jest uĝyty mecha-
nizm obs
ïugi bïÚdów. Jeĝeli w trakcie wykonywania funkcji
oci_rollback()
lub
oci_commit()
wyst
Èpi bïÈd, identyfikator poïÈczenia bÚdzie przekazany funkcji
oci_error()
, która z kolei
zwróci komunikat b
ïÚdu opisujÈcy powstaïy bïÈd.
Strukturyzacja aplikacji PHP/Oracle
w celu nadzorowania transakcji
Jak Czytelnik mo
ĝe przypomnieÊ sobie z lektury rozdziaïu 3., ogólnie rzecz biorÈc, dobrym
pomys
ïem jest umieszczenie wewnÈtrz bazy danych kluczowej logiki biznesowej aplikacji
PHP/Oracle. Pad
ïo tam teĝ równieĝ stwierdzenie, ĝe w prostych przypadkach nie trzeba na-
wet tworzy
Ê kodu PL/SQL w celu przeniesienia do bazy danych procesu przetwarzania da-
nych. Zamiast tego mo
ĝna zaprojektowaÊ skomplikowane zapytania SQL, które po wydaniu
b
ÚdÈ nakazywaïy serwerowi bazy danych przeprowadzenie wszystkich wymaganych operacji
przetwarzania danych.
Wracaj
Èc do przykïadu omówionego w poprzednim podrozdziale: istnieje moĝliwoĂÊ modyfi-
kacji u
ĝytego w nim polecenia
UPDATE
w taki sposób, aby rekordy przedstawiaj
Èce menedĝe-
rów magazynu by
ïy uaktualniane tylko wtedy, gdy nowa wysokoĂÊ pensji kaĝdego menedĝera
nadal b
Údzie znajdowaïa siÚ miÚdzy wartoĂciÈ minimalnÈ i maksymalnÈ dla tego stanowiska.
Wymienione warto
Ăci sÈ zdefiniowane w tabeli
jobs
. Taka modyfikacja wyeliminuje potrzeb
Ú
wykonywania oddzielnego zapytania, które zwraca liczb
Ú rekordów speïniajÈcych powyĝszy
warunek. W ten sposób nast
Èpi zmniejszenie iloĂci kodu koniecznego do napisania w celu
implementacji
ĝÈdanego zachowania funkcji.
W skrócie: nowa wersja polecenia
UPDATE
ïÈczy w ramach pojedynczego polecenia wszystkie
trzy kroki wymienione na pocz
Ètku poprzedniego podrozdziaïu. Nie trzeba nawet wyraěnie
Rozdzia
á 4. • Transakcje
111
zatwierdza
Ê bÈdě wycofywaÊ operacji
UPDATE
. Zamiast tego polecenie
UPDATE
mo
ĝna wydaÊ
w trybie
OCI_COMMIT_ON_SUCCESS
, który gwarantuje,
ĝe operacja zostanie automatycznie zatwier-
dzona, je
Ăli jej wykonanie zakoñczy siÚ powodzeniem, a w przeciwnym razie — wycofana.
Przedstawiony poni
ĝej skrypt prezentuje w dziaïaniu nowÈ wersjÚ polecenia
UPDATE
:
<?php
// Plik: transQuery.php.
if(!$dbConn = oci_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$jobno = 'ST_MAN';
$query = "
UPDATE (SELECT salary, job_id FROM employees WHERE
(SELECT count(*) FROM employees WHERE job_id=:jobid AND
salary*1.1 BETWEEN (SELECT min_salary FROM jobs WHERE
job_id=:jobid) AND
(SELECT max_salary FROM jobs WHERE
job_id=:jobid)) IN
(SELECT count(*) FROM employees WHERE job_id=:jobid)
) emp
SET emp.salary = salary*1.1
WHERE emp.job_id=:jobid";
$stmt = oci_parse($dbConn,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
if (!oci_execute($stmt, OCI_COMMIT_ON_SUCCESS)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
$updrows = oci_num_rows($stmt);
if ($updrows>0) {
print "Transakcja zostaïa zatwierdzona.";
} else {
print "Transakcja zostaïa wycofana.";
}
?>
W powy
ĝszym fragmencie kodu zdefiniowano polecenie
UPDATE
, które spowoduje uaktualnie-
nie wszystkich rekordów reprezentuj
Ècych menedĝerów magazynu poprzez zwiÚkszenie wy-
soko
Ăci ich pensji o 10%, pod warunkiem ĝe ĝadna z nowych wartoĂci pensji nie przekroczy
maksymalnej pensji mened
ĝera magazynu zdefiniowanej w tabeli
jobs
. Je
ĝeli chociaĝ jedna
pensja przekroczy warto
ĂÊ maksymalnÈ, polecenie
UPDATE
nie uaktualni
ĝadnych rekordów.
Aby uzyska
Ê taki sposób dziaïania, zamiast podawania tabeli
employees
w klauzuli polecenia
UPDATE
u
ĝyte zostaïo polecenie
SELECT
, którego warto
ĂciÈ zwrotnÈ sÈ albo wszystkie rekordy
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
112
tabeli
employees
, albo
ĝaden z nich. Zaleĝy to od tego, czy wszystkie rekordy speïniajÈce wa-
runek zdefiniowany w klauzuli
WHERE
polecenia
UPDATE
(w omawianym przypadku b
ÚdÈ to
wszystkie rekordy reprezentuj
Èce menedĝerów magazynu) mogÈ byÊ uaktualnione w taki spo-
sób, aby ka
ĝda nowa wysokoĂÊ pensji nie przekraczaïa wartoĂci maksymalnej pensji dla tego
stanowiska.
To polecenie
SELECT jest uznawane za widok wewnÚtrzny. W przeciwieñstwie do zwykïych widoków
omówionych w podrozdziale „Wykorzystanie zalet widoków”, znajduj
Ècym siÚ w rozdziale 3., widoki
wewn
Útrzne nie sÈ obiektami schematu bazy danych, ale podzapytaniami, które mogÈ byÊ stosowane
jedynie w ramach zawieraj
Ècych je poleceñ za pomocÈ aliasów.
W omawianym przyk
ïadzie zastosowanie w poleceniu
UPDATE
wewn
Útrznego widoku
emp
po-
woduje wyeliminowanie potrzeby wykonywania oddzielnego zapytania zwracaj
Ècego liczbÚ
rekordów reprezentuj
Ècych menedĝerów magazynu, a nastÚpnie sprawdzajÈcego, czy otrzy-
mana liczba jest równa liczbie rekordów faktycznie zmodyfikowanych przez polecenie
UPDATE
.
Teraz skrypt wykonuje tylko pojedyncze zapytanie SQL, dzi
Úki czemu wyraěnie skraca czas
wykonywania skryptu.
Omówiony skrypt jest dobrym przyk
ïadem pokazujÈcym korzyĂci, jakie moĝna odnieĂÊ po przeniesieniu
kluczowej logiki biznesowej aplikacji PHP/Oracle z PHP do bazy danych Oracle. Tu zamiast u
ĝywania
dwóch oddzielnych polece
ñ i analizowania ich wyników w PHP zastosowane zostaïo tylko jedno pole-
cenie SQL, które powoduje,
ĝe przetwarzanie danych zachodzi caïkowicie w serwerze bazy danych.
Warto tak
ĝe zwróciÊ uwagÚ na uĝyty w skrypcie sposób wiÈzania zmiennych. Zmienna PHP
jobno
jest dowi
Èzywana do znacznika
jobid
u
ĝytego w poleceniu
UPDATE
. Interesuj
Ècy jest fakt, ĝe
znacznik zmiennej wi
Èzanej
jobid
pojawia si
Ú w poleceniu czÚĂciej niĝ tylko jednokrotnie.
Inaczej ni
ĝ w poprzednim przykïadzie, w którym polecenie
UPDATE
by
ïo wykonywane w try-
bie
OCI_DEFAULT
, wyra
ěnie rozpoczynajÈcym transakcjÚ, nowa wersja skryptu wykonuje pole-
cenie w trybie
OCI_COMMIT_ON_SUCCESS
. Operacja
UPDATE
jest wi
Úc automatycznie zatwierdza-
na, je
Ăli jej wykonanie zakoñczy siÚ powodzeniem.
Jak wcze
Ăniej wspomniano, OCI_COMMIT_ON_SUCCESS jest trybem domyĂlnym wykonywania polece-
nia. Oznacza to,
ĝe nie trzeba wyraěnie go okreĂlaÊ podczas wywoïywania funkcji oci_execute().
W omawianym przyk
ïadzie zostaï wyraěnie umieszczony w kodzie ěródïowym, aby zwróciÊ na to uwagÚ.
W poprzednim przyk
ïadzie nadal uĝywano funkcji
oci_num_rows()
w celu pobrania liczby re-
kordów zmodyfikowanych przez polecenie
UPDATE
. Jednak tym razem nie trzeba porównywa
Ê
tej liczby z ca
ïkowitÈ liczbÈ rekordów reprezentujÈcych menedĝerów magazynu, jak to miaïo
miejsce w przypadku poprzedniego skryptu. Wszystko, co trzeba zrobi
Ê, to prostu sprawdziÊ,
czy liczba rekordów zmodyfikowanych przez polecenie
UPDATE
jest wi
Úksza niĝ
0
.
Rozdzia
á 4. • Transakcje
113
Je
ĝeli liczba uaktualnionych rekordów jest wiÚksza niĝ
0
, oznacza to,
ĝe operacja
UPDATE
zmo-
dyfikowa
ïa wszystkie rekordy reprezentujÈce menedĝerów magazynu i zostaïa z powodze-
niem zatwierdzona. W takim przypadku nale
ĝy wyĂwietliÊ uĝytkownikowi komunikat infor-
muj
Ècy o zatwierdzeniu transakcji.
Je
ĝeli liczba uaktualnionych rekordów wynosi
0
, oznacza to,
ĝe operacja
UPDATE
nie zmodyfi-
kowa
ïa ĝadnych rekordów. W takim przypadku naleĝy wyĂwietliÊ uĝytkownikowi komunikat
informuj
Ècy o wycofaniu transakcji. Jednak w rzeczywistoĂci transakcja jest zatwierdzona, ale
ĝaden rekord nie zostaï zmodyfikowany przez operacjÚ
UPDATE
.
Tworzenie kodu transakcyjnego
Dotychczas przedstawiono kilka prostych przyk
ïadów pokazujÈcych podstawy dziaïania
transakcji Oracle z PHP. W podrozdziale zostan
È zaprezentowane bardziej skomplikowane
przyk
ïady stosowania transakcji w aplikacjach PHP/Oracle.
Nadzorowanie transakcji z poziomu PHP
Jak Czytelnik dowiedzia
ï siÚ z przykïadów omówionych we wczeĂniejszej czÚĂci rozdziaïu,
funkcja
oci_execute()
pozwala na wykonywanie polecenia SQL w dwóch trybach —
OCI_COMMIT_
´ON_SUCCESS
oraz
OCI_DEFAULT
.
Podczas gdy stosowanie trybu
OCI_COMMIT_ON_SUCCESS
powoduje,
ĝe polecenia sÈ automatycz-
nie zatwierdzane, u
ĝycie trybu
OCI_DEFAULT
wymaga wyra
ěnego wywoïania funkcji
oci_commit()
w celu zatwierdzenia transakcji lub
oci_rollback()
, aby j
È wycofaÊ.
Jednak warto zwróci
Ê uwagÚ, ĝe gdy polecenie jest wykonywane w trybie
OCI_DEFAULT
, utwo-
rzona wówczas transakcja nadal mo
ĝe byÊ zatwierdzona bez wywoïywania funkcji
oci_commit()
.
W tym celu pó
ěniejsze polecenie naleĝy wykonaÊ w trybie
OCI_COMMIT_ON_SUCCESS
.
Powy
ĝsza technika moĝe byÊ zastosowana podczas grupowania dwóch lub wiÚkszej liczby
polece
ñ w pojedynczÈ transakcjÚ. Aby zagwarantowaÊ, ĝe caïa transakcja bÚdzie wycofana,
kiedy wykonanie jednego z polece
ñ zakoñczy siÚ niepowodzeniem lub zostanÈ uzyskane wy-
niki wskazuj
Èce na koniecznoĂÊ wycofania transakcji, moĝna po prostu przerwaÊ wykonywa-
nie skryptu. W tym celu mo
ĝna wywoïaÊ na przykïad funkcjÚ
trigger_error()
wraz ze sta
ïÈ
E_USER_ERROR
jako drugim parametrem, a tym samym wycofa
Ê transakcjÚ bez potrzeby wy-
wo
ïywania funkcji
oci_rollback()
.
Czytelnik mo
ĝe siÚ zastanawiaÊ, dlaczego omawiany jest niejawny sposób koñczenia transakcji
Oracle z poziomu PHP zamiast wyra
ěnego wywoïania funkcji
oci_commit()
b
Èdě
oci_rollback()
.
W ko
ñcu druga z wymienionych funkcji stanowi zalecanÈ metodÚ koñczenia transakcji. Ogólnie
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
114
rzecz bior
Èc, celem tej analizy jest umoĝliwienie ïatwiejszego zrozumienia sposobu dziaïania
transakcji Oracle w skryptach PHP, które wspó
ïdziaïajÈ z bazÈ danych za pomocÈ rozsze-
rzenia OCI8.
W przyk
ïadzie, który zostanie omówiony poniĝej, uĝyto struktur danych, które zdefiniowano
w podrozdziale „Przyk
ïad uĝycia podprogramu skïadowanego”, znajdujÈcym siÚ w rozdziale 3.
Zanim jednak przejdziemy do przyk
ïadu, konieczne jest zmodyfikowanie wymienionych struktur
danych w przedstawiony poni
ĝej sposób. Te polecenia SQL moĝna wykonaÊ z poziomu na-
rz
Údzia SQL*Plus po nawiÈzaniu poïÈczenia z bazÈ danych jako
usr/usr
:
ALTER TABLE accounts
ADD (num_logons INT);
UPDATE accounts
SET num_logons = 0;
COMMIT;
DELETE logons;
ALTER TABLE logons
ADD CONSTRAINT log_time_day
CHECK (RTRIM(TO_CHAR(log_time, 'Day'))
NOT IN ('Saturday', 'Sunday'));
Wydanie polecenia
ALTER TABLE
w powy
ĝszym bloku kodu powoduje dodanie do tabeli
accounts
kolumny
num_logons
typu
INT
. W nowej kolumnie b
Údzie przechowywana liczba udanych operacji
logowania ka
ĝdego uĝytkownika. W tym celu gdy uwierzytelnienie uĝytkownika zakoñczy siÚ
powodzeniem, trzeba b
Údzie zwiÚkszyÊ liczbÚ operacji logowania przechowywanÈ w polu
num_logons
.
Oczywi
Ăcie, nadal moĝna siÚ obejĂÊ bez tej kolumny, wydajÈc wzglÚdem tabeli
logons
zapyta-
nie podobne do poni
ĝszego:
SELECT count(*) FROM logons WHERE usr_id='bob';
Jednak wraz ze wzrostem liczby operacji logowania powy
ĝsze zapytanie bÚdzie bardzo kosz-
towne jak na otrzymanie informacji o liczbie logowa
ñ przeprowadzonych przez danego uĝyt-
kownika.
Po dodaniu komuny
num_logons
do tabeli
accounts
nowej kolumnie nale
ĝy ustawiÊ wartoĂÊ
pocz
ÈtkowÈ wynoszÈcÈ
0
. Ewentualnie wcze
Ăniejsze polecenie
ALTER TABLE
mo
ĝna byïo wydaÊ
z u
ĝyciem klauzuli
DEFAULT
wzgl
Údem kolumny
num_logons
, na przyk
ïad nastÚpujÈco:
ALTER TABLE accounts
ADD (num_logons INT DEFAULT 0);
W omawianym bloku kodu nast
Úpuje wyraěne zatwierdzenie transakcji, aby zmiany wprowa-
dzone przez operacj
Ú
UPDATE
by
ïy trwaïe.
W kolejnym kroku usuwane s
È wszystkie rekordy tabeli
logons
. Ten krok jest wymagany, aby
zagwarantowa
Ê prawidïowe wykonanie polecenia
check constraint
zdefiniowanego w nast
Úpnym
Rozdzia
á 4. • Transakcje
115
kroku. W omawianym przyk
ïadzie moĝna pominÈÊ ten krok, jeĝeli tabela
logons
nie zawiera
rekordów utworzonych w sobot
Ú lub niedzielÚ, co umoĝliwi prawidïowe wykonanie polecenia
check constraint
zdefiniowanego w kolejnym kroku. W przeciwnym razie podczas próby wyko-
nania polecenia
ALTER TABLE
zostanie wy
Ăwietlony nastÚpujÈcy komunikat bïÚdu:
ERROR at line 2:
ORA-02293: cannot validate (USR.LOG_TIME_DAY) - check constraint violated
W skrypcie definicja
check constraint
obejmuje kolumn
Ú
log_time
tabeli
logons
. Wymienio-
ne ograniczenie uniemo
ĝliwia wstawianie nowych rekordów do tabeli
logons
w sobot
Ú lub
niedziel
Ú. Pozwala to na modyfikacjÚ systemu uwierzytelniania w taki sposób, aby uniemoĝli-
wi
Ê uĝytkownikom logowanie siÚ w soboty i niedziele, a tym samym pozwoliÊ na logowanie
jedynie w dni robocze. W pó
ěniejszym czasie takie ograniczenie zawsze bÚdzie moĝna usunÈÊ
poprzez wydanie nast
ÚpujÈcego polecenia:
ALTER TABLE logons DROP CONSTRAINT log_time_day;
Wró
Êmy jeszcze do polecenia
ALTER TABLE
, które przedstawiono powy
ĝej. Warto zwróciÊ
uwag
Ú na uĝyty format
'Day'
, podany jako drugi parametr funkcji
TO_CHAR()
. Nakazuje on
funkcji
TO_CHAR()
konwersj
Ú daty przechowywanej w polu
log_time
na posta
Ê dnia tygodnia.
Nast
Úpnie stosowany jest operator
NOT IN
, który powoduje wykluczenie soboty (
Saturday
)
i niedzieli (
Sunday
) z listy dozwolonych dni.
Nale
ĝy pamiÚtaÊ, ĝe baza danych Oracle rozróĝnia wielkoĂÊ liter podczas dopasowania. Dla-
tego te
ĝ jeĂli podano
'Day'
jako drugi parametr funkcji
TO_CHAR()
, dni tygodnia na li
Ăcie znajdujÈ-
cej si
Ú po prawej stronie operatora
NOT IN
nale
ĝy podaÊ jako:
'Saturday', 'Sunday'
. Mia
ïyby one
posta
Ê
'SATURDAY', 'SUNDAY'
, gdyby drugi parametr funkcji
TO_CHAR()
zosta
ï podany jako
'DAY'
.
Powy
ĝsze polecenia powodujÈ przeprowadzenie wszystkich wymaganych modyfikacji struk-
tury bazy danych. Po ich wykonaniu mo
ĝna wiÚc przejĂÊ do przykïadu, którego zadaniem jest zilu-
strowanie sposobów tworzenia transakcji poprzez wykonanie polecenia
DML
w trybie
OCI_DEFAULT
,
a nast
Úpnie wyraěnego zakoñczenia tej transakcji po wykonaniu kolejnego polecenia, ale
w trybie
OCI_COMMIT_ON_SUCCESS
.
W rzeczywisto
Ăci oczywiĂcie moĝe zajĂÊ potrzeba uĝycia wiÚcej niĝ tylko dwóch poleceñ
w transakcji. W tym celu mo
ĝna wykonaÊ w trybie
OCI_DEFAULT
wszystkie polecenia (poza ostat-
nim), które maj
È zostaÊ zgrupowane w pojedynczej transakcji, a nastÚpnie wykonaÊ ostatnie
polecenie w trybie
OCI_COMMIT_ON_SUCCESS
— transakcja b
Údzie zakoñczona.
Graficzne przedstawienie ca
ïego procesu pokazano na rysunku 4.3.
Przedstawiony poni
ĝej skrypt prezentuje, w jaki sposób architektura pokazana na rysunku 4.3
mo
ĝe byÊ zaimplementowana w PHP. Warto zauwaĝyÊ, ĝe w przeciwieñstwie do funkcji
login()
,
omówionej w podrozdziale „Przyk
ïad uĝycia podprogramu skïadowanego”, który znajduje siÚ
w rozdziale 3., przedstawiona poni
ĝej funkcja
login()
zatrzymuje wykonywanie i zwraca
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
116
Rysunek 4.3. Graficzne przedstawienie procesu wykonywania omówionego powy
ĝej bloku kodu
warto
ĂÊ
false
, kiedy wstawienie rekordu do tabeli
logons
zako
ñczy siÚ niepowodzeniem. Ma
to sens, poniewa
ĝ obecnie nowy rekord jest wstawiany do tabeli
logons
nie tylko w celu zapisania
informacji o logowaniu, ale tak
ĝe sprawdzenia, czy wstawiane dane stosujÈ siÚ do zasad bizneso-
wych. Wymienione zasady oznaczaj
È, ĝe do tabeli
logons
nie mo
ĝe byÊ wstawiony ĝaden rekord
zawieraj
Ècy w kolumnie
log_time
dat
Ú, dla której dniem tygodnia jest sobota bÈdě niedziela.
<?php
// Plik: userLoginTrans.php.
function login($usr, $pswd) {
if(!$rsConnection = oci_connect('usr', 'usr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$query = "SELECT full_name, num_logons FROM accounts
WHERE usr_id = :userid AND pswd = :passwd";
$stmt = oci_parse($rsConnection,$query);
oci_bind_by_name($stmt, ':userid', $usr);
oci_bind_by_name($stmt, ':passwd', $pswd);
if (!oci_execute($stmt)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
}
if (!$arr = oci_fetch_array($stmt, OCI_ASSOC)) {
print "Podano nieprawidïowÈ nazwÚ uĝytkownika lub hasïo.";
return false;
Rozdzia
á 4. • Transakcje
117
}
$num_logons=$arr['NUM_LOGONS']+1;
oci_free_statement($stmt);
$query = "UPDATE accounts SET num_logons = num_logons + 1";
$stmt = oci_parse($rsConnection,$query);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Operacja uaktualnienia zakoñczyïa siÚ niepowodzeniem: '
. $err['message'], E_USER_WARNING);
return false;
}
oci_free_statement($stmt);
$query = "INSERT INTO logons VALUES (:userid, SYSDATE)";
$stmt = oci_parse($rsConnection,$query);
oci_bind_by_name($stmt, ':userid', $usr);
if (!oci_execute($stmt, OCI_COMMIT_ON_SUCCESS)) {
$err = oci_error($stmt);
trigger_error('Operacja wstawienia zakoñczyïa siÚ niepowodzeniem: '
. $err['message'], E_USER_WARNING);
if ($err['code']=='02290'){
print "Nie moĝna nawiÈzaÊ poïÈczenia w sobotÚ lub niedzielÚ.";
}
return false;
}
print "Witaj ".$arr['FULL_NAME']."<br/>";
print "OdwiedziïeĂ nas juĝ ".$num_logons." raz(y)";
session_start();
$_SESSION['user']=$usr;
return true;
}
?>
Jak ju
ĝ wczeĂniej wspomniano, kolumna
num_logons
w tabeli
accounts
przechowuje liczb
Ú
zako
ñczonych powodzeniem operacji logowania kaĝdego uĝytkownika. W skrypcie zdefinio-
wano polecenie
UPDATE
, zwi
ÚkszajÈce wartoĂÊ pola
num_logons
w rekordzie reprezentuj
Ècym
u
ĝytkownika, którego dane uwierzytelniajÈce zostaïy uĝyte podczas procesu uwierzytelniania.
Poprzez wykonanie tego polecenia w trybie
OCI_DEFAULT
nast
Úpuje utworzenie nowej transak-
cji. Ma to sens, poniewa
ĝ wówczas istnieje moĝliwoĂÊ wycofania zmian wprowadzonych przez
operacj
Ú
UPDATE
, je
Ăli kolejna operacja wstawiania danych do tabeli
logons
zako
ñczy siÚ nie-
powodzeniem.
Je
ĝeli wykonanie operacji
UPDATE
zako
ñczy siÚ niepowodzeniem, nastÈpi opuszczenie funkcji
login()
i zwrócenie warto
Ăci
false
wywo
ïujÈcemu jÈ skryptowi. W ten sposób skrypt otrzy-
muje informacje,
ĝe uwierzytelnienie nie powiodïo siÚ.
Nast
Úpnie w omawianym przykïadzie zdefiniowane jest polecenie
INSERT
, które jest wykonywane
po zako
ñczonym powodzeniem uwierzytelnieniu uĝytkownika. Wykonanie go powoduje zwiÚk-
szenie licznika informuj
Ècego o przeprowadzonej liczbie prawidïowych operacji logowania.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
118
Wykonanie polecenia
INSERT
w trybie
OCI_COMMIT_ON_SUCCESS
w omawianym skrypcie gwa-
rantuje,
ĝe transakcja zostanie zatwierdzona w przypadku powodzenia operacji, a w przeciw-
nym razie wycofana. Oznacza to,
ĝe zmiany wprowadzone zarówno przez polecenie
INSERT
,
jak i
UPDATE
albo stan
È siÚ trwaïe, albo bÚdÈ wycofane.
Jak Czytelnik zapewne pami
Úta, wartoĂciÈ zwrotnÈ funkcji
oci_error()
jest tablica asocjacyj-
na dwóch elementów. Pierwszy z nich —
code
— zawiera kod b
ïÚdu Oracle, a drugi,
message
— komunikat b
ïÚdu opisujÈcy ten bïÈd. W omawianym przykïadzie nastÚpuje sprawdzenie,
czy kod b
ïÚdu Oracle jest równy
02290
. Je
ĝeli tak, oznacza to wystÈpienie bïÚdu zwiÈzanego
ze z
ïamaniem naïoĝonego ograniczenia. Poniewaĝ w tabeli
logons
zdefiniowano tylko jedno
ograniczenie (uniemo
ĝliwiajÈce wstawiania nowych rekordów do tabeli
logons
w soboty i nie-
dziele), mo
ĝna poinformowaÊ uĝytkownika o braku moĝliwoĂci uzyskania poïÈczenia w sobotÚ
b
Èdě niedzielÚ.
Je
ĝeli w omawianym skrypcie wykonanie polecenia
INSERT
zako
ñczy siÚ niepowodzeniem,
nast
Èpi zakoñczenie dziaïania funkcji
login()
wraz z warto
ĂciÈ zwrotnÈ
false
. Wskazuje to
wywo
ïujÈcemu jÈ skryptowi, ĝe uwierzytelnienie nie powiodïo siÚ. W przypadku uwierzytelnienia
zako
ñczonego powodzeniem moĝna podjÈÊ odpowiednie dziaïania, na przykïad wyĂwietliÊ
komunikat powitania i utworzy
Ê nowÈ sesjÚ.
Aby teraz zobaczy
Ê w dziaïaniu nowo utworzonÈ funkcjÚ
login()
, mo
ĝna wykorzystaÊ poniĝ-
szy prosty skrypt:
<?php
// Plik: testLoginTrans.php.
require_once "userLoginTrans.php";
if (login('bob','pswd')) {
if (isset($_SESSION['user'])) {
print '<p>'.'Twoja nazwa konta: '.$_SESSION['user'].'</p>';
} else {
print '<p>'.'Zmienna sesji przedstawiajÈca nazwÚ konta
nie zostaïa ustawiona.'.'</p>';
}
}else {
print '<p>'.'Uwierzytelnienie zakoñczyïo siÚ niepowodzeniem'.'</p>';
}
?>
Je
ĝeli przedstawiony powyĝej skrypt testLoginTrans.php zostanie uruchomiony w sobotÚ lub
niedziel
Ú, spowoduje wyĂwietlenie nastÚpujÈcego komunikatu:
Nie moĝna nawiÈzaÊ poïÈczenia w sobotÚ lub niedzielÚ.
Uwierzytelnienie zakoñczyïo siÚ niepowodzeniem.
Jednak uruchomienie skryptu w dniu roboczym spowoduje wygenerowanie nast
ÚpujÈcych
danych wyj
Ăciowych:
Rozdzia
á 4. • Transakcje
119
Witaj Bob Robinson
OdwiedziïeĂ nas juĝ 1 raz(y)
Twoja nazwa konta: bob
Ka
ĝde kolejne uruchomienie skryptu testLoginTrans.php w dniu roboczym spowoduje zwiÚk-
szenie licznika odwiedzin strony przez u
ĝytkownika Bob Robinson. Jednak wykonanie tego
skryptu w sobot
Ú lub niedzielÚ nie powoduje zwiÚkszenia wartoĂci licznika. Powyĝszy test
udowadnia,
ĝe wszystko dziaïa zgodnie z zaïoĝeniami.
Przenoszenie kodu transakcyjnego do bazy danych
Teraz, gdy Czytelnik dysponuje ju
ĝ dziaïajÈcym rozwiÈzaniem transakcyjnym zaimplemento-
wanym przede wszystkim w PHP, warto zastanowi
Ê siÚ, w jaki sposób moĝna zmniejszyÊ iloĂÊ
kodu PHP poprzez przeniesienie cz
ÚĂci logiki biznesowej aplikacji do bazy danych.
U
ĝywanie wyzwalaczy
Prac
Ú moĝna rozpoczÈÊ od zdefiniowania wyzwalacza
BEFORE INSERT
, obejmuj
Ècego tabelÚ
logons
, który b
Údzie automatycznie uaktualniaï tabelÚ
accounts
poprzez zwi
Úkszanie wartoĂci
pola
num_logons
w odpowiednim rekordzie. W ten sposób zostanie wyeliminowana potrzeba
wykonywania tej operacji
UPDATE
z poziomu kodu PHP.
Przedstawione poni
ĝej polecenie SQL powoduje utworzenie wyzwalacza. Naleĝy je wykonaÊ
z poziomu narz
Údzia SQL*Plus po nawiÈzaniu poïÈczenia z bazÈ danych jako
usr/usr
:
CREATE OR REPLACE TRIGGER logons_insert
BEFORE INSERT
ON logons
FOR EACH ROW
BEGIN
UPDATE accounts
SET num_logons = num_logons + 1
WHERE usr_id = :new.usr_id;
END;
/
Po utworzeniu przedstawionego powy
ĝej wyzwalacza
logons_insert
nale
ĝy z kodu funkcji
login()
, która znajduje si
Ú w skrypcie userLoginTrans.php, usunÈÊ poniĝszy fragment:
$query = "UPDATE accounts SET num_logons = num_logons + 1";
$stmt = oci_parse($rsConnection,$query);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Operacja uaktualnienia zakoñczyïa siÚ niepowodzeniem: '
. $err['message'], E_USER_WARNING);
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
120
return false;
}
oci_free_statement($stmt);
Warto zwróci
Ê uwagÚ, ĝe powyĝsza modyfikacja funkcji
login()
nie wymaga zmiany istniej
È-
cego kodu, który implementuje t
Ú funkcjÚ. Dlatego teĝ w celu sprawdzenia uaktualnionej wersji
funkcji
login()
nadal mo
ĝna wykorzystaÊ skrypt testLoginTrans.php, przedstawiony w poprzed-
niej sekcji. Powinien on wygenerowa
Ê takie same jak poprzednio dane wyjĂciowe.
Wycofanie na poziomie polecenia
Przegl
ÈdajÈc kod uaktualnionej funkcji
login()
, Czytelnik mo
ĝe zauwaĝyÊ, ĝe nie powoduje
ona wykonania jakiegokolwiek polecenia w trybie
OCI_DEFAULT
, a tym samym nie tworzy
transakcji. Zamiast tego polecenie
INSERT
jest wykonywane w trybie
OCI_COMMIT_ON_SUCCESS
.
Oznacza to,
ĝe kaĝdy bïÈd wykryty podczas dziaïania polecenia
INSERT
spowoduje wycofanie
wszystkich zmian wprowadzonych przez polecenie
INSERT
. Z kolei je
ĝeli wykonanie polecenia
INSERT
zako
ñczy siÚ powodzeniem, zmiany zostanÈ automatycznie zatwierdzone.
Jak dot
Èd wszystko idzie dobrze. Co siÚ jednak zdarzy, jeĂli polecenie
UPDATE
, wywo
ïane z po-
ziomu wyzwalacza, zako
ñczy swoje dziaïanie niepowodzeniem? Czy spowoduje to wycofanie
zmian wprowadzonych przez polecenie
INSERT
? Prostym testem pomagaj
Ècym w odpowiedzi na
te pytania jest tymczasowe zmodyfikowanie polecenia
UPDATE
w wyzwalaczu
logons_trigger
w taki sposób, aby dzia
ïanie operacji
UPDATE
zawsze ko
ñczyïo siÚ niepowodzeniem. NastÚpnie
nale
ĝy uruchomiÊ skrypt testLoginTrans.php, który omówiono w podrozdziale „Nadzorowanie
transakcji z poziomu PHP”, i zobaczy
Ê co siÚ zdarzy.
W celu ponownego utworzenia wyzwalacza, aby wykonanie jego polecenia
UPDATE
zawsze
ko
ñczyïo siÚ niepowodzeniem, moĝna uĝyÊ przedstawionego poniĝej polecenia SQL:
CREATE OR REPLACE TRIGGER logons_insert
BEFORE INSERT
ON logons
FOR EACH ROW
BEGIN
UPDATE accounts
SET num_logons = num_logons + 'str'
WHERE usr_id = :new.usr_id;
END;
/
Trzeba koniecznie zwróci
Ê uwagÚ na fakt, ĝe chociaĝ wykonanie polecenia
UPDATE
w wyzwala-
czu zawsze b
Údzie koñczyïo siÚ niepowodzeniem, sam wyzwalacz zostanie poprawnie skom-
pilowany.
Teraz, po uruchomieniu skryptu testLoginTrans.php, powinny zosta
Ê wyĂwietlone nastÚpujÈce
dane wyj
Ăciowe:
Uwierzytelnienie zakoñczyïo siÚ niepowodzeniem
Rozdzia
á 4. • Transakcje
121
Jak mo
ĝna siÚ przekonaÊ, proces uwierzytelniania zakoñczyï siÚ niepowodzeniem. Aby upew-
ni
Ê siÚ, ĝe wykonanie polecenia
INSERT
w tabeli
logons
równie
ĝ bÚdzie miaïo taki wynik, moĝna
oblicza
Ê liczbÚ rekordów tabeli przed i po wykonaniu skryptu testLoginTrans.php. Taki efekt
da si
Ú osiÈgnÈÊ za pomocÈ poniĝszego polecenia SQL, które wydano z poziomu narzÚdzia
SQL*Plus po nawi
Èzaniu poïÈczenia z bazÈ danych jako
usr/usr
:
SELECT count(*) FROM logons;
Czytelnik powinien przekona
Ê siÚ, ĝe po wykonaniu skryptu testLoginTrans.php liczba rekordów
tabeli
logons
pozostanie taka sama. Stanowi to dowód,
ĝe niepowodzenie uaktualnienia tabeli
logons
za pomoc
È wyzwalacza
logons_insert BEFORE INSERT
, obejmuj
Ècego tabelÚ
accounts
, po-
woduje tak
ĝe wycofanie zmian wprowadzonych przez polecenie
INSERT
.
Ogólnie rzecz bior
Èc, jeĝeli w trakcie wykonywania wyzwalacza nastÈpi bïÈd, wycofane zostanÈ wszyst-
kie operacje, które spowodowa
ïy uruchomienie go. Wynika to z dziaïania tak zwanego wycofywana na
poziomie rekordu — czyli ka
ĝdy bïÈd powstaïy podczas wykonywania polecenia spowoduje wycofanie
wszystkich zmian wprowadzonych przez to polecenie.
Jednak powy
ĝsze stwierdzenie nie zawsze jest prawdziwe. Na przykïad wyzwalacz
logons_insert
mo
ĝe zostaÊ zaimplementowany w taki sposób, ĝe efekty dziaïania polecenia
INSERT
nie zo-
stan
È wycofane, kiedy wykonanie polecenia
UPDATE
z tego wyzwalacza zako
ñczy siÚ niepowo-
dzeniem. Warto przeanalizowa
Ê przedstawionÈ poniĝej wersjÚ wyzwalacza
logons_insert
:
CREATE OR REPLACE TRIGGER logons_insert
BEFORE INSERT
ON logons
FOR EACH ROW
BEGIN
UPDATE accounts
SET num_logons = num_logons + 'str'
WHERE usr_id = :new.usr_id;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/
Teraz, po uruchomieniu skryptu testLoginTrans.php powinny zosta
Ê wyĂwietlone dane wyj-
Ăciowe o podobnej postaci:
Witaj Bob Robinson
OdwiedziïeĂ nas juĝ 3 raz(y)
Twoja nazwa konta: bob
Nast
Úpnie jeĝeli skrypt zostanie uruchomiony ponownie, wyĂwietlana liczba operacji logowa-
nia pozostanie taka sama. Jednak po sprawdzeniu liczby rekordów tabeli
logons
, jak przed-
stawiono we wcze
Ăniejszej czÚĂci podrozdziaïu, bÚdzie moĝna dostrzec, ĝe kolejne wykonanie
skryptu testLoginTrans.php spowodowa
ïo zwiÚkszenie wartoĂci tej liczby.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
122
Wskazuje to,
ĝe chociaĝ wykonanie polecenia
UPDATE
z wyzwalacza zako
ñczyïo siÚ niepowo-
dzeniem, wykonanie polecenia
INSERT
zako
ñczyïo siÚ powodzeniem. Wynika to z faktu, ĝe
omówiony powy
ĝej wyzwalacz
logons_insert
powoduje ciche zignorowanie jakiegokolwiek b
ïÚdu
zg
ïaszanego podczas jego wykonywania. W sekcji
WHEN OTHERS
— która jest jedyn
È procedurÈ
obs
ïugi wyjÈtków w czÚĂci wyzwalacza odpowiedzialnej za obsïugÚ wyjÈtków — ustalono
warto
ĂÊ
NULL
.
W wi
ÚkszoĂci przypadków zastosowanie powyĝej techniki nie jest zalecane, poniewaĝ powoduje zmianÚ
oczekiwanego sposobu zachowania bazy danych. Rozs
Èdne zaïoĝenie jest takie, ĝe jeĂli podczas wyko-
nywania jakiegokolwiek polecenia SQL wyst
Èpi bïÈd, modyfikacje wprowadzone przez to polecenie sÈ
automatycznie wycofywane.
Dlatego te
ĝ zamiast ustawiaÊ wartoĂÊ
NULL
w procedurze obs
ïugi wyjÈtków, naleĝy utworzyÊ
kod, który b
Údzie podejmowaï odpowiednie dziaïania w odpowiedzi na wystÈpienie bïÚdu.
Na przyk
ïad moĝna wykorzystaÊ procedurÚ
RAISE_APPLICATION_ERROR
w celu wygenerowania
zdefiniowanego przez u
ĝytkownika bïÚdu ORA. ZnajdujÈcy siÚ poniĝej fragment kodu poka-
zuje, w jaki sposób wyzwalacz
logons_insert
móg
ïby zostaÊ zmodyfikowany, aby wywoïywaÊ
RAISE_APPLICATION_ERROR
z poziomu procedury obs
ïugi bïÚdów:
CREATE OR REPLACE TRIGGER logons_insert
BEFORE INSERT
ON logons
FOR EACH ROW
BEGIN
UPDATE accounts
SET num_logons = num_logons + 'str'
WHERE usr_id = :new.usr_id;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20000, 'Uaktualnienie licznika nie powiodïo siÚ.');
END;
/
W powy
ĝszym wyzwalaczu procedura obsïugi wyjÈtków wyraěnie wywoïujÚ procedurÚ
RAISE_
´APPLICATION_ERROR
w celu wygenerowania b
ïÚdu ORA zdefiniowanego przez uĝytkownika.
Je
ĝeli w pliku konfiguracyjnym
php.ini
parametr
display_errors ma wartoĂÊ On, omówiony w pod-
rozdziale „Nadzorowanie transakcji z poziomu PHP” skrypt
userLoginTrans.php
spowoduje wy
Ăwietlenie
komunikatu b
ïÚdu wskazanego jako drugi parametr procedury RAISE_APPLICATION_ERROR.
Teraz, po uruchomieniu skryptu testLoginTrans.php, powinien zosta
Ê wyĂwietlony nastÚpujÈ-
cy komunikat:
Uwierzytelnienie zakoñczyïo siÚ niepowodzeniem
Rozdzia
á 4. • Transakcje
123
Liczba rekordów w tabeli
logons
powinna pozosta
Ê taka sama, co oznacza, ĝe niepowodzenie
operacji uaktualnienia tabeli
accounts
przez wyzwalacz powoduje nie tylko wycofanie zmian
wprowadzonych przez polecenie
UPDATE
, ale równie
ĝ przez polecenie
INSERT
.
Przed zako
ñczeniem pracy z tym przykïadem naleĝy siÚ upewniÊ o ponownym utworzeniu
wyzwalacza
logons_trigger
, aby klauzula
SET
w jego poleceniu
UPDATE
przedstawia
ïa siÚ na-
st
ÚpujÈco:
SET num_logons = num_logons + 1
Rozwa
ĝania dotyczÈce izolacji transakcji
Kiedy transakcja modyfikuje rekord tabeli bazy danych, Oracle nak
ïada na niego blokadÚ
utrzymywan
È aĝ do chwili zatwierdzenia bÈdě wycofania tej transakcji. Celem takiego zacho-
wania jest niedopuszczenie do sytuacji, w której dwie wspó
ïbieĝne transakcje bÚdÈ modyfi-
kowa
ïy ten sam rekord.
Bardzo wa
ĝne jest, aby w tym miejscu wspomnieÊ, ĝe rekordy z naïoĝonymi blokadami nadal
mog
È byÊ odczytywane zarówno przez transakcje uaktualniajÈce rekordy, jak i inne transak-
cje. Ró
ĝnica miÚdzy dwiema wymienionymi transakcjami polega na tym, ĝe transakcja nakïa-
daj
Èca blokadÚ moĝe zauwaĝyÊ zmiany natychmiast po wykonaniu polecenia modyfikujÈcego
te rekordy. Natomiast inna transakcja nie mo
ĝe zobaczyÊ tych zmian aĝ do chwili zatwierdze-
nia zmian przez transakcj
Ú, która naïoĝyïa blokadÚ.
Podczas gdy zastosowany w bazie danych Oracle mechanizm nak
ïadania blokad zostaï szcze-
gó
ïowo omówiony w dokumentacji Oracle (w rozdziale „Data Concurrency and Consistency”
w podr
Úczniku uĝytkownika Oracle Database Concepts), w podrozdziale przedstawiono ogól-
ny opis dzia
ïania izolacji transakcji w aplikacjach PHP/Oracle.
Któr
È funkcjÚ rozszerzenia OCI8 sïuĝÈcÈ do nawiÈzywania
po
ïÈczenia naleĝy wybraÊ?
Jak Czytelnik pami
Úta z podrozdziaïu „Funkcje OCI8, które sïuĝÈ do nawiÈzywania poïÈczenia
z Oracle” znajduj
Ècego siÚ w rozdziale 2., rozszerzenie PHP OCI8 oferuje trzy róĝne funkcje sïu-
ĝÈce do nawiÈzywania poïÈczenia z bazÈ danych Oracle. SÈ to
oci_connect()
,
oci_new_connect()
oraz
oci_pconnect()
. Jedyn
È róĝnicÈ miÚdzy tymi funkcjami jest rodzaj nawiÈzywanego poïÈ-
czenia z baz
È danych.
Zarówno funkcja
oci_connect()
, jak i
oci_pconnect()
u
ĝywajÈ bufora poïÈczenia bazy danych,
tym samym eliminuj
Èc koszt nawiÈzania poïÈczenia w trakcie kaĝdego ĝÈdania. Róĝnica miÚdzy
dwiema wymienionymi funkcjami polega na tym,
ĝe poïÈczenie nawiÈzane przez
oci_connect()
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
124
zostaje zwolnione po zako
ñczeniu wykonywania skryptu, podczas gdy poïÈczenia nawiÈzane
przez funkcj
Ú
oci_pconnect()
s
È trwaïe miÚdzy wykonywaniem kolejnych skryptów.
W przeciwie
ñstwie do
oci_connect()
i
oci_pconnect()
funkcja
oci_new_connect()
nie u
ĝywa
bufora po
ïÈczeñ i zawsze powoduje nawiÈzanie nowego poïÈczenia. Trzeba jÈ stosowaÊ, gdy
zachodzi potrzeba utworzenia w skrypcie dwóch lub wi
Úkszej liczby wspóïbieĝnych transak-
cji. Przedstawiony poni
ĝej przykïad prezentuje uĝycie funkcji
oci_new_connect()
w dzia
ïaniu.
W poni
ĝszym skrypcie warto zwróciÊ uwagÚ na wykorzystanie funkcji
oci_new_connect()
w celu
utworzenia wspó
ïbieĝnej transakcji. Chociaĝ do utworzenia pierwszej transakcji w skrypcie
u
ĝyto funkcji
oci_connect()
, w celu nawi
Èzania nowego poïÈczenia zastosowano funkcjÚ
oci_
´new_connect()
.
<?php
// Plik: newConns.php.
function select_emp_job ($conn, $jobno) {
$query = "SELECT employee_id, first_name, last_name, salary
FROM employees WHERE job_id =:jobid";
$stmt = oci_parse($conn,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
print '<table border="1">';
while ($emp = oci_fetch_array($stmt, OCI_ASSOC)) {
print '<tr>';
print '<td>'.$emp['EMPLOYEE_ID'].'</td>';
print '<td>'.$emp['FIRST_NAME'].' '.$emp['LAST_NAME']. '</td>';
print '<td>'.$emp['SALARY'].'</td>';
print '</tr>';
}
print '</table>';
}
if(!$conn1 = oci_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
if(!$conn2 = oci_new_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$jobno = 'AD_VP';
$query = "UPDATE employees SET salary = 18000 WHERE job_id=:jobid";
$stmt = oci_parse($conn1,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
Rozdzia
á 4. • Transakcje
125
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
print "<h2>Testowanie izolacji transakcji!</h2>";
print "<h4>Transakcja A w ramach poïÈczenia conn1:</h4>";
print "<p>(wyniki po uaktualnieniu oraz przed zatwierdzeniem transakcji
w ramach poïÈczenia conn1)</p>";
select_emp_job($conn1, $jobno);
print "<h4>Transakcja B w ramach poïÈczenia conn2:</h4>";
print "<p>(wyniki po uaktualnieniu oraz przed zatwierdzeniem transakcji
w ramach poïÈczenia conn1)</p>";
select_emp_job($conn2, $jobno);
if (!oci_commit($conn1)) {
$err = oci_error($conn1);
trigger_error('Zatwierdzenie transakcji zakoñczyïo siÚ niepowodzeniem: '
.$err['message'], E_USER_ERROR);
}
print "<h4>Transakcja B w ramach poïÈczenia conn2:</h4>";
print "<p>( wyniki po uaktualnieniu oraz po zatwierdzeniu transakcji
w ramach poïÈczenia conn1)</p>";
select_emp_job($conn2, $jobno);
$query = "UPDATE employees SET salary = 17000 WHERE job_id=:jobid";
$stmt = oci_parse($conn1,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
if (!oci_execute($stmt)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
?>
Na rysunku 4.4 pokazano dane wyj
Ăciowe powyĝszego skryptu. Jak moĝna zobaczyÊ, zmiany
wprowadzone przez operacj
Ú
UPDATE
przeprowadzon
È w transakcji dziaïajÈcej w ramach poïÈ-
czenia
conn1
z baz
È danych sÈ widoczne wewnÈtrz tej transakcji natychmiast po wykonaniu
polecenia
UPDATE
. Nie s
È natomiast widoczne dla wspóïbieĝnej transakcji dziaïajÈcej w ramach
po
ïÈczenia
conn2
a
ĝ do chwili zatwierdzenia pierwszej transakcji.
Powracaj
Èc do kodu ěródïowego omawianego skryptu newConns.php: warto zwróciÊ uwagÚ,
ĝe wszystkie polecenia SQL uĝyte w skrypcie zostaïy wykonane w trybie
OCI_DEFAULT
. Gwa-
rantuje on natychmiastowe zatwierdzenie transakcji.
Nale
ĝy równieĝ zwróciÊ uwagÚ, ĝe pierwsze poïÈczenie w skrypcie zostaïo nawiÈzane za po-
moc
È funkcji
oci_connect()
. Poniewa
ĝ jest to pierwsze poïÈczenie, bufor poïÈczeñ przypisany
temu skryptowi jest pusty, wi
Úc funkcja
oci_connect()
nawi
Èĝe nowe poïÈczenie z bazÈ danych.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
126
Rysunek 4.4. Testowanie izolacji transakcji
Transakcje wspó
ïbieĝne trzeba tworzyÊ z uĝyciem transakcyjnie izolowanych poïÈczeñ. Podczas gdy
pierwsze po
ïÈczenie w skrypcie moĝe byÊ utworzone za pomocÈ funkcji oci_connect(), w celu utworzenia
kolejnych, transakcyjnie izolowanych po
ïÈczeñ w skrypcie trzeba wykorzystaÊ funkcjÚ oci_new_connect().
Nast
Úpnie w celu utworzenia nowego, transakcyjnie izolowanego poïÈczenia w skrypcie na-
st
Úpuje wywoïanie funkcji
oci_new_connect()
.
Poprzez wykonanie polecenia
UPDATE
w trybie
OCI_DEFAULT
w ramach po
ïÈczenia
conn1
nast
Ú-
puje utworzenie transakcji w tym po
ïÈczeniu.
Po wykonaniu polecenia
UPDATE
efekty tej operacji b
ÚdÈ widoczne dla innych operacji prze-
prowadzanych wewn
Ètrz tej samej transakcji. Aby to udowodniÊ, w skrypcie wyĂwietlane sÈ
rekordy zmodyfikowane przez polecenie
UPDATE
wewn
Ètrz tej samej transakcji przed jej za-
twierdzeniem. Jak wida
Ê na rysunku 4.4, polecenie
SELECT
zwraca nowe warto
Ăci uaktualnio-
nych rekordów.
Rozdzia
á 4. • Transakcje
127
Jednak podczas przeprowadzania operacji
SELECT
we wspó
ïbieĝnej transakcji nadal bÚdÈ wi-
doczne pocz
Ètkowe wartoĂci uaktualnionych rekordów. Wynika to z faktu, ĝe wspóïbieĝne trans-
akcje s
È izolowane od zmian wprowadzonych przez niezatwierdzone transakcje. Gdy transak-
cja zostanie zatwierdzona, wszystkie wprowadzone przez ni
È zmiany stanÈ siÚ widoczne dla
innych transakcji.
Wreszcie, za pomoc
È funkcji
UPDATE
nast
Úpuje przywrócenie wartoĂci poczÈtkowych uaktual-
nionym rekordom.
Kwestie zwi
Èzane z wspóïbieĝnym uaktualnianiem
Podczas projektowania aplikacji, która b
Údzie modyfikowaïa dane bazy danych w Ărodowisku
wielodost
Úpnym, programista bÚdzie musiaï zmierzyÊ siÚ z dwoma wyzwaniami:
Q
Zapewnienie poprawno
Ăci i spójnoĂci danych.
Q
Zagwarantowanie,
ĝe wydajnoĂÊ nie ucierpi z powodu nakïadania blokad.
Chocia
ĝ Oracle dostarcza zestaw funkcji, które mogÈ pomóc osiÈgnÈÊ wymienione powyĝej
cele, prawid
ïowe wykorzystanie ich jest juĝ zadaniem programisty. Przedstawione poniĝej
podrozdzia
ïy koncentrujÈ siÚ na pewnych kwestiach zwiÈzanych z wspóïbieĝnym uaktualnia-
niem danych, które mog
È pojawiÊ siÚ w Ărodowisku wielodostÚpnym podczas nieprawidïowe-
go u
ĝywania transakcji.
Kwestie zwi
Èzane z nakïadaniem blokad
Jak ju
ĝ wczeĂniej wspomniano, Oracle nakïada blokadÚ na uaktualniany rekord, aby inne
transakcje nie mia
ïy moĝliwoĂci jego modyfikacji aĝ do zakoñczenia uaktualniajÈcej go trans-
akcji. Podczas gdy celem takiego zachowania jest zagwarantowanie poprawno
Ăci danych uĝy-
wanych w
Ărodowisku wielodostÚpnym, w kiepsko zaprojektowanej aplikacji moĝe ono spo-
wodowa
Ê powstanie znaczÈcego obciÈĝenia.
Warto przyjrze
Ê siÚ prostemu skryptowi pokazujÈcemu, jak kiepsko zaprojektowany skrypt,
który wykonuje d
ïugotrwajÈce operacje, moĝe spowodowaÊ problemy zwiÈzane z nakïadaniem
blokad, je
Ăli zostanie uĝyty w Ărodowisku wielodostÚpnym. Przedstawiony poniĝej skrypt
updateSleep.php wykonuje nast
ÚpujÈce kroki:
Q
Tworzy transakcj
Ú.
Q
Uaktualnia niektóre rekordy w tabeli
employees
.
Q
Wstrzymuje na 20 sekund wykonywanie skryptu.
Q
Wycofuje transakcj
Ú.
Wstrzymanie wykonywania poni
ĝszego skryptu za pomocÈ funkcji
sleep()
pozwala na symu-
lacj
Ú przeprowadzania operacji wymagajÈcej ogromnej iloĂci obliczeñ.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
128
<?php
// Plik: updateSleep.php.
if(!$dbConn = oci_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$jobno = 'AD_VP';
$query = "
UPDATE employees
SET salary = salary*1.1
WHERE job_id=:jobid";
$stmt = oci_parse($dbConn,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
. $err['message'], E_USER_ERROR);
};
$updrows = oci_num_rows($stmt);
print 'Uaktualniono '.$updrows. ' rekord(ów)'.'<br/>';
sleep(20);
oci_rollback($dbConn);
print 'Transakcja zostaïa wycofana.';
?>
W powy
ĝszym skrypcie polecenie
UPDATE
jest wykonywane w trybie
OCI_DEFAULT
, nakazuj
Èc
bazie danych Oracle utworzenie transakcji.
Nast
Úpnie wywoïanie funkcji
sleep()
powoduje wstrzymanie wykonywania skryptu na dwa-
dzie
Ăcia sekund, symulujÈc tym samym przeprowadzanie operacji wymagajÈcej ogromnej ilo-
Ăci obliczeñ.
Wreszcie, za pomoc
È funkcji
oci_rollback()
nast
Úpuje wycofanie transakcji. W rzeczywisto-
Ăci ten krok jest opcjonalny, poniewaĝ transakcja zostaje wycofana automatycznie, gdy skrypt
ko
ñczy dziaïanie.
Teraz uruchomiony skrypt updateSleep.php, który omówiono wcze
Ăniej, uaktualni wszystkie
rekordy
Administration Vice President
w tabeli
employees
, zablokuje je na dwadzie
Ăcia se-
kund, a nast
Úpnie wycofa transakcjÚ.
Je
ĝeli w trakcie wymienionych dwudziestu sekund wystÈpi próba uaktualnienia tych samych
rekordów, ale z poziomu innego skryptu, na przyk
ïad za pomocÈ narzÚdzia SQL*Plus, bÚdÈ
one zablokowane, a
ĝ skrypt updateSleep.php zwolni blokadÚ naïoĝonÈ na wymienione rekordy.
Mora
ï z powyĝszego skryptu jest taki, ĝe jeĂli skrypt ma przeprowadziÊ dïugotrwajÈcÈ opera-
cj
Ú w bardzo obciÈĝonym Ărodowisku wielodostÚpnym, zawsze dobrym pomysïem bÚdzie za-
mkni
Úcie wszystkich aktywnych transakcji wewnÈtrz skryptu przed rozpoczÚciem przetwa-
rzania tej operacji.
Rozdzia
á 4. • Transakcje
129
Utracone uaktualnienia
Omówiony powy
ĝej przykïad pokazaï, jak kiepsko zaprojektowana aplikacja transakcyjna mo-
ĝe na dïugo zablokowaÊ zasoby bazy danych, co uniemoĝliwi wspóïbieĝnym transakcjom uzy-
skanie dost
Úpu do tych zasobów w rozsÈdnym czasie. Jednak warto zwróciÊ uwagÚ, ĝe zasto-
sowanie podej
Ăcia bez blokad podczas modyfikowania danych bazy danych w Ărodowisku
wielodost
Úpnym moĝe spowodowaÊ inny problem — utracone uaktualnienia. Aby zrozumieÊ,
na czym polega problem utraconych uaktualnie
ñ, naleĝy przeanalizowaÊ kroki, które interak-
tywna aplikacja zwykle przeprowadza podczas modyfikacji informacji przechowywanych
w bazie danych:
Q
Wybór danych z bazy danych.
Q
Wy
Ăwietlenie danych uĝytkownikowi.
Q
Oczekiwanie na dzia
ïanie uĝytkownika.
Q
Uaktualnienie danych w bazie danych.
Powinno by
Ê caïkiem oczywiste, ĝe kiedy w powyĝszym przykïadzie aplikacja czeka na dzia-
ïanie uĝytkownika, inny uĝytkownik moĝe spowodowaÊ zmianÚ danych. Dlatego teĝ jeĝeli
pierwszy u
ĝytkownik bÚdzie kontynuowaï proces uaktualniania danych, zmiany wprowadzone
przez drugiego u
ĝytkownika zostanÈ utracone.
Problem mo
ĝna lepiej zrozumieÊ na podstawie przykïadu. Warto wiÚc spojrzeÊ na skrypt
updateQuickForm.php, który implementuje powy
ĝsze kroki za pomocÈ pakietu PEAR o nazwie
HTML_QuickForm
. Po uruchomieniu skryptu wykona ona nast
ÚpujÈce kroki:
Q
Uaktualni dwa rekordy w tabeli
employees
.
Q
Wygeneruje formularz prosz
Ècy uĝytkownika o zatwierdzenie bÈdě wycofanie zmian.
Q
Zako
ñczy dziaïanie, wycofujÈc wprowadzone zmiany.
Za pomoc
È formularza wygenerowanego przez skrypt uĝytkownik moĝe albo zatwierdziÊ, albo
wycofa
Ê zmiany, a nastÚpnie musi kliknÈÊ przycisk WyĂlij. Gdy to zrobi, skrypt zostanie wy-
wo
ïany ponownie, tym razem wykonujÈc poniĝsze kroki:
Q
Uaktualnienie tych samych rekordów w tabeli
employees
.
Q
Zatwierdzenie lub wycofanie zmian w zale
ĝnoĂci od decyzji uĝytkownika.
Q
Zako
ñczenie dziaïania skryptu.
Dane wyj
Ăciowe wygenerowane przez skrypt zostaïy pokazane na rysunku 4.5.
Jednak przed uruchomieniem skryptu updateQuickForm.php nale
ĝy siÚ upewniÊ o zainstalo-
waniu pakietu
PEAR::HTML_QuickForm
. Poniewa
ĝ pakiet
HTML_QuickForm
jest uzale
ĝniony od
innego pakietu o nazwie
HTML_Common
, w pierwszej kolejno
Ăci naleĝy zainstalowaÊ drugi z wymie-
nionych. Zak
ïadajÈc, ĝe w systemie znajduje siÚ zainstalowany i skonfigurowany PEAR Installer,
w celu pobrania i instalacji pakietu
HTML_Common
mo
ĝna wydaÊ poniĝsze polecenie:
$ pear install HTML_Common
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
130
Rysunek 4.5. Dane wyj
Ăciowe skryptu updateQuickForm.php wyĂwietlone w przeglÈdarce internetowej
Po instalacji pakietu
HTML_Common
mo
ĝna przystÈpiÊ do pobrania i instalacji pakietu
HTML_Quick
´Form
. Nale
ĝy wydaÊ polecenie:
$ pear install HTML_QuickForm
Po przeprowadzeniu powy
ĝszych kroków moĝna uruchomiÊ skrypt updateQuickForm.php, które-
go kod
ěródïowy przedstawiono poniĝej:
<?php
// Plik: updateQuickForm.php.
require_once 'HTML/QuickForm.php';
if(!$dbConn = oci_connect('hr', 'hr', '//localhost/orcl')) {
$err = oci_error();
trigger_error('Nie moĝna nawiÈzaÊ poïÈczenia z bazÈ danych: '
. $err['message'], E_USER_ERROR);
};
$jobno = 'AD_VP';
$query = "
UPDATE employees
SET salary = salary*1.1
WHERE job_id=:jobid";
$stmt = oci_parse($dbConn,$query);
oci_bind_by_name($stmt, ':jobid', $jobno);
if (!oci_execute($stmt, OCI_DEFAULT)) {
$err = oci_error($stmt);
trigger_error('Wykonanie zapytania zakoñczyïo siÚ niepowodzeniem: '
.$err['message'], E_USER_ERROR);
};
print '<h2>Potwierdzenie uaktualnienia!</h2>';
$updrows = oci_num_rows($stmt);
Rozdzia
á 4. • Transakcje
131
$frm=new HTML_QuickForm('frm1', 'POST');
$frm->addElement('header','msg1','Uaktualniono '.$updrows. '
rekord(ów). Czy chcesz zatwierdziÊ zmiany?');
$grp[] =& HTML_QuickForm::createElement('radio', null, null,'zatwierdě', 'C');
$grp[] =& HTML_QuickForm::createElement('radio', null, null,'wycofaj', 'R');
$frm->addGroup($grp, 'trans');
$frm->setDefaults(array('trans' => 'C'));
$frm->addElement('submit','submit','WyĂlij');
if(isset($_POST['submit'])) {
if ($_POST['trans']=='C'){
oci_commit($dbConn);
print 'Transakcja zostaïa zatwierdzona.';
} elseif ($_POST['trans']=='R'){
oci_rollback($dbConn);
print 'Transakcja zostaïa wycofana.';
} else {
$frm->display();
}
} else {
$frm->display();
}
?>
Przygl
ÈdajÈc siÚ formularzowi pokazanemu na rysunku 4.5, Czytelnik moĝe pomyĂleÊ, ĝe po
uaktualnieniu dwóch rekordów tabeli
employees
skrypt czeka na dzia
ïanie uĝytkownika, pozo-
stawiaj
Èc aktywnÈ transakcjÚ. W rzeczywistoĂci dziaïa on zupeïnie inaczej.
Skrypt podczas pierwszego uruchamiania faktycznie powoduje uaktualnienie tabeli
employees
, ale
nast
Úpnie wycofuje transakcjÚ. W ten sposób moĝliwe jest zliczenie liczby rekordów modyfi-
kowanych przez polecenie
UPDATE
i podanie tej informacji u
ĝytkownikowi. Gdy ten wybierze
opcj
Ú zatwierdě albo wycofaj transakcji i kliknie przycisk WyĂlij, skrypt ponownie wykona tÚ
sam
È operacjÚ
UPDATE
. Tym razem w zale
ĝnoĂci od opcji wybranej przez uĝytkownika zmiany
wprowadzone przez polecenie
UPDATE
b
ÚdÈ zatwierdzone lub wycofane.
Zalet
È stosowania powyĝszej techniki jest to, ĝe przetwarzane rekordy nie zostajÈ zablokowa-
ne w czasie potrzebnym na podj
Úcie decyzji, który przycisk kliknÈÊ: zatwierdě lub wycofaj.
Dzi
Úki temu w tym samym czasie inne transakcje mogÈ przeprowadzaÊ operacje na tych re-
kordach. Jednak powoduje to powstanie kolejnego problemu — utraconych uaktualnie
ñ, jak
to zosta
ïo omówione we wczeĂniejszej czÚĂci podrozdziaïu.
Aby unikn
ÈÊ problemu utraconych uaktualnieñ, moĝna zastosowaÊ strategiÚ blokowania optymi-
stycznego, dzi
Úki czemu wartoĂci uaktualnianych pól nie zostanÈ zmienione, kiedy uĝytkow-
nik rozpocznie z nimi prac
Ú.
PHP i Oracle. Tworzenie aplikacji webowych: od przetwarzania danych po Ajaksa
132
Transakcje autonomiczne
Kontynuuj
Èc poprzedni przykïad: moĝe wystÈpiÊ potrzeba rejestrowania prób uaktualnienia
rekordów w tabeli
employees
. Aby umo
ĝliwiÊ takie zadanie, trzeba utworzyÊ tabelÚ przechowujÈcÈ
sprawdzane rekordy, jak równie
ĝ wyzwalacz
BEFORE UPDATE
, który obejmuje tabel
Ú
employees
.
Zadaniem wyzwalacza b
Údzie wstawianie rekordu do nowej tabeli za kaĝdym razem, gdy kto-
kolwiek spróbuje uaktualni
Ê rekord w tabeli
employees
.
W celu utworzenia wymienionej struktury danych nale
ĝy wydaÊ nastÚpujÈce polecenie SQL:
CONN usr/usr
CREATE TABLE emp_updates(
emp_id NUMBER(6),
job_id VARCHAR2(10),
timedate DATE);
CONN /AS SYSDBA
GRANT INSERT on usr.emp_updates TO hr;
CONN hr/hr
CREATE OR REPLACE TRIGGER emp_updates_trigger
BEFORE UPDATE
ON employees
FOR EACH ROW
BEGIN
INSERT INTO usr.emp_updates VALUES (:new.employee_id, :new.job_id,
SYSDATE);
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, 'W wyzwalaczu wystÈpiï bïÈd.');
END;
/
Po wykonaniu powy
ĝszego polecenia moĝna uruchomiÊ skrypt updateQuickForm.php, omó-
wiony w poprzednim podrozdziale, i sprawdzi
Ê, czy mechanizm kontroli dziaïa zgodnie z za-
ïoĝeniami. W formularzu wygenerowanym przez skrypt naleĝy zaznaczyÊ opcjÚ wycofaj, a na-
st
Úpnie kliknÈÊ przycisk WyĂlij. Teraz przy próbie wyĂwietlenia rekordów tabeli
emp_updates
w nast
ÚpujÈcy sposób:
CONN usr/usr;
SELECT * FROM emp_updates;
Czytelnik powinien zobaczy
Ê, ĝe tabela
emp_updates
wci
Èĝ nie zawiera ĝadnych rekordów:
no rows selected
Oznacza to,
ĝe po wycofaniu operacji
UPDATE
nast
Èpiïo wycofanie rekordu takĝe z tabeli kon-
trolnej. Takie zachowanie jest oczekiwane, poniewa
ĝ nie moĝna wycofaÊ pewnych efektów
transakcji — mo
ĝna albo zatwierdziÊ wszystkie efekty, albo wszystkie wycofaÊ.
Rozdzia
á 4. • Transakcje
133
Próba zatwierdzenia jedynie polecenia
INSERT
wykonywanego wewn
Ètrz wyzwalacza zakoñ-
czy si
Ú niepowodzeniem, poniewaĝ w wyzwalaczu niedozwolone jest uĝycie ĝadnych poleceñ
nadzoruj
Ècych transakcjÚ. Dlatego teĝ po próbie utworzenia wyzwalacza
emp_updates_trigger
w nast
ÚpujÈcy sposób (trzeba pamiÚtaÊ, aby byÊ poïÈczonym jako uĝytkownik
hr
— wykonu-
j
Èc polecenie
CONN hr/hr
— inaczej tabela
employees
nie b
Údzie widoczna i nie uda siÚ utwo-
rzy
Ê wyzwalacza):
CREATE OR REPLACE TRIGGER emp_updates_trigger
BEFORE UPDATE
ON employees
FOR EACH ROW
BEGIN
INSERT INTO usr.emp_updates VALUES (:new.employee_id,
:new.job_id, SYSDATE);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END;
/
uruchomienie skryptu updateQuickForm.php spowoduje wy
Ăwietlenie nastÚpujÈcych komu-
nikatów b
ïÚdów:
Warning: oci_execute()[function.oci-execute]:ORA-04092: cannot
ROLLBACK in a trigger ORA-06512: at "HR.EMP_UPDATES_TRIGGER", line
6 ORA-04092: cannot COMMIT in a trigger ORA-04088: error during
execution of trigger 'HR.EMP_UPDATES_TRIGGER'
Fatal error: Query failed: ORA-04092: cannot ROLLBACK in a trigger
ORA-06512: at "HR.EMP_UPDATES_TRIGGER", line 6 ORA-04092: cannot
COMMIT in a trigger ORA-04088: error during execution of trigger 'HR.
EMP_UPDATES_TRIGGER
Powy
ĝsze komunikaty bïÚdów zostanÈ wyĂwietlone tylko wtedy, gdy w pliku konfiguracyjnym
php.ini
parametr
display_errors ma ustawionÈ wartoĂÊ On.
Jednym ze sposobów rozwi
Èzania powyĝszego problemu jest uĝycie transakcji autonomicznej.
Transakcja autonomiczna jest transakcj
È wewnÈtrz innej transakcji. Poniewaĝ jest caïkowicie niezaleĝna
od wywo
ïujÈcej jÈ transakcji, pozwala na przeprowadzanie operacji SQL, a nastÚpnie zatwierdzanie ich
lub wycofywanie bez zatwierdzania lub wycofywania wywo
ïujÈcej jÈ transakcji.
Zastosowanie transakcji autonomicznej w omawianym przyk
ïadzie pozwoli na zatwierdzenie
polecenia
INSERT
wykonywanego przez wyzwalacz
emp_updates_trigger
niezale
ĝnie od transakcji