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:

Czym s

È transakcje i kiedy programista moĝe chcieÊ je stosowaÊ?

Jak przeprowadza

Ê transakcje za pomocÈ PHP i Oracle?

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:

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

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

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:

Zapewnienie poprawno

Ăci i spójnoĂci danych.

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:

Tworzy transakcj

Ú.

Uaktualnia niektóre rekordy w tabeli 

employees

.

Wstrzymuje na 20 sekund wykonywanie skryptu.

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:

Wybór danych z bazy danych.

Wy

Ăwietlenie danych uĝytkownikowi.

Oczekiwanie na dzia

ïanie uĝytkownika.

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:

Uaktualni dwa rekordy w tabeli 

employees

.

Wygeneruje formularz prosz

Ècy uĝytkownika o zatwierdzenie bÈdě wycofanie zmian.

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:

Uaktualnienie tych samych rekordów w tabeli 

employees

.

Zatwierdzenie lub wycofanie zmian w zale

ĝnoĂci od decyzji uĝytkownika.

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