informatyka php i oracle tworzenie aplikacji webowych od przetwarzania danych po ajaksa yuli vasiliev ebook

background image

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!

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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.

background image

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

background image

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.

background image

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Ú.

background image

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%.

background image

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);

background image

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.

background image

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

background image

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

background image

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

.

background image

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

background image

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

background image

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

background image

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;

background image

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.

background image

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:

background image

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);

background image

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

background image

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.

background image

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

background image

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-

ï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()

background image

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'].'&nbsp;'.$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);

background image

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.

background image

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.

background image

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ñ.

background image

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.

background image

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

background image

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);

background image

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

Ú.

background image

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Ê.

background image

Czytaj dalej...

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


Wyszukiwarka

Podobne podstrony:
PHP i Oracle Tworzenie aplikacji webowych od przetwarzania danych po Ajaksa
PHP i Oracle Tworzenie aplikacji webowych od przetwarzania danych po Ajaksa
informatyka php web 2 0 tworzenie aplikacji typu mashup shu wai chow ebook
PHP Web 2 0 Tworzenie aplikacji typu mashup phpw2m
PHP i MySQL Tworzenie aplikacji WWW phmsap
PHP i MySQL Tworzenie aplikacji WWW
PHP i MySQL Tworzenie aplikacji WWW phmsap
informatyka php nuke tworzenie witryn www douglas paterson ebook

więcej podobnych podstron