Lucrul cu Baze de Date la Distanta Sub Limbajul Java
Lucrul cu baze de date la distanță sub limbajul JAVA
Cuprins
INTRODUCERE
CAPITOLUL 1. “Limbajul JAVA
1.1 Tipuri de aplicații JAVA
1.1.1 Aplicații de sine stătătoare
1.1.2 Aplicații client
1.1.3 Aplicații server
1.2 Fundamentele limbajului JAVA
1.2.1 Fișiere sursă
1.2.2 Atomii lexicali
1.2.3 Caractere Unicode
1.2.4 Traduceri lexicale
1.2.5 Tipuri de date
1.2.6 Expresii și Operatori
1.2.7 Variabile
1.2.8 Conversii
1.2.9 Contextele de conversie
1.3 Clase, interfețe, tablouri, fișiere, fluxuri de date
1.3.1 Clase
1.3.2 Modificatorii unei clase
1.3.3 Implementarea interfețelor
1.3.4 Declarațiile constructorilor
1.3.5 Distrugerea obiectelor și eliberarea memoriei
1.3.6 Divizarea unei aplicații în fișiere
1.3.7 Interfețe
1.3.8 Tablouri
1.4 Interfețe grafice
1.4.1 Appleturi
1.4.2 Servleturi
1.4.3 Componente
CAPITOLUL 2. “ACCESUL LA BAZE DE DATE PRIN JDBC
2.1 JDBC
2.1.1 Definiții
2.1.2 Clasificarea driverelor
2.2 Limbajul SQL
2.3 Accesarea unei baze de date prin JDBC
2.3.1 Înregistrarea driverului
2.3.2 Stabilirea unei conexiuni cu baza de date
2.3.3 Execuția unei instrucțiuni SQL
2.3.4 Procesarea rezultatelor
2.3.5 Închiderea unei conexiuni la o bază de date
CAPITOLUL 3. “IMPLEMENTARE JAVA LA DISTANȚĂ”
3.1 Programarea rețelelor de calculatoare
3.1.1 Adresele, porturile și socketurile
3.1.2 Clasa Socket și ServerSocket
3.1.2.1 Clasa ServerSocket
3.1.2.2 Clasa Socket
3.1.3 Clasa DatagramPacket și Datagram Socket
3.1.3.1 Clasa DatagramPacket
3.1.3.2 Clasa DatagramSocket
3.2 Apelul metodelor la distanță
3.2.1 Definiții RMI
3.2.2 Declarare interfețe
CAPITOLUL 4. “ APLICAȚIA PRACTICĂ”
4.1 Informații generale
4.2 Exemple crearea aplicație
4.3 Exemple rularea aplicației
4.4 Rularea aplicației
Bibliografie
INTRODUCERE ÎN LIMBAJUL JAVA
JAVA este un limbaj de programare orientat pe obiecte, conceput în mod special pentru proiectarea aplicațiilor pentru internet. Acest limbaj, în secolul 21, este folosit cu success și pentru programarea aplicațiilor destinate retelelor, și bineînțeles, este unul din cele mai folosite limbaje de programare deoarece este gratuit și în mod continuu îmbogățit și îmbunătățit.
Începutul acestuia este în toamna anului 1991, când compania Sun Microsystems decide să ajungă în fruntea pieței în ceea ce privește produsele electronice comerciale, prin finanțarea unui proiect numit “GREEN” condus de James Gosling alături de alte persoane importante cum ar fi Bill Joy și Patrick Naughton. Acest proiect a avut ca obiectiv principal proiectarea unui limbaj de programare flexibil și portabil, mult mai simplu de folosit decât limbajele deja existente. Succesul acestuia constă în abilitatea de a lucra pe platforme multiple.
Fig. 1.1 James Gosling, “părintele” limbajului Java
În 1995, echipa proiectului “GREEN” finalizează specificațiile limbajului Java și semnează contracte cu mari companii precum IBM, Microsoft, Silicon Grahpics, Adobe și Netscape.
Caracteristicile limbajului JAVA [2]:
• limbaj compilat și interpretat. Limbaj compilat înseamnă de fapt traducerea acelui program într-un cod înțeles de calculator.
Limbaj interpretat reprezintă faptul că instrucțiunile unui program sunt procesate linie cu linie. Java este atât un limbaj compilat cât și interpretat.
• limbaj independent de platformă. Atunci când se instalează limbajul Java, automat se creează o mașină virtuală Java care traduce instrucțiunile unui program în instructiuni-masina pentru platform respectivă.
• limbaj orientat obiecte. Este una din cele mai importante caracteristici ale limbajului Java.
Java pune în evidență toate aspectele legate de programarea orientată pe obiecte: obiecte, trimitere de parametrii, încapsulare, clase, biblioteci, moștenire, modificatori de acces.
• limbaj concurent. Această caracteristică înseamnă capacitatea de a se executa mai multe secvențe de cod în același timp.
În Java, o secvență de cod se numește fir de execuție. Se pot executa astfel, mai multe fire de execuție (animație unei imagini, transmiterea unei melodii către placă de sunet, comunicarea cu un server ).
• limbaj simplu. Este un limbaj mult mai simplu de implementat de cât C++, deoarece aici nu există pointeri, alocarea și dealocarea memoriei se face automat, nu apare conceptul de moștenire multiplă, șirurile fiind încapsulate într-o structură clasă.
• limbaj distribuit. Permite utilizarea obiectelor locale și de la distanță.
Practic, Java oferă posibilitatea dezvoltării de aplicații pentru internet capabile să ruleze pe mai multe platform.
Poate fi utilizat în aplicații de rețea, deoarece respecta protocoalele de rețea (FTP, HTTP, SOAP).
• limbaj performant. Java poate executa un cod la fel de repede ca și cum l-ar compila. De asemenea, folosește mai multe fire de execuție, de aceea este un limbaj performant.
• limbaj dinamic și robust. Robust deoarece nu se mai utilizează pointeri, care generau multe erori în C++.
Dinamic deoarece întârzie legarea dinamică a claselor până în momentul execuției, astfel evitând erorile de alocare.
• limbaj sigur. Programele realizare în limbaj Java nu pot accesa memoria heap și stack deoarece nu folosește pointeri.
Înainte de execuție, Java verifica acel cod. Astfel putem spune că e un limbaj sigur.
Fig 1.2 Logo-ul JAVA
Există 3 platforme Java furnizate de Sun Microsystems:
– Java Platform, Micro Edition (Java ME) — pentru hardware cu resurse limitate, cum ar un PDA sau telefoanele mobile;
– Java Platform, Standard Edition (Java SE) — pentru sisteme de tip workstation, este ceea ce se găsește pe PC-uri;
– Java Platform, Enterprise Edition (Java EE) — pentru sisteme de calcul puternice, eventual distribuite;
Istoric al versiunilor:
– 23 ianuarie 1996, JDK 1.0 – versiunea inițială
– 19 februarie 1997, JDK 1.1
– 8 decembrie 1998, J2SE 1.2
– 8 mai 2000, J2SE 1.3
– 6 februarie 2002, J2SE 1.4
– 30 septembrie 2004, J2SE 5.0
– 11 decembrie 2006, Java SE 6
– 14 februarie 2012, Java SE 7
CAPITOLUL 1
Limbajul JAVA
1.1 Tipuri de aplicații JAVA
Există mai multe tipuri de aplicații Java:
– Aplicații de sine stătătoare (stand-alone)
– Aplicații care se execută pe partea de client (appleturi)
– Aplicații care se execută pe partea de server (servleturi)
1.1.1 Aplicații de sine stătătoare
O aplicație de sine stătătoare este un program scris în Java, un cod Java care poate fi executat fără a recurge la un browser. Acest tip de aplicatie incadreaza in clasa o functie main() :
public static void main (Strâng[] args)
Aplicații Java de sine stătătoare se pot împărți în:
Aplicații de consola, rulează la cmd (linia de comandă) pe un calculator personal ce utilizează sistemul de operare Windows;
Aplicatii-fereastra, care lucrează cu interfețe grafice (Graphical User Interface – GUI)
1.1.2 Aplicații care se execută pe partea de client
Astfel de aplicații sunt încărcate pe un server aflat la distanță și executate pe calculatorul client de anumite programe special concepute în acest sens (un exemplu ar fi browser-ele: acestea executa appletul deoarece au implementată mașina virtuală JAVA).
1.1.3 Aplicații care se execută pe partea de server
O astfel de aplicație, așa cum se poate înțelege din denumire, se execută de către server ca urmare a unei cereri primite de acesta, iar rezultatul va fi trimis aplicației solicitante. Un exemplu de astfel de aplicații sunt servleturile care sunt similare apleturilor prin aceea că sunt extensii dinamice (adică se lansează în execuție când aplicația rulează deja) ale aplicațiilor. În loc de a lucra în browsere, servleturile lucrează împreună cu serverele java, configurând, extinzând sau modificând funcțiunile serverului.
Bineînțeles, există și programe mult mai complexe ce au nevoie de o cantitate foarte mare de informații, astfel recurganduse la utilizarea unei baze de date. Conexiunea la o bază de date se realizează prin intermediul JDBC (Java Database Connection). JDBC este o aplicație dezvoltat de Sun care definește o interfață uniformă pentru accesarea diferitelor baze de date. JDBC face parte din platforma JAVA, fiind inclusa, bineintels, in pachetul JDK. Una din cele mai importantr funcții a JDBC-ului este posibilitatea lucrului cu instrucțiuni SQL și procesarea rezultatelor într-o manieră independent și consitenta de bazele de date. JDBC furnizează acces orientat pe obiecte la bazele de date prin definirea de clase și interfețe care reprezintă obiecte, cum ar fi conexiuni la baze de date, instrucțiuni SQL, multimi-rezultat, obiecte binare și șiruri de caractere de dimensiuni mari, driver de baze de date, manageri de driver.
Toate aceste tipuri de aplicații se pot conecta la o bază de date sau la anumite servere din rețea pentru prelucrarea datelor în diferite formate grafice, video sau de tip text.
În general, aplicațiile folosite cel mai des rulează în rețele de calculatoare folosind metodele de conectare la distanță (RMI – Remote Method Invocation, ce se folosește pentru comunicarea între procese care se execută pe calculatoare diferite).
1.2 Fundamentele limbajului JAVA
1.2.1 Fișiere sursă
Sunt acele fișiere cu extensia .java. Acestea conțin o singură clasă publică, aceasta având același nume cu numele fișierului, și pot conține mai multe clase nepublice. Aceste fișiere conțin de obicei 3 elemente importante: declarații de pachete (numite și biblioteci Java), instrucțiuni de includere de clase și definiții de clase.
Un pachet Java este format din mai multe clase aflate în același fișier (exemplu: java.io, java.awt, java.lang, java.applet).Pentru a declara un pachet se folosește termenul “import” alături de numele acelui pachet.
1.2.2 Atomii lexicali
După scrierea codului sursa unui program, este necesar înainte de rulare, compilarea acelui cod. Există mai multe etape de compilare a unui program Java, primul dintre acestea fiind analizarea lexicală a codului sursa.
Analizarea lexicală reprezintă parcurgerea întregului cod și identificarea cuvintelor acceptate de sintaxa Java. Aceste cuvinte se numesc atomi lexicali (tokens). Printre atomii lexicali se număra identificatorii, cuvintele rezervate, literalii, separatorii și operatorii limbajului Java.
1.2.3 Caractere Unicode
Limbajul Java folosește în mod nativ setul de caractere Unicode. Acesta este un standard internațional care înlocuiește vechiul set de caractere ASCII și care folosește pentru reprezentarea caracterelor 2 octeți, ceea ce înseamnă că se pot reprezenta 65536 de semne, spre deosebire de ASCII, unde era posibilă reprezentarea a doar 256 de caractere. Primele 256 de caractere Unicode corespund codului ASCII, referirea la celelalte facanduse prin \uxxxx, unde xxxx reprezintă codul caracterului.
Limbajul JAVA face diferențiere între caractere mari și caractere mici (este case-sensitive). De asemenea, acesta utilizează două feluri de reprezentări text
Setul de caractere Unicode – pentru reprezentarea internă a caracterelor și a șirurilor de caractere;
Codări UTF – pentru intrări și ieșiri
1.2.4 Traduceri lexicale
Un șir de elemente Unicode poate fi tradus într-o secvență de atomi lexicali Java astfel:
Traducerea secventelor Unicode din fluxul caracterelor Unicode în caracterul Unicode corespunator.
Traducerea fluxului Unicode de la pasul i într-un flux de caractere de intrare și terminatori de linie.
Traducerea fluxului caracterelor de intrare și a terminatorilor de linie de la pasul îi în secvențe de elemente de intrare Java care comprima atomii lexicali, ce devin simboli terminali pentru gramatica sintactică Java.
1.2.5 Tipuri de date
Limbajul Java este unul destul de puternici tipizat, deoarece orice variabilă sau expresie are un tip cunoscut în momentul compilării. Acele tipuri de date limitează valorile pe care le poate memora o expresie și bineînțeles, limitează și operațiile suportate de aceste valori.
În Java, există 3 categorii de tali (tokens). Printre atomii lexicali se număra identificatorii, cuvintele rezervate, literalii, separatorii și operatorii limbajului Java.
1.2.3 Caractere Unicode
Limbajul Java folosește în mod nativ setul de caractere Unicode. Acesta este un standard internațional care înlocuiește vechiul set de caractere ASCII și care folosește pentru reprezentarea caracterelor 2 octeți, ceea ce înseamnă că se pot reprezenta 65536 de semne, spre deosebire de ASCII, unde era posibilă reprezentarea a doar 256 de caractere. Primele 256 de caractere Unicode corespund codului ASCII, referirea la celelalte facanduse prin \uxxxx, unde xxxx reprezintă codul caracterului.
Limbajul JAVA face diferențiere între caractere mari și caractere mici (este case-sensitive). De asemenea, acesta utilizează două feluri de reprezentări text
Setul de caractere Unicode – pentru reprezentarea internă a caracterelor și a șirurilor de caractere;
Codări UTF – pentru intrări și ieșiri
1.2.4 Traduceri lexicale
Un șir de elemente Unicode poate fi tradus într-o secvență de atomi lexicali Java astfel:
Traducerea secventelor Unicode din fluxul caracterelor Unicode în caracterul Unicode corespunator.
Traducerea fluxului Unicode de la pasul i într-un flux de caractere de intrare și terminatori de linie.
Traducerea fluxului caracterelor de intrare și a terminatorilor de linie de la pasul îi în secvențe de elemente de intrare Java care comprima atomii lexicali, ce devin simboli terminali pentru gramatica sintactică Java.
1.2.5 Tipuri de date
Limbajul Java este unul destul de puternici tipizat, deoarece orice variabilă sau expresie are un tip cunoscut în momentul compilării. Acele tipuri de date limitează valorile pe care le poate memora o expresie și bineînțeles, limitează și operațiile suportate de aceste valori.
În Java, există 3 categorii de tipuri de date: primitive, referință și null.
Primitive : – numerice : – integrale (byte, short, int, long, char);
– virgulă flotantă (float , double);
– boolean;
Referință: – clasă;
– interfața;
– tablou;
Un obiect în Java este o instanță creată dinamic a unui tip clasă sau a unui tablou creat dinamic. Valorile unui tip referință sunt referințe la obiecte. Toate obiectele, inclusiv tabourile, suportă metode ale clasei Object. Literalii String sunt reprezentați de obiecte String.
Tipul null este special, aceasta neavând nume. Este deci imposibil să declarăm o variabilă de tip null sau să o convertim la o expresie de tip null. Referință null este o valoare posibilă pentru o expresie de tip null și poate fi convertită la orice tip referință. Numele tipurilor sunt folosite în declarații, conversii, în expresii de creare a instanței unei clase, în expresii de creare a tablourilor și în expresii folosite de operatorul instanceof.
Fig. 1.2.1 – Tipurile de date din Java
Orice valoare a oricărui tip integral poate fi convertită la sau către un tip numeric. În schimb, nu există conversii între tipurile integrale și tipul boolean.
1.2.6 Expresii și operatori[2]
Un program realizat în Java se bazează în primul rând pe evaluarea expresiilor. Aceasta evaluarea se poate realiza prin efectele laterale sau pentru valorile returnate de acestea sau pentru afectarea secvenței de execuție în instrucțiuni. Prin efectele laterale se înțelege asignarea de valori variabilelor în timpul evaluării expresiilor.
În momentul în care se evaluează o expresia dintr-un cod scris în Java, rezultatul poate constă într-o valoare, variabile sau nimic (void). Deoarece expresiile pot conține asignări, operatori de incrementare/decrementare și apeluri de metode, evaluarea unei expresii poate produce efecte laterale.
Orice expresie trebuie declarată într-o clasă sau interfață, într-un constructor sau într-o metodă. Este nevoie ca o expresie ce reprezintă o valoare să fie cunoscută încă de la compilare.
O expresie primară este un literal, accesul la câmpuri și tablouri, apelurile de metode, expresiile parasintetizate. Un operator cu n argumente este o funcție cu n argumente care se mai numesc și operanzi. Utilizând o manieră recursivă de descriere, o expresie compusă poate fi o expresie primară, o expresie care folosește operatori unari, binari, ternari sau o expresie de conversie. Ca un exemplu, operatorul + este unar și binar, operatorul condițional ? : ternar.
În limbajul Java există mai multe tipuri de operatori ce lucrează cu operanzi de tipuri integrale:
Operatorii de comparație (<,>,=,<=,>=,==,!=)
Operatorii numerici (care întorc o valoare de tip int sau long: +,-,*,/,%,++,–,&,|,^)
Operatorul condițional ?
Operatorul de conversie explicită, care poate converi o valoare integrală la o valoare de un tip numeric specificat.
Operatorul de concatenare a șirurilor, care în cazul unui operând de tip String și al unuia de tip integral, va face conversia celui integral la String, creând în final un nou obiect String egal cu concatenarea acestora.
În cazul operatorilor de comparație, tipul fiecărui operând trebuie să fie tip numeric primitiv, altfel se obține eroare la compilare. Operatorul <= reprezintă mai mic sau egal, > este mai mare, >= mai mare sau egal, == egal, != diferit. Ultimii 2 operatori sunt operatori de egalitate, deosebinduse de ceilalți printr-o prioritate mai mică.
Operatorii +,- se mai numesc și operatori de adunare și se aplică operanzilor numerici de același tip, producând suma, respective diferența acestora.
Operatorii *,/ și % se numesc operatori multiplicativ și se aplică operanzilor numerici de același tip, executând produsul, raportul și restul împărțirii acestora. Tipul fiecărui operând al unui operator multiplicativ trebuie să aparțină unui tip numeric primitive, astfel obținem o eroare de compilare.
Referitor la operatorii de incrementare/decrementare (++,–), aceștia pot apărea în formă postfixata sau prefixata. Rezultatul expresiei unare trebuie să fie o variabilă a unui tip numeric, altfel apare o eroare de compilare. Tipul expresiei de incrementare/decrementare este identic cu tipul variabilei. În cazul operatorilor postfixati, a++ sau a–, se va aduna/scădea cu 1. În cazul operatorilor prefixați, ++a, –a, se va aduna/scădea cu 1.
Operatorii de deplasare, << deplasare stânga, >> deplasare dreaptă, sunt stang-asociativi, operandul din dreapta specificând numărul de poziții cu care se deplasează. Tipul fiecare operând al unui operator de deplasare trebuie să fie de tip integral, altfel apare o eroare de compilare.
Operatorul unar de complementariere “~” trebuie să aibă ca și operând un integral, altfel apare o eroare de compilare. Regulile de complementariere aplicate alfabetului {0 și 1} sunt ~0 = 1 și ~1 = 0.
Operatorii pe biți “și – &”, “sau inclusive – |”, “sau exclusive – ^”. Acești operatori sunt stâng asociativi dar au precedente diferite, operatorul & având cea mai mare prioritate și operatorul | având cea mai mică prioritate. În plus, acești operatori sunt comutativi dacă expresiile operanzilor nu au efecte laterale. Când ambii operanzi sunt de tip integral, atunci aceștia se converstesc la int sau long, după caz.
Operatorul condițional ? apare în expresii de forma <exprs. booleana> ? <expres> : <expres>, unde se evaluează prima dată expresia booleana; dacă aceasta e adevărată, se evaluează operandul al doilea a cărui valoarea se returnează; dacă nu e adevărată expresia booleană, se returnează valoarea celui de-al treilea operând.
Continuăm cu operatorul de concatenare a șirurilor. Orice tip Java poate fi convertit la tipul String. O valoare x a tipului primitv T este mai întâi convertita la o valoare referință apelând la clasele corespunzătoare tipului T având ca parametru pe x:
Pentru T = char, se folosește new Character(x)
Pentru T=byte,short,int se folosește new Interger(x)
Pentru T=long, se folosește new Long(x)
Pentru obiectele primitive, implementarea poate optimiza crearea unui obiect wrapper prin convertirea directă a unui tip primitiv la un obiect de tip String.
Mai departe vom prezenta operatorii care lucrează cu operanzi de tip virgulă flotantă :
Operatorii de comparație, care întorc o valoare de tip boolean: <,<=,>,>=, ==,!=.
Operatorii numerici, care întorc o valoare de tip float sau double: +,-,*,/,%,++,–.
Operatorul condițional ? :
Operatorul de conversie explicită, care poate converti o valoare în virgule mobila la o valoare de un tip numeric specificat.
Operatorul de concatenare a șirurilor, care în cazul unui operând de tip String și al unuia de tip în virgule flotanta, va face coversia operandului în virgulă mobila la String, creând astfel un nou obiect String egal cu concatenarea acestora.
În mod normal, operația dintre 2 operatori este efectuată folosind o precizie de 32 biți, deoarece se convertesc operanzii la tipul float, rezultatul fiind tot de tip float. Dacă un operator numeric în virgule mobile are un operând de tip double, atunci operația este efectuată folosind aritmetica în virgulă mobilă și 64 biți și convertind celălalt operând la tipul double, valoarea returnată fiind tot de tip double.
Operatorii de comparație au următoarele rezultate:
-daca unul dintre operanzi este NaN, atunci rezultatul este false;
-toate valorile diferite de NaN sunt ordonate, cu minus infinit mai mic decât toate valorile finite și plus infinit mai mare decât toate valorile finite;
-zero negativ și pozitiv sunt considerate egale.
Operatorii +,-; adunarea în virgule flotanta nu este asociativa, rezultatul determinându-se după următoarele reguli:
-daca unul dintre operanzi este NaN, atunci rezultatul este NaN;
-suma a doua infinități de semn opus este NaN;
-suma a doua infinități de același semn este respectivul infinit;
-suma dintre un infinit și o valoarea finite este respectivul infinit;
-suma a doua zerouri de semn opus este zero pozitiv;
-suma a doua zerouri de semn opus este zero pozitiv;
-suma a doua zerouri de același semn este zero de acel semn;
-suma dintre zero și o valoare finite este acea valoare finită;
-suma dintre două numere finite opuse ca semn, dar egale în valoare absolută, este zero pozitiv;
-in restul cazurilor, se calculează suma. Dacă suma a două numere este prea mare, atunci se întoarce infinit (spre deosebire de numerele întregi). Dacă suma a două numere este prea mică pentru reprezentare, se întoarce zero apropiat ca semn. Astfel, suma este rotunjită către cea mai apropiată valoare reprezentabilă.
La scădere, se procedează ca la adunare cu opusul numărului.
Multiplicarea în virgulă flotantă (înmulțirea *) nu este asociativă . Rezultatul unei înmulțiri în virgulă flotantă este dat de regulile:
-daca unul dintre operanzi este NaN, atunci rezultatul este NaN;
-daca rezultatul nu este NaN, atunci semnul rezultatului este pozitiv dacă ambii operatori au același semn sau negativ dacă operanzii au semne diferite;
-inmultirea dintre infinit și zero este NaN;
-inmultirea dintre infinit și o valoare finită este infinit cu semnul determinat cu regulă semnelor;
-in celelalte cazuri, se calculează produsul celor doi operanzi. Dacă produsul este prea mare, atunci se întoarce infinit. Dacă produsul este prea mic, atunci se întoarce zero. Astfel, produsul este rotunjit către cea mai apropiată valoare reprezentabilă.
Împărțirea în virgulă flotantă nu este asociativă. Aici avem următoarele reguli:
-daca unul dintre operanzi este NaN, atunci rezultatul este NaN;
-daca rezultatul nu este NaN, atunci semnul rezultatului este pozitivi dacă ambii operanzi au același semn sau negativ dacă operanzii au semne diferite;
-impartirea dintre infinit și infinit este NaN;
-impartirea dintre infinit și o valoare finită este infinit cu semnul determinat de regulă semnelor;
-impartirea dintre o valoare finită și infinit este 0 cu semnul determinat de regulă semnelor;
-impartirea dintre o valoare finită și zero este semnalizata prin aruncarea la execuție a unei excepții;
-in celelalte cazuri, se calculează raportul celor doi operanzi. Dacă raportul este prea mare, atunci se întoarce infinit. Dacă raportul este prea mic, atunci se întoarce zero. Altfel, raportul este rotunjit către cea mai apropiată valoare reprezentabilă.
Continuăm cu prezentarea operatorului % pentru operanzi în virgulă flotantă. Prin definiție a%b = a-b*[a/b], [x] reprezentând partea întreagă inferioară a lui x. Rezultatul unei operații % este determinat de regulile:
-daca unul dintre operanzi este NaN, atunci rezultatul este NaN;
-daca rezultatul nu este NaN, atunci semnul rezultatului este egal cu semnul operandului care se împarte.
-daca operandul care se împarte este infinit sau operandul la care se împarte este zero, atunci rezultatul este NaN;
-daca operandul care împarte este finit și operandul la care se împarte este infinit, atunci rezultatul este operandul care se împarte;
-daca operandul care se împarte este zero și operandul la care se împarte este finit, atunci rezultatul este operandul care se împarte;
-in celelalte cazuri, se folosește formula de mai sus.
Operatorul de concatenare șiruri este similar cu operatorul + pentru tipurie integrale, tipurile float și double pot fi convertite la tipul String. O valoare x a tipului primitiv T este mai întâi convertita la o valoare referință apelând la clasele corespunzătoare tipului T având ca parametru pe x:
Pentru T = float se utilizează new Float(x);
Pentru T = double se utilizează new Double(x);
Pentru obiecte primitive, implementarea poate optimiza crearea unui obiect wrapper (T) prin convertirea directă a unui tip primitiv la un obiecte de tip String. Citirea unui număr în virgulă flotantă se realizează în mod analog ca la tipurile integrale. Similară cu metoda parseInt() este metoda parseFloat() pentru numere în virgulă flotantă.
În continuare vom prezenta operatorii care lucrează cu operanzi de tip boolean:
Operatorii relaționali == și !=;
Operatorul de complement logic !;
Operatorii logici &, ^,| ;
Operatorii condiționali &&, || ;
Operatorul condițional ? :
Operatorul de concatenare șiruri care în cazul unui operând de tip String și a unuia de tip boolean va face conversia operandului boolean la String creând astfel un nou obiect String egal cu concatenarea acestora.
Expresiile boolene determina controlul fluxului în instrucțiunile if, while, do, for, precum și pe cel al operatorului condițional. Primul operând al operatorului condițional poate fi doar expresia booleana.
Operatorii de egalitate booleeni sunt asociativi. Rezultatul evaluării expresiei a==b este true dacă operanzii a și b sunt egali. În caz contrar, rezultatul e false. Rezultatul evaluării expresiei a != b este true dacă operanzii a și b sunt diferiți. Alfel, e false. Tipul expresiei operând al operatorului ! trebuie să fie boolean altfel apare o eroare de compilare și tipul întors de valoarea expresiei !a este true, dacă operandul a se evaluează la false și false dacă operatorul a se evaluează la true.
În cazul operatorilor logici în care operanzii sunt de tip boolean, lucrurile sunt foarte simple. Tipul întors de expresiile a&b, a^b,a|b este boolean. Semantic, avem:
-a&b este true, dacă a și b sunt true, altfel e false;
-a^b este true, dacă a și b sunt diferiți; altfel e false;
-a|b este false, dacă a și b sunt false; altfel e true;
Operatorii && și || sunt variante performante ale operatorilor & și |. Astfel, operatorul && este echivalent cu & cu deosebirea că evaluează operandul drept dacă valoarea operandului din stânga este true. Este stâng asociativ și fiecare operând a lui && trebuie să fie boolean, altfel apare o eroare de compilare. Tipul returnat de expresia dată de operatorul condițional && este boolean. Dacă valoarea operandului a din expresia a&&b este false, atunci operandul b nu se mai evaluează, valoarea rezultatului fiind false.
Similar, operatorul || este echivalt cu |, cu deosebirea cu se evaluează operandul drept dacă și numai dacă valoarea operandului sin stânga este false. Este stang-asociativ și fiecare operând a lui || trebuie să fie de tip boolean, altfel apare o eroare de compilare. Tipul returnat este boolean. Dacă valoarea operandului a din expresia a||b este true, atunci operandul b nu se mai evaluează, valoarea întoarsă fiind true.
1.2.7 Variabile
O variabilă este o locație de memorie care are un tip asociat.Conține o valoare compatibilă cu tipul variabilei. Valoarea unei variabile se poate schimba în urma unei asignări sau a folosirii operatorilor ++ și –. Compatibilitatea valorilor implicite și toate asignările unei variabile sunt verificate în timpul compilării.
Indiferent de tipul lor, variabilele trebuiesc declarate și inițializate pentru a fi folosite:
– declararea variabilelor: Tip numeVariabila;
– inițializarea variabilelor: Tip numeVariabila = valoare;
– declararea constantelor: final Tip numeVariabile;
În Java există 7 tipuri de variabile:
a. O variabilă de clasa reprezintă orice câmp declarat cu specificatorul static, aceasta fiind creată în momentul creării clasei, și distrusă în momentul finalizării clasei.
b. Din punct de vedere tehnic, obiectele își stochează stările în câmpuri "non-statice", care sunt câmpuri declarate fără a conține cuvântul cheie static. Câmpurile non-statice mai sunt cunoscute și sub denumirea de variabile de instanță deoarece valorile lor sunt unice pentru fiecare instanță a unei clase. Dacă clasa C are un atribut A care este o variabilă instanță, atunci se va crea și inițializa cu valoare implicită o nouă instanță a variabile a corespunzătoare fiecărui obiect nou creat al clasei C sau oricărei subclase a clasei C. Variabilă instanță își încetează existena când obiectul corespunzător atributului nu mai este referentiat.
c. Componentele unui tablou sunt acele variabile inițializate cu anumite valori implicite în momentul creării unui obiect tablou. Componentele tabloului își încetează existenta când tabloul nu mai este referentiat.
d. Variabilă parametru a unei metode reprezintă acea valoare trimisă către o metodă prin intermediul unui argument. În momentul când se apelează acea metodă se creează o variabilă ce primește valoarea argumentului corespunzător apelului metodei.
e. Variabilă parametru a unui constructor reprezintă valoarea argumentului trimis către un constructor.Pentru fiecare parametru din declarație constructorului, se creează o variabilă parametru de fiecare dată când se apelează implicit sau explicit constructorul. Nouă variabilă este inițializata cu valoarea argumentului corespunzător al apelului constructorului. Variabilă parametru a constructorului își încetează existenta când se termină execuția blocului asociat clauzei catch respective.
f.Variabilă locală este declarată de o instructiunie de declarare a variabile locale. De fiecare dată când fluxul de control intra într-un bloc sau instructiunie for, se creează o nouă variabilă pentru fiecare variabile locală declarată. Spre deosebire de celelalte categorii de variabile, variabilele locale nu sunt inițializate implicit, deci se va depisa eroare la compilare dacă se încearcă accesare unei variabile locale care nu a fost inițializata explicit. Varibila locală își încetează existena când se termină execuția blocului sau a instrucțiunii for asociate.
1.2.8 Conversii
Orice expresie Java are un tip ce poate fi determinat din structura expresiei și a tipurilor literalilor, variabilelor și metodelor menționate în expresie. Sunt cazuri când anumite expresii apar într-un context în care tipul expresiei nu este potrivit. În anumite cazuri, contextul poate fi capabil să accepte un tip apropiat de tipul expresiei. Astfel, în loc să fie obligatorie scrierea tipului dorit, Java face o conversie implicită a tipului expresiei la un tip mai acceptabil pentru contextul respectiv.
În fiecare context, sunt permise doar câteva conversii specifice:
Conversii identice;
Conversii primitive implicite
Conversii primitive explicite
Conversii de referință implicite
Conversii de referință explicite
Conversii la String
Conversia identică reprezintă o conversie de la un tip la același tip. Astfel, aceasta implica determinarea tipului dorit pentru expresie și mărește claritatea programului.
Conversia primitivă implicită reprezintă conversia de la un tip primitiv cu domeniul mai mic la un tip primitiv cu domeniul mai mare (adică de la byte la short,int,long,float și double sau short la int, long, float, double sau char la int, long, float , double sau de la int la long, float , double sau long la float și double sau de la float la double).
Conversia primitivă explicită reprezintă o conversie de la un tip primitiv cu domeniul mai mare la un tip primitiv cu domeniul mai mic (adică de la byte la char, de la short la byte și char, de la char la byte și short, de la int la byte, short și char, de la long la byte, short,char și int, de la float la byte, short, char, int și long sau de la double la byte, short, char , int , long și float).
1.2.9 Contextele de conversie
Limbajul Java cunoaște 5 astfel de contexte de conversie:
Conversie de asignare (convertește tipul unei expresii la tipul unei variabile specificate)
Conversia la apelul unei metode (ca și conversia de asignare doar că se aplică unui argument din apelul unei metode sau constructor)
Conversia explicită (convertește tipul unei expresii la un tip explicit specificat de operatorul cast.
Conversia la String permite oricărui tip să fie convertit la tipul String.
Conversia numerică la compilare aduce operanzii operatorului numeric la un tip comun pentru efectuarea operației. [2]
1.3 Clase, interfețe, tablouri, fișiere, fluxuri de date
1.3.1 Clase
Clasa reprezintă unitatea de programarea fundamentală în limbajul Java, entitatea principală a limbajului orientat pe obiecte.
O clasă se declară astfel:
class nume_clasă
{
specificator_de_acces1:
membru1;
specificator_de_acces2:
membru2;
…
} nume_obiect;
unde nume_clasă este numele clasei.
Există posibilitatea declarării unei clase într-un packet (declarat cu “packet”), în acest caz numele clasei declarate va fi package.clasă. Orice clasă deține câmpuri, constructori și metode. Câmpurile reprezintă niște variabile declarate în clasa respectivă. Metodele sunt funcțiile declarate în interiorul clasei.
Un constructor este tot o metodă ce are același nume cu clasa din care face parte. Acesta nu returnează nicio valoare, ci sunt utilizați pentru inițializarea datelor membre. Unul din constructori este creat în mod automat în momentul compilării, execuția să neavând niciun efect.
Spre deosebre de limbajul C++, unde este necesară prezența destructorilor pentru eliberarea resurselor ocupate de un obiect al unei clase, în Java memoria este eliberată în mod automat. Există totuși destructori ce reprezintă tot metode, car distrug un obiect în momentul apelării acestora. [1]
1.3.2 Modificatorii unei clase
Modificatorii unei clase pot fi de 3 feluri:
– finali;
– abstracți;
– publici;
O clasă publică reprezintă faptul că poate fi accesată de orice alt program creat în Java. Altfel, această clasă poate fi acesata doar de metodele, funcțiile din pachetul în care este declarată.
O clasă abstractă reprezintă un concept general care necesită variante concrete pentru a putea fi utilizat. Atfel, o clasă conține o metodă abstractă dacă:
Clasa C conține o declarație a unei metode abstracte;
Clasa C moștenește o metodă abstractă de la o altă clasă;
Când există o interfață care implementează clasa C și care declară sau moștenește o metodă (care este clar că este abstractă, deoarece o interfață nu are metodele definite) și clasa C nu declară și nici nu moștenește o metodă care o implementează.
1.3.3 Implementarea interfețelor
Limbajul Java suporta declararea unor clase abstracte unde metodele sunt toate abstracte . Acest lucru se poate realiza și prin intermediul interfețelor. Practic, aceste interfețe reprezintă o modalitatea de a declara un tip format din constante și metode abstracte.
Fiecare clasă care implementează o interfață vă trebuii să redefinească metodele respective. Clauza opțională” implements” din declarația unei clase specifică lista interfețelor imediat următoare clasei curente. Mai exact, în declarația “C1 implements I1, I2,…. IN”, spunem că I1,I2,…,IN sunt superinterfețe directe. Acestea trebuie să fie accesibile, altfel se obține eroare la compilare. Numele interfețelor I1,I2,…..,IN trebuie să fie distincte, altfel se obține eroare la compilare. Dacă o clasă implementează o interfață, atunci respectivă clasa trebuie să redefinească toate metodele interfeței. [3]
Declarație atributelor unei clase
Are sintaxa generală: <ModificatoriAtribut> <Tip> <DeclaratieVariabilaI>… <DeclaratieVariabileN> unde
<ModificatoriAtribut> poate fi public, protected, private, final, static, transient, volatile;
<DeclaratieVariabilaI>, cu I = 1…N poate fi:
<Identificator>[= <InitializatorVariabila>] sau
<Identificator> [] … [] [= <InitializatorTablou>]
În momentul declarării unei clase, atributele acestei clase trebuie să aibă nume distincte.Atributele și metodele pot avea același nume din moment ce ele se utilizează în contexte diferite. Dacă o clasă declară un atribut cu un anumit nume, se spune că acel atribut ascunde toate toate declarațiile atributului cu același nume din superclasele și subinterfetele acelei clase.
O clasă moștenește din superclasa directă și din subinterfetele directe toate atributele superclasei și superinterfetelor care sunt accesibile codului clasei și nu sunt ascunse în declarația clasei. Este posibil ca o clasă să moștenească mai multe atribute cu același nume. Un atribut ascuns poate fi accesat prin nume calificat(dacă acesta este static), printr-o expresie de acces a atributului folosind cuvântul rezervat super sau prin conversie explicită la tipul superclasei.[2]
Dacă un atribut este static, atunci se aloca o singură dată memorie pentru acesta, indiferent de numărul de instante ale clasei respective. Deoarece toate instanțele unei clase au același atribut (cu aceeași valoare în orice moment al execuție programului) atributele statice se mai numesc variabile clasă. În constrast, un atribut nestatic se numește variabile instanță. Când se creează o nouă instanță a clasei, se creează și o nouă variabilă asociată atributului nestatic. Atributele statice sunt inițializare în momentul încărcării clasei, deci înainte creării instanțelor acelei clase.
Declarațiile atributelor final trebuie să conțină o expresie de inițializare, altfel avem eroare la compilare. Valoarea unui atribut final este aceeași pe toată execuția programului. Declararea unui atribut final deservește de obicei la documentarea programului și evitarea erorilor de compilare.
Variabilele marcate transient nu fac parte din starea persistentă a unui obiect. Acestea se utiliezeaza la serializarea obiectelor. Serializarea este procesul de împărțire a datelor în octeți, astfel să poate fi trimiși printr-un flux de ieșire. La implementarea unei clase trebuie cont care parte a stării clasei trebuie serializata. Implicit, toate datele nestatice și neocazionale vor fi serializate. Acest lucru înseamnă, că dacă nu se dorește serializarea unor anumite variabile, atunci acestea trebuie declarate transient. [2]
Limbajul Java permite firelor de execuție care accesează variabile să poastreze copii de lucru ale acestora; aceasta permite astfel o impementare mai eficientă a firelor de execuție multiple. Copiile de lucru trebuie să fie corelate cu originalele din memoria principală numai în punctele de sincronizare prescrie, adică la blocarea și deblocarea obiectelor.
Modificatorul volatile specifică faptul că o variabile poate fi modificată în orice moment de către firele de execuție. Scopul acestui tip de modificator o reprezintă protecția împotriva coruperii memoriei unei variabile. Într-un mediu asincron, coruperea datelor apare când variabilă este memorata în regiștrii procesorului.
Modificatorul volatile determina sistemul de execuție Java să refere o variabile direct din memorie,în loc de a utiliza regiștrii. În plus, variabilă este citită din memorie și scris înapoi în memorie după fiecare acces la memorie. Acest tip de modificator se utilieaza destul de rar, deoarece este preferat modificatorul synchronized. [3]
1.3.4 Declaratiie constructorilor
Un constructor este folosit în momentul creării un obiect ce reprezintă o instanță a unei clase. După cum am amintit deja, un constructor se declasa ca o metodă fără să returneze nimic.
Constructorii pot fi apelații prin expresiile de crearea a instanței clasei, de apelarea metodelor newInstance() a clasi Class sau de conversiile și concatenările obținute în urma operatorului de concatenare + .
Constructorii nu pot fi apelați în expresii de apel al unei metode. Accesul la constructori se realizează după modificatorii de acces. Constructorii nu sunt membrii deci nu pot fi niciodată subiect de ascundere sau suprascriere. Signatura unui constructor este similară cu structura și comportarea signaturii unei metode. Spre deosebire, însă, de o metodă , un constructor nu poate fi final, abstract, static, synchronized sau native. Un constructor nu se moștenește, deci nu are sens să fie declarat final său abstract. Un constructor se apelează relativ la un obiect, deci nu are sens să fie static. Nu există o necesitate practică pentru că un constructor să fie synchronized, deoarece acesta va bloca obiectul care se construiește, ce oricum nu este accesibil altor fire de execuție.
O clasă poate interzice crearea de instante ale clasei prin declararea a cel puțin un constructor și declarând toți constructorii private. Dacă o clasă nu conține nicio declarație de constructor, atunci compilatorul creează un constructor implicit fără parametrii care apelează constructorul superclasei fără argumente. Se obține eroare la compilare în cazul în care compilatorul crează un constructor implicit și superclasa acesteia are constructori, însă niciunul fără parametrii.
1.3.5 Distrugerea obiectelor și eliberarea memoriei
Obiectele (sau instanțele unei clase) se creează cu operatorul new sau cu metoda newInstance() a unei clase Class. În momentul creearii unei instante pentru o clasă se aloca memori pentru obiectul nou creat. Acesta va avea o adresă în memorie.
Dacă obiectului creat i se aplică ulterior din nou operatorul new sau metoda newInstance(), atunci se aloca o nouă zona de memorie care va avea bineînțeles altă adresă. Spațiul vechi din memorie se eliberează automat. Acest mecanism se numește garbage collection, numit în ideea reutilizării spațiului de memorie.
1.3.6 Divizarea unei aplicații în fișiere
Dacă un program constă doar dintr-o singură clasă, codul sursă Java este plasat într-un fișier cu același nume ca al clasei și cu extensia .java. Orice instrucțiune de tip import trebuie plasată înaintea declarației clasei. Compilatorul transforma codul-sursă Java în cod binar Java, plasat într-un fișier cu numele clasei, dar cu estensia .class. Dacă programul constă în mai multe de o clasă există 2 alternative:
Plasarea tuturor claselor într-un singur fișier;
Plasarea fiecare clase într-un fișier separat (cu același nume cu numele clasei).
Dacă toate clasele sunt plasate într-un signru fișier, prima clasă din fișier trebuie declarată public, iar celelalte nu. Numele fișierului trebuie să corespundă numelui clasei publice din respectivul fișier. [1]
1.3.7 Interfețe
O interfață grupează mai multe metode și date membre publice. Sunt definite doar prototipurile metodelor, implementările urmând a fi scrie în clasă care va implementa respectivă interfața. Gruparea metodelor publice este utilă pentru uniformizarea modalității de lucru cu anumite clase. Există și interfețe care nu poseda metode sau date membre, acestea având rol de indicator. O clasă poate implementa mai multe interfețe, dar poate moșteni o singură clasă. Relația de moștenire este valabilă și pentru interfețe. Prin implementarea mai multor interfețe se simulează moștenirea multiplă.
Numele unei interfețe nu poate conicide cu numele altei clase sau interfețe din același pachet, altfel apare eroare la compilare. Ca și în cazul claselor, domeniul numelui unei interfețe este întreg pachetul în care este declarat. Fiecare interfață este implicit abstractă, deci este inutil modificatorul abstract în declarația unei interfețe. Dacă declarație unei interfețe conține clauza extends, atunci interfața va moșteni toate metodele și constantele interfețelor enumerate în <ListaInterfete> care se numesc superinterfetele directe.
Orice clasă care implementează interfața declarată va trebui să implementeze toate superinterfetele accesibile clasei. Superinterfetele din clauza extends trebuie să fie accesibile, altfel avem eroare la compilare. Nu există o superinterfata generală, cum este clasa Object pentru superclase.
1.3.8 Tablouri
În limbajul Java, tablourile sunt obiecte create dinamic și pot fi asignate variabilelor de tip Object. Un obiect tablou conține un număr de variabile (elemente sau componente), eventual 0 elemente, caz în care tabloul este vid. Elementele unui tablou nu au nume, dar pot fi referentiate prin expresii de access la tablou care se evaluează la valori întregi pozitive. Dacă un tablou are n elemente, spunem că n este lungimea taboului , elementele putând fi accesate folosind indici de la 0 la n-1. Toate elementele unui tablou au același tip, numit tipul componentelor tabloului.
Declararea unui tablou:
<TipComponente> [ []…..[] ] <Identificator> [ []….[] ]; unde
<Identificator> reprezintă numele tabloului;
Există măcar o pereche de paranteze pătrate, neavând importanță unde apar;
<TipComponente> poate fi primitv sau de referință;
Tipul tabloului este <TipComponente> [ []…[] ], unde numărul de [] este egal cu suma perechilor de paranteze pătrate care apar în declarație.
Un tablou se poate crea prin expresii de crearea care folosesc operatorul new sau un initializator de tablouri. O expresie de creare a unui tablou specifică tipul elementelor, dimensiunea tabloului (număr de perechi de paranteze pătrate) și lungimea tabloului cel puțin pentru prima pereche de paranteze pătrate. Numărul de elemente ale tabloului este dat de variabilă instanță constantă numită length. [2]
1.4 Interfețe grafice
Pentru simplificarea utilizării aplicațiilor complexe se preferă crearea unei interfețe în mod grafic. De asemenea, o aplicație ce deține o interfață grafică are șanse mult mai mari de succes. Ecranul sistemului de calcul utilizat este format dintr-o matrice de pixeli (fiecare pixel având trei componente de culoare roșie, verde și albastră). O astfel de interfață poartă denumirea de GUI – Graphical Ușer Interface. O interfață GUI este formată din mai multe componente ce pot conține elemente grafice de control care permit interacțiunea cu utilizatorul.
Limbajul Java pune la dispoziție o mare varietate de facilități de elaborare și prelucrare a interfețelor grafice, acestea fiind cuprinse în bibliotecile de clase AWT (Abstract Windowsing Toolkit).
1.4.1 Appleturi
Appleturile sunt mici programele încărcate de pe un calculator aflat la distanță (adică un server) și apoi executate local (în navigatorul Web). Un applet gestionează o suprafață de afișare (container) ce poate fi inclusă într-o pagină Web.
Clasa principala , numită Applet, este superclasa oricărui applet, făcând parte din pachetul java.applet și extinzând clasa Panel.
Figura 1.4.1 Exemplu de applet
Un appelat este introdus în cadrul unei pagini web prin intermediul marcatorului <applet> . Acesta are următoarele atribute [2]:
archive Enumerarea de arhive separate prin virgulă.
code Conține numele clasei appletului.
codebase URL-ul directorului în care se afla fișierul .class corespunzător appletului.
object Indică un fișier care conține un applet serializat.
alt Specifică un text care va fi afișat de navigatoarele care nu suporta appleturi.
name Stabilește un nume pentru applet.
align Indica modalitatea de aliniere (similar cu align de la marcatorul <img> și poate avea valorile top, center, bottom).
width Stabilește lățimea appletului în pixeli.
height Stabileste înălțimea appletului în pixeli.
vspace Indica distanța care este lăsată înainte pe verticală și după applet.
hspace Indica distanța care este lăsată pe orizontală în stânga și în drepta appletului.
Cele mai importante metode din clasa Applet sunt următoarele[2]:
public void init() Este prima metodă apelată de navigator sau de appletviewer. Apoi se apelează metoda start(). Există navigatoare care apelează această metodă de mai multe ori.
public void start() Se apelează imediat după metoda init () și la reluarea execuției appletului (ex. când se revine la pagina).
public void stop() Se apelează la suspendarea activității appletului. Unele navigatoare nu apelează această metodă.
public void destroy() Se apelează la terminarea execuției appletului.
Pentru aflarea unor informații privind mediul în care se execută un applet, limbajul Java pune la dispoziția utilizatorilor interfața AppletContext (face parte din pachetul java.applet), metodele acestuia fiind utile în acest sens.
Cele mai importante metode pe care le oferă interfața AppletContext sunt:
public Applet getApplet(String name) Returnează o referință la appletul pentru care s-a stabilit numele specificat (valoare dată de atributul name). Dacă nu există appletul căutat se va întoarce null.
public Enumeration<Applet> getApplets() Returnează o enumerare a tuturor appleturilor din pagină Web curentă.
public void showDocument (URL url) Se va încărca în fereastra navigatorului documentul de la URL-ul specificat. Dacă appletul nu este rulat de un navigator, atunci apelul acestei metode este ignorat.
public void showDocument (URL url, String target) În funcție de țintă specificată, documentul de la URL-ul precizat se va încărca în cadrul (frame-ul) în care se afla appeltul (target =”_self”), în cadrul părinte (target=”_parent”), în fereastra navigatorului(target = “_top”), într-o fereastră nouă fără nume (target=”_blank”) sau într-o fereastră cu numele dat de target (dacă are o valoare diferită de cele de mai sus).
public void showStatus (String mesaj) Afișează mesajul specificat în bară de stare a navigatorului.
Programele Java pot avea ca ieșire elemente grafice sau sunete.
Cele mai cunoscute formate de fișiere grafice sunt GIF (Graphics Interchange Format), JPEG (Joint Photographic Experts Group) și PNG (Portable Network Graphics).
În ceea ce privește formatul sunetului, JAVA suporta AIFF, AU, WAV, MIDI și RMF.
Fig.1.4.2 Cicul de viață al unui applet.
1.4.2 Servlet-uri
Un servlet este un program dezvoltat pe partea de server, scris în Java (sunt clase Java). Acesta extinde dinamic funcționalitatea unui server. Acestea practic returnează rezultate către un client.
Funcționalitatea unui servlet se poate extinde la orice server care suport Java și Servlet API (FTP, Telnet,mail, servere de știri).
Servlet API este o specificare dezvoltat de Sun Microsystems care definește clasele și interetele necesare pentru crearea și execuția servleturilor. Servleturile furnizează un cadru pentru crearea de aplicații care implementează paradigma cerere/ răspuns. [1]
Funcționalitatea servleturilor[2]:
– construiesc dinamic și returnează un document HTML pe baza cererii clientului;
– procesează datele completate de utilizatori printr-un formulat HTML și returnează un răspuns;
– furnizează suport pentru autentificarea utilizatorului și alte mecanisme de securitate;
– interacționează cu resursele serverului, cum ar fi baze de date, fișiere cu informații utile pentru clienți;
– procesează intrările de la mai mulți clienți pentru aplicații cum ar fi jocurile în rețea;
– permite serverului să comunice cu un applet-client printr-un protocol specific și păstrează conexiunea în timpul conversației.
– atașează automat elemente de design pentru păgâni Web, cum ar fi antete sau note de subsol pentru toate paginile returnate de server;
– redirectează cereri de la un server la altul în scop de echilibrare a încărcării;
– partiționează un serviicu logic între servere sau între serrvleturi pentru a procesa eficient o problemă;
Fig. 3.1 – Modul de funcționare a unui servlet
1.4.3 Componente
O componentă reprezintă un obiect cu interfața grafică, ce implementează elemente grafice ce pot interacționa cu utilizatorul.
Cu excepția meniurilor, componentele sunt derivate din clasa Component.
Figura 1.4.3 Componentele Java
CAPITOLUL 2
ACCESUL LA BAZE DE DATE PRIN JDBC
2.1 JDBC
2.1.1 Limbajul JAVA permite conectarea la o bază de date atât local cât și de la distanță. JDBC (Java DataBase Connectivity) reprezintă interfața dezvoltată de Sun MicroSystems pentru o oferi acces la bazele de date gestionate de diverse sisteme de management (DBMS sau DataBase Management System – SGBD).
Unul din cele mai importante aspecte ale JDBC-ului este faptul că se poate lucra cu instrucțiuni SQL , rezultatul obținut în urma unei interogări SQL putând fi procesat independent. Practic, JDBC-ul este o interfață standard între diverse aplicații și diverse DBMS-uri, astfel se pot creea programe într-un mod uniform și independent de DBMS, deoarece interogările SQL sunt preluate și traduse de către diverse drivere ce aparțin DBMS-urilor.
Fig. 2.1 Aplicația trimite instrucțiuni SQL, apoi primește rezultatul;
JDBC permite conectarea la o altă bază de date doar prin schimbarea driver-ului, chiar și în timpul rulării aplicației, nemaifiind necesară recompilarea programului (se poate schimba driverul în mod dinamic).
JDBC furnizează acces orientat pe obiecte la bazele de date prin definirea de clase și interfețe care înfășoară diverse concepte abstracte :
Conexiuni la baze de date Connection
Interogări SQL Statement, PreparedStatement,CallableStatement
Multimi-rezultat ResultSet
Obiecte mari binare sau caractere Blob (Binary Large Objects); Clob (Character Large Objects);
Drivere Driver
Gestionari de drivere DriverManager
Aplicațiile care folosesc bazele de date trebuie să includă pachetul java.sql.
2.1.2 Clasificarea driverelor JDBC[2]:
1. Puntea JDBC-ODBC reprezintă legătura dintre JDBC și un alt mecanism de conectivitate a bazelor de date numit ODBC (Object DataBase Connectivity). Un astfel de driver vine odată cu ultimele distribuții ale platformelor Java și este reprezentat în Java 2 SDK de clasa sun.jdbc.odbc.JdbcOdbcDriver. Este folositor în aplicații de accesare a datelor unde nu există drivere JDBC pure. Puntea traduce metodele JDBC în apeluri de funcții ODBC. Puntea JDBC-ODBC necesita ca bibliotecile ODBC native și driverele ODBC să fie instalate și configurate pentru fiecare client ce folosește un driver de tip 1. Această cerință reprezintă o limitare serioasă pentru multe aplicații, funcționând doar pentru sistemele de operare Microsoft Windows, Sun Solaris și Linux.
2. Java-API nativ aceste drivere folosesc interfața nativă Java pentru a face apeluri direct la API-ul unei baze de date locale. Sunt mai rapide decât driverele JDBC-ODBC. O asemănarea cu driverele 1 este faptul că și driverele 2 necesita instalarea și configurarea bibliotecilor native de accesare a datelor pe masina-client. Sunt foarte convenabile când există biblioteci de accesare a datelor scris în limbajul C, însă acestea nu sunt portabile pe toate platformele.
3. Java-protocol de rețea acest tip include drivere Java pure care folosesc un protocol de rețea (TCP/IP) pentru a comunica cu aplicația JDBC middleware. Apoi aplicația JDBC middleware traduce cererile JDBC folosind protocolul de rețea în apeluri de funcții specifice bazelor de date. Este cea mai flexibilă soluție deoarece nu necesită biblioteci de baze de date native pe client și se poate conecta la mai multe baze de date diferite.
4. Java-protocol baza de date acest tip conține drivere Java pure care implementează un protocol de bază de date pentru a comunica direct cu baza de date. De aceea, aceste drivere sunt cele mai rapide. Ca și driverele de tip 3, nu necesită biblioteci de bază de date native și pot fi folosite distribuit fără instalarea clientului.
În general, driverele de tip 1 și 2 sunt gratuite, însă necesita instalarea la nivelul client a bibliotecilor ODBC native, fiind folosite doar acolo unde nu sunt încă disponibile soluții dezvoltate integral în Java. Driverele de tip 3 sunt comerciale și se pot folosi în aplicații flexibile pe mai multe platforme. Driverele de tip 4 sunt mai rapide, unele fiind chiar gratis,însă se pot folosi doar pt DBMS-uri particulare. Driverele de tip 3 și 4 ofera toate avantajele tehnologiei Java, printre care posibilitatea download-ului acestora în timp ce aplicația este folosită.
Fig. 2.2 Tipurile de drivere BD în Java
2.2 Limbajul SQL
SQL (Structured Query Language) reprezintă unul din cele mai puternice limbaje structuare pentru interogarea bazelor de date relaționale, devenind standard oficial în 1980. Motivul pentru care s-a realizat acest lucru a fost datorită apariției arhitecturii client – server. Pentru că există o standardizare a limbajului SQL, multe SGBD-uri recunosc principalele instrucțiuni ale acestuia.
Instrucțiunile SQL se împart în mai multe categorii:
• Instrucțiuni de definire a datelor, care permit descrierea structurii bazei de date (DDL – Data Definition Language, folosite pentru crearea și modificarea obiectelor bazei de date);
• Instrucțiuni de manipulare a datelor: adăugă, șterge, modifica înregistrări (DML – Data Manipulation Language);
• Instrucțiuni de selecție a datelor, care permit consultarea BD;
• Instrucțiuni de procesare a tranzacțiilor;
• Instrucțiuni de control al cursorului;
• Instrucțiuni privind controlul accesului la date;
Pentru crearea unei tabele vom folosi DDL, care definește sintaxa pentru comenzile CREATE TABLE și ALTER TABLE. Pentru adăugarea, modificarea, ștergerea, căutarea datelor într-o bază de date se folosește limbajul DML (INSERT, UPDATE, DELETE, SELECT).
Accesare unei baze de date folosind JDBC
Pentru a obține informații dorite dintr-o bază de date folosind JDBC este necesară:
1. înregistrarea driverului JDBC folosind gestionarul de drivere DriverManager;
2. stabilirea unei conexiuni cu baza de date;
3. execuția unei instrucțiuni SQL;
4. procesarea rezultatelor;
5. închiderea conexiunii cu baza de date;
2.3.1 Înregistrarea driverului JDBC folosind clasa DriverManager
Orice driver JDBC este înregistrat în mod automat în limbajul Java atunci când se folosește clasa driver (această clasă fiind încărcată dinamic).
Pentru încărcarea dinamică a unui driver JDBC, folosim metodele Class.forName():
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”) incarcarea driverului punte JDBC – ODBC din pachetul sun.jdbc.odbc;
Class.forName(“com.mysql.jdbc.Driver”) încărcarea driverului MySQL Connector /J;
Class.forName() este o metodă statică prin care se permite mașinii virtuale Java să aloce dinamic , să încarce și să facă o legătură la clasa specificata ca argument printr-un șir de caractere. Dacă acea clasă nu este identificată, se arunci o excepție ClassNot-FoundException.
2.3.2 Stabilirea unei conexiuni cu baza de date
După încărcarea driver-ului, putem stabili o conexiune cu baza de date, conexiune identificată printr-un URL specific:
jdbc:<subprotocol>:<nume>
jdbc -> reprezintă faptul că se folosește driverul JDBC pentru conexiunea cu baza de date;
<subprotocol> este numele driverului sau al unei soluții de conexiune cu baza de date.
<nume> este numele logic al bazei de date. Dacă baza de date va fi accesată din internet, secțiunea <nume> va respecta următoarea convenție de nume:
//numegazda:portsubnume
Astfel, un exemplu de adresa corectă va fi:
jdbc:mysql://localhost:386/arhiva
Pentru stabilirea unei conexiuni la o bază de date, se folosește metoda statică getConnection() din clasa DriverManager.
Connection con = DriverManager.getConnection (“jdbc:mysql://localhost/arhiva”);
Pentru baza de date care necesită autentificare, se utilizează o formă a acestei metode cu 3 argumente.
Connection con = DriverManager.getConnection(“jdbc:mysql://localhost/arhiva”, nume_utilizator, parola);
Se pot afla informații despre DBMS prin apelarea Connection.getMetaData() , astfel se creează o instanță DataBaseMetaData, căreia îi putem aplica diverse metode de a afla informații despre tabelele bazei de date, sintaxa SQL suportată, dacă suporta sau nu proceduri stocate.
Folosind JNDI (Java Name an Directory Interface) putem să ne conectăm la o bază de date considerând-o sursa de date având un nume logic, în loc să codăm direct în aplicație numele ei și driverul pe care îl vom folosi. Acest mod de conectare nu este obiectul acestui capitol, fiind o caracteristică a sistemelor distribuite. [1]
2.3.3 Execuția unei instrucțiuni SQL
De îndată ce conexiunea este realizată, se pot trimite instrucțiuni SQL către baza de date. Java nu verifica corectitudinea instrucțiunilor trimise către baza de date, astfel se pot trimite chiar și instrucțiuni nonSQL.
API-ul JDBC specifică 3 interfețe pentru trimiterea de interogări către bazele de date, fiecăreia corespunzându-i o metodă specială în clasa Connection, de crearea a instanțelor corespunzătoare.
Statement Connection.create Statement() Este folosită pentru trimiterea de instrucțiuni SQL simple, fără parametrii;
Prepared Statement Connection.prepare Statement() Permite folosirea instrucțiunilor SQL precompilate și a parametrilor de intrare în interogări. Această metodă de a face interogări este utilă în cazul în care se fac interogări care diferă doar printr-un număr de parametrii.
Callable Statement Connection.prepare Call() Permite folosirea procedurilor stocate pe serverul DBMS.
Pentru a executa o instructiunie SQL, folosim metoda create Statement(), aplicată unui obiect Connection. Această metodă întoarce un obiect din clasa Statement.
Statement instrucțiune = con.createStatement();
Putem aplica apoi una din metodele executeQuery(), executeUpdate() sau execute() obiectului de tip Statement pentru a trimite DBMS-ului instrucțiunile SQL. Metoda executeQuery() este folosită în cazul interogărilor care returnează multimi-rezultat cum este SELECT. Pentru operațiile de actualizare sau ștergere cum ar fi INSERT, UPDATE sau DELETE se folosește metoda executeUpdate() aplicată obiectului de tip Statement rezultând un întreg care reprezintă numărul înregistrării afectate. Aceeași metodă este folosită pentru inteogarile SQL DDL cum ar fi CREATE TABLE, DROP TABLE și ALTER TABLE în acest caz returnând întotdeauna 0. Metoda execute() este folosită în cazul în care se obține mai mult de o mulțime – rezultat sau un număr de linie.
ResultSet rs = instrucțiune.executeQuery(“select * form arhive”);
Strâng sql = “insert into arhive values (‘Popescu’,’Ion’,’Iasi’)”;
int răspuns = instrucțiune.executeUpdate(sql);
Dacă se dorește realizarea de apeluri SQL având date variabile drept intrare, se va folosi clasa PreparedStatement care moștenește clasa Statement. Prezentăm în continuare modul de construcție a unei astfel de instrucțiuni, unde con reprezintă o instanță Connection:
PreparedStatement instrucțiune = con.prepareStatements(“update arhive set nume = ? where prenume like ?”);
2.3.4 Procesarea rezultatelor
Pentru parcurgerea simplă a înregistrărilor unui obiect din clasa ResultSet folosind JDBC 1.0, putem folosi metoda next(), ca în următoarea secvență de cod:
while (rs.next()) //implicit, cursor poziționat înainte de prima linie
{
System.ouț.println(rs.getString (“nume”) + “,”+rs.getString(“prenume”)+
,”+rs.getString(“oraș”));
}
Clasa ResultSet oferă suport și pentru actualizări programatice (mai exact, sunt actualizări aplicate ResultSet-ului, care sunt automat efectuate și asupra bazei de date. În acest fel, baza de date va putea fi modificată fără a efectua instrucțiuni SQL. O astfel de metodă este updateXXX(), XXX fiind un tip de date Java. Spre exemplu, update-String (Strâng nume_câmp, Strâng valoare), poate fi folosit pentru a actualiza celula aflat la intersecția dintre linia curentă și coloana nume_câmp.
rez.last();
rez.updateInt(“nr_curent”,100);
rez.updateString(2,”Popescu”);
rez.updateRow();
Pe lângă actualizări, API-ul JDBC 2.0 oferă posibilitatea ștergerii de linii folosind metoda deleteRow();
rez.last();
rez.deleteRow();
O altă operație este inserarea unei linii în tabela.Această operație se realizează prin intermediul unei linii speciale care poartă numele insert row.
rez.moveToInsertRow();
rez.updateInt(1,357);
rez.updateString(2,”Popescu”);
rez.updateString(3,”Ioan”);
rez.insertRow();
rez.first();
2.3.5 Închiderea unei conexiuni la o bază de date
Când vorbim despre resursele exterioară, așa cum este o conexiune la o bază de date, “garbage collector” nu știe nimic despre starea acestor resurse, dacă este să nu cazul să le elibereze. De aceea, este recomandat ca după ce procesarea datelor s-a terminat, programatorii să închidă explicit conexiunile către baza de date. Pentru aceasta se folosește metoda close() aplicată obiectului Connection. În plus, trebuie închise (înaintea conexiuni) și obiectele Statements și ResultSet folosind metodele lor close() : [5]
try {
//..
rs.close();
instrucțiune.close();
}
catch (SQLException e) {
System.err.println(“Eroare la închiderea interogării: “+ e.toString());
}
finally {
try {
if (conexiuneBazaDate != null) {
conexiuneBazaDate.close();
}
}
catch (SQLException ex) {
System.err.println(“Eroare la închiderea conexiunii: “+ex.toString());
}
}
CAPITOLUL 3
Implementarea JAVA la distanță
3.1 Programarea rețelelor de calculatoare
Programarea rețelelor de calculatoare o descriem folosind conceptul de server și client. Un server este un program, o aplicație ce rulează pe un calculator gazda (numit și host) ce furnizează o conexiune și, bineînțeles, informații utile din momentul stabilirii acelei conexiuni. Un client este un calculator din rețea, ce se poate conecta la un server. Serverul reprezintă furnizorul de informații iar clientul este un consumator. Practic, un client ce are nevoie de anumite informații se conectează la un server specific și îi adresează o cerere, serverul răspunzându-i cu informația respectivă sau nu. Ca un exemplu, browserele Web sunt niște clienți, iar serverele Web sunt niște servere.
Pentru realizarea unei conexiuni între clienți și un server, aceștia vor trebui să respecte un set de reguli (protocol) cum ar fi: un client trimite un request către un server, iar serverul îi răspunde, comunicarea fiind recunoscută de ambii parteneri.
3.1.1 Adresele, porturile și socketurile
Pentru a realiza o conexiune între ele, calculatoarele trebuie să fie conectate fizic. Orice calculator dintr-o rețea este identificat printr-un șir de numere ce este unic în acea rețea, numit adresă IP (adresă Internet Protocol, exemplu: 192.168.0.1). Pentru a stabili o conexiune, se folosește portul, un mecanism conceput pentru realizarea conexiunilor între două sau mai multe echipamente din rețea. Un port este de fapt, un număr întreg cuprins între 0 și 65535, unde valorile mici sunt rezervate unor servicii predefinite. O combinație între portul și adresa IP a unui echipament din rețea poartă numele de socket. Un socket mai este numit și canal de comunicație sau conexiune.
Un client stabilește o conexiune cu un server prin care informația poate fi trimisă, iar la final se va deconecta lăsând liber socketul pentru o utilizare ulterioară. Un socket furnizează facilități pentru crearea de fluxuri de intrare și de ieșire, care permit datelor să fie schimbate între client și server. [2]
Oricărui socket îi putem asigna un port dorit sau în cazul în care nu-i specificăm niciun port, sistemul de operare îi va asigna un port default. Atunci când se trimite un pachet printr-un socket, acestuia trebuie să-i adăugăm informații despre destinația pachetului, cum ar fi:
adresa mașinii din rețea (adresa IP- Internet Protocol) care trebuie să primească pachetul;
port (între 0 și 65535) pentru indicarea socketului corespunzător care va primi datele respective.
În momentul stabilirii unei conexiuni, atât clientul cât și serverul vor avea câte un socket.
Comunicare efectivă se va realiza între socketuri. Socketul serverului are nevoie de un port la creare, după care va aștepta conexiuni cu clienții. Numerele de port pentru socketurile serverului sunt numere cunoscute pentru programele – client. Ca un exemplu putem aminti protocolul HTTP (folosit de navigatoarele Web) ce folosește portul 80.
Pentru a utiliza socketuri în realizarea conexiunilor server-client, este nevoie de ne decidem ce tip de protocol dorim să utilizăm pentru transportul datelor în rețea:
protocol orientat pe conexiune(connexion-oriented protocol);
protocol neorientat pe conexiune(connection-undirected protocol);
În cazul procolului orientat pe conexiune, un client stabilește o conexiune cu un server via socketuri. Odată stabilită conexiunea, un protocol orientat pe conexiune asigură cu datele s-au trimis sigur[2] :
fiecare pachet trimis este și primit. Se așteaptă o confirmare de la fiecare pachet trimis. Dacă nu se primește confirmarea într-o anumită perioadă de timp, se trimite din nou pachetul.
pachetule sunt citite de socketul-destinatie în aceeași ordine în care au fost trimise. Datorită modului în care funcționează rețelele, pachetele pot ajunge într-o ordine diferită față de cea în care au fost trimise. Protocolul orientat pe conexiune va permite socketului-detinatie să restabilească ordinea în care au fost trimise pachetele. În comunicarea orientat conexiune este necesară participarea simultană a celor 2 aplicații.
Un protocol neorientat pe conexiune permite cea mai rapidă trimitere a pachetelor. Acesta nu garantează, în schimb, ca acele pachete trimise sunt citite în aceeași ordine de programul detinatar. Mai mult, programul destinatar nu trebuie neapărat să fie disponibil pentru recepționarea datelor. Expeditorul trimite informațiile, iar destinatarul le va citi eventual mai târziu, după care va răspunde. În schimb, este garantată primirea pachetelor. Vom prezenta 2 situații în care protocoalele neorientat pe conexiune sunt frecvent preferate în locul celor orientate pe conexiune: [2]
când trebuie trimis doar un singur pachet și garantarea trimiterii nu este crucială, atunci un protocol neorientat pe conexiune elimina timpul pierdut pentru crearea di distrugerea unei conexiuni. Ca un exemplu, protocolul orientat pe conexiune TCP/IP utilizează 7 pachete pentru trimiterea unui signru pachet, în timp ce protocolul neorientat pe conexiune UDP/IP folosește doar unul.
pentru aplicații în timp real, cum ar fi aplicațiile video/audio, garantarea unei transmisii sigure nu este o necesitate. Așteptările unor date care întârzie pot implica pauze importante în aplicația respectivă. Tehnicile de trimitere audio/video care utilizează un protocol neorientat pe conexiune au fost dezvoltate și funcționează mai bine.
Diferențele între conexiunea TCP și conexiunea UDP pot fi descrise prin exemplul următor:
TCP poate fi asemănat unui apel telefonic: este format numărul de telefon după care se stabilește o conexiune cu un client până când conexiunea este întreruptă de unul din cei doi participanți.
UDP poate fi asemănat unui sistem de poștă obișnuit. Orice scrisoare este pusă într-un plic și trimisă către destinație. Scrisorile pot ajunge separat sau nu la destinație. De asemenea, acestea pot fi pierdute și nu este sigur dacă vor fi recepționate în aceeași ordine în care au fost trimise.
Figura 3.2 – diferențele dintre protocoalele TCP și UDP.
3.1.2 Java folosește 2 mecanisme de bază pentru accesarea datelor și a resurselor prin intermediul unei rețele ambele făcând parte din pachetul java.net :
socketul care este mecanismul fundamental. Un socket permite programelor să efectueze schimburi de pachete. Există clase în pachetul java.net care oferă suport pentru lucrul cu socketuri: clasele Socket și ServerSocket sunt utile pentru realizarea unui transfer orientat conexiune, iar DatagramSocket, DatagramPacket și MulticastSocket pentru cele neorientat conexiune.
URL (Uniform Resource Locator), care este mecanismul de nivel înalt. Pachetul java.net conține clasa URL pentru accesarea și obținerea unei resurse dintr-o rețea.
3.1.2.1 Clasa ServerSocket
Pentru implementarea unui server orientat conexiune, este necesar să urmăm pașii [2]:
se construiește o instanță a clasei ServerSocket pentru a crea un socket ce vă așteaptă la un port precizat, cu scopul stabilirii conexiunilor;
când se va accepta o conexiune (prin apelul metodei accept()), se va crea un obiect Socket care încapsulează conexiunea;
se va utiliza obiectul Socket creat anterior pentru a obine un flux de intrare de tip InputStream, respectiv unul de ieșire OutputStream, în vederea citirii și scrierii informațiilor către respectiv de la client. Comunicarea cu clientul se va realiza deci prin intermediul fluxurilor.
opțional, se vor putea crea noi fire de execuție pentru fiecare conexiune nouă astfel încât să se poate accepta în continuare noi conexiuni în timp ce sunt deservite celelalte cereri.
Pentru a construi un obiect de tip ServerSocket este necesar să alegem un port pe mașina locală, cuprins între 1 și 65535.Dacă se selectează 0, atunci sistemul de operare va atribui un port liber și valid de fiecare dată când rulează aplicația. Numărul de port ales trebuie comunicat și clienților pentru a se putea conecta la server. După ce a fost creat un obiect ServerSocket se vor putea accepta conexiuni cu clienții. În cazul în care sunt mai multe cereri de conexiune din partea clienților, acestea vor fi depuse într-o coadă de așteptare și eliminate una câte una de îndată ce serverul apelează metoda accept(). Prin intermediul constructorului clasei ServerSocket se permite specificarea numărului de conexiuni care pot fi în coada de așteptare, sistemul de operare respingând orice cerere de conexiune apărută ulterior în cazul în care coada e plină.
Constructorii clasei ServerSocket sunt:
ServerSocket() – creează un socket pentru server, neatasat încă unui port.
ServerSocket(int port) – creează un socket pentru server, neatasat la portul indicat. Coada de așteptare poate conține cel mult 50 de cereri pentru acceptarea conexiunii.
ServerSocket(int port, int dimCoada) – în plus, față de constructorul precedesnt, se stabilește și dimensiunea cozii de așteptare.
ServerSocket(int port, int dimCoada, InetAddress bindAddr) – al treilea parametru este obiect al clasei InetAddress și indică adresa IP a mașinii pentru care se acceptă conexiuni.
Dacă se transmite null, se vor accepta conexiuni pentru orice adresă a mașinii.
3.1.2.2 Clasa Socket
Un obiect Socket corespunde unei conexiuni de rețea TCP. Utilizând această clasă, un client poate stabili un canal de comunicație bazat pe fluxuri cu o mașină de la distanță. Structură generală a unui client care stabilește o conexiune este foarte simplă, acesta creează un obiect Socket care deschide o conexiune către un server și apoi prin intermediul aceluiași obiect se realizează comunicarea cu serverul. Programul-client trebuie să cunoască adresa serverului precum și portul acestuia. Dacă la adresa și portul indicat nu există niciun server activ, atunci stabilirea conexiunii va eșua și va fi aruncată o excepție IOException. Clasa Socket este utilizată și de server pentru comunicare cu clienții. Pentru fiecare client va exista un obiect de acest tip (obinut în urma apelului metodei accept() din clasa ServerSocket), iar trimiterea respectiv recepționarea mesajelor pentru server sunt identice cu cele pentru client [2].
Crearea unui obiect Socket va realiza conexiunea cu serverul. Prezentăm constructorii semnificativi ai acestui obiect:
Socket() – crează un obiect Socket neconectat.
Socket(Strâng gazdă, int port) crează un obiect Socket conectat la portul indicat al adresei gazda specificate. Gazda poate conține numele serverului sau adresa IP. Portul trebuie să fie cuprins între valorile 1 și 65535.
Socket(InetAddress adresa, int port) – față de constructorul precedent, se specifică adresa IP a serverului prin intermediul unei obiect de tip InetAddress.
Socket(Strâng gazdă, int port, InetAddress adresaLocala, int portlocal) – crează un obiect Socket legat la adresa locală și portul local și conectat la portul specificat și adresa specificata.
Socet(InetAddress adresa, int port, InetAddress adresalocala, int portlocal) – similar cu precedentul costructor cu deosebirea că primul argument este un obiect din clasa InetAddress.
Pentru a comunica prin TCP, este necesar să stabilim conexiunea (creearea unui obiect de tip Socket) dupa care să apelam metodele getInputStream() și getOutputStream() pentru obținerea fluxurilor de intrare sau de ieșire.Așadar, atât clientul, cât și serverul vor avea câte un obiect InputStream și OutputStream, în scopul comunicării. În acest caz, s-a folosit readUTF() pentru citirea fluxurilor de intrare și writeUTF() pentru scrierea informațiilor în fluxul de ieșire.
Fig. 3.2.2 Modul de interconectare prin clasele ServerSocket și Socket. [7]
3.2.3 Socketurile pentru protocoalele neorientate pe conexiune (UDP) sunt implementate în Java de clasele DatagramPacket și DatagramSocket.
3.1.3.1 Clasa DatagramPacket
Clasa DatagramPacket utilizează un pachet de date numit datagramă. Această datagramă permite specificarea adresei destinatarului precum și a informațiilor transportate.
Spre deosebire de TCP, nu se asigură primirea mesajului de către destinatar, însă tehnologia din ziua de azi permite utilizarea acestuia datorită ratei de pierdere extrem de mică. De asemenea, nu este asigurată nici recepționarea acestor mesaje în aceeași ordine cu cea în care au fost emise.
Există 2 tipuri de constructori pentru clasa DatagramPacket [2] :
pentru transmiterea mesajelor;
pentru recepționarea mesajelor;
Constructorii pentru recepționarea datagramelor sunt
DatagramPacket(byte[] buf, int lungime)
Buf reprezintă zona de memorie în care se va copia mesajul, iar lungime numărul maxim de octeți pe care îl poate avea acel mesaj.
DatagramPacket(byte[] buf, int deplasament, int lungime)
Mesajul va fi stocat în buff, începând cu poziția deplasament.
Constructorii pentru trimiterea datagramelor sunt
DatagramPacket(byte[] buf, int lungime, InetAddress adresa, int port)
DatagramPacket(byte[] buf, int deplasament, int lungime, InetAddress adresa, int port)
Parametrii bug, lungime și deplasament au aceeași semnificație ca la constructorii pentru primirea datagramelor cu deosebirea că se referă la transmiterea mesajului (lungime va indica numărul de caractere pe care îl va avea mesajul). În plus, parametru adresa indica adresa masnii iar port specifică portul la care se așteaptă datagrame.
Obiectul DatagramPacket conține și metodele: [3]
– InetAddress getAddress() returnează adresa IP a datagramei
– int getPort() returnează portul conținut de datagrama
– byte[] getData() returnează conținutul datagramei
– int getLength() întoarce dimensiunea mesajului datagramei
– void setAddress (InetAddress adresa) stabilește adresa pentru datagrama curentă
– void setData (byte[] buf) modifica conținutul mesajului
– void setLength(int lungime) modifica dimensiunea mesajului
3.1.3.2 Clasa DatagramSocket
Această clasă corespunde unui socket pentru datagrame prin intermediul căruia se vor putea transmite sau recepționa pachete. Ca și în cazul unui server TCP, unul UDP trebuie să utilizeze un număr de port particular între 1 și 65535. UDP este un protocol neorientat pe conexiune, astfel se va crea un singur socket pentru trimiterea pachetelor către diferite destinații și primirea pachetelor de la diferite surse.
Un obiect ce aparține clasei DatagramSocket va recepționa pe un anumit port UDP de pe mașina locală pachetele care sosesc. Numărul portului se poate specifica sau OS-ul îl poate asigna.
Constructorii clasei DatagramSocket:
– DatagraSocket() throws SocketException
Această comandă creează un obiect DatagramSocket cu un număr de port ales aleator.
– DatagramSocket(int port) throws SocketException
Această comandă creează un socket UDP care utilizează portul specificat.
– DatagramSocket(int port, InetAddress locală) throws SocketException
Această comandă creează un obiect de tipul DatagramSocket care utilizează portul specificat și care este legat la adresa indicată.
3.2 Apelul metodelor la distanță
3.2.1 Remote Method Invocation sau pe scurt, RMI, furnizează posibilitatea comunicării unor obiecte Java aflate pe mașini diferite, la distanță, prin apeluri de metode normale. Calculatoarele conectate la internet comunica prin protocolul orientat conexiune TCP/IP, ceea ce reprezintă faptul că aplicațiile de rețea pot fi dezvoltate fără fluxuri și socketuri.
Această metodă este foarte folositoare deoarece permite conectarea între obiecte prin intermediul metodelor, fără să conteze locația lor (practic, un client ar trebui să acceseze un server din rețea ca și cum ar fi în același sistem).
Pentru crearea unei clase care va fi accesibilă de la distanță, trebuie să definim și să implementăm o interfață care stabilește metodele disponibile (la distanță). În Java, există un instrument disponibil pentru compilarea claselor care pot fi invocate la distanță, numit rmic (remote method invocation compiler) care generează 2 fișiere de cod binar Java importante:
a) stub
b) skeleton
Aceste două fișiere sunt folosite pentru transportul informațiilor prin rețea, conform schemei de mai jos:
Fig. 3.2 -> Modul de functionare RMI
Stub-ul reprezintă o clasă din Java care preia informațiile ce trebuie comunicate unui obiect la distanță, transmițându-le prin rețea skeleton-ului.
Skeleton-ul este o clasă care staționează pe o mașină virtuală de la distanță și care acceptă aceste informații primite prin rețea de la stub, trimițându-le serverului. Acest server nu face altceva decât să invoce metoda cu argumentele primite de la skeleton, respectiv stub, respectiv client, dupa care le retransmite skeleton-ului, acesta le transmite mai departe către stub, iar apoi stub-ul transmite clientului.
Figura 3.3 Modul de interconecare a unul client la server prin RMI [6]
Pentru a putea fi utilizat, obiectul de la distanță trebuie să fie înregistrat de un serviciu de nume (domain name) care permite clienților să localizeze respectivul obiect. Clientul se conectează la un registru de nume și cere o referință la un serviciu înregistrat sub un nume dat. Acesta registru de nume întoarce apoi o referință la distanță la un obiect listat sub acest nume. Aceasta referință conține gazda, portul și identificatorul RMI al obiectului. Astfel, se primește un stub ce implementează interfața cerută și traduce în mod automat toate apelurile metodei în apeluri la distanță pentru acel obiect real;
Apelul unui client este trimis stub-ului care rezolva toare detaliile de comunicare transmițând apelul și parametrii către skeleton. Acesta face atunci un apel al metodei actuale pentru obiectul de la distanță. Valoarea returnată este în final trimisă înapoi de la skeleton la stub, care apoi returnează rezultatul clientului.
Astfel, dacă programatorii setează corect un obiect de la distanță, atunci totul devine transparent pentru obiectul de la distanță și client. Pot fi apelate doar metodele declarate în interfața de la distanță. Cu aceste diferențe cadrul RMI este un mecanism extrem de puternic, deoarece el permite dezvoltarea aplicațiilor de rețea ca și cum ar fi un apel de metoda obișnuit[2].
Un obiect de la distanță este un obiect care a fost setat să accepte apeluri de metode de la un alt obiect care rulează într-o mașină virtuală Java la distanță. Acesta necesită 2 etape:
– declararea unei interfețe care descrie metodele obiectului;
– implementarea acestei interfețe.
Această interfață trebuie să extindă interfața java.rmi.Remote care este de fapt un indicator ce identifică interfețele accesibile la distanță. Interfața Remote este superinterfata tuturor interfețelor de la distanță. O interfață la distanță descrie metodele pe care le conține un obiect la distanță. Acestea sunt doar metode care pot fi accesate prin RMI de un client. Interfața Remote este doar o interfață de marcare și astfel nu are metode. Toți parametrii și rezultatul întors al unei metode la distanță trebuie să fie serializabile de fluxul de obiecte.
Atunci când un client RMI accesează un obiect de la distanță, acesta va putea accesa obiectul doar prin una sau mai multe interfețe la distanță. Beneficiul este ca implementarea este separată de interfață public.
3.2.2 Declararea unei interfețe la distanța:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface EXEMPLU extends Remote
{
public int metoda(Strâng s) throws RemoteException;
}
Din punct de vedere al unei aplicații RMI, există 5 tipuri de fișiere .class cu implementarea clientului, a serverului, interfața de la distanță, clasele stub și skeleton.
Pentru distribuirea unui client RMI trebuie să includem clasele de implementarea a clientului, clasele de interfață la distanță și clasele stub.
Pentru distribuirea unui server RMI trebuie să includem clasele de implementarea a serverului, interfețele de la distanță, clasele stub și clasele skeleton. Toate aceste clase sunt necesare în timpul rulării serverului și cu toate că stub-ul este executat doar de client, acesta este necesar când se exporta la distanță.
Anumite aplicații RMI nu se separă așa de categoric pe partea de client și cea de server. Este posibil ca și serverul să poată executa anumite metode la distanță.
3.2.3 Principalele pachete Java care sunt utilizare de aplicațiile ce invoca metode la distanță sunt: [1][2]
– java.rmi care oferă suport pe partea de client RMI; include interfețele prin care sunt accesate obiectele la distanță și un mecanism pentru localizarea serviciilor RMI pe o mașină la distanță.
– java.rmi.server conține clase referitoare la partea de server RMI; include clase pentru lucrul cu servicii RMI, pentru rezolvarea cererilor TCP/IP și HTTP.
– java.rmi.registry pune la dispoziție clase pentru lucrul cu regiștri de nume RMI (creare , localizare și manipulare la distanță);
– java.rmi.dgc conține clase referitoare la eliberarea memoriei, serverele RMI păstrează în mod automat un număr de referințe la distanță active pe care le servesc și le pot închide;
– java.rmi.activation oferă suport pentru salvarea stărilor obiectelor la distanță atunci când sunt neutilizare și respectiv restaurarea acestora când apar cerei. Serverele RMI nu trebuie neapărat să ruleze mereu; ele pot fi activate de un daemon de activare doar dacă sosește o cerere și sunt ținute într-o stare pasivă serializata.
CAPITOLUL 4 Aplicația practică
4.1 Lucrul cu baze de date la distanță sub limbajul Java
– Informații generale aplicație practică –
Acest proiect face referire la conectarea unui client sau mai multor clienți la o bază de date printr-un server. Baza de date este realizată folosind aplicația Microsoft Access 2007-2010. Această poartă numele de “parcauto”. Baza de date conține 6 câmpuri :
a) ID (număr de identificare în BD)
b) Marca (marca autovehiculului)
c) Model (modelul autovehiculului)
d) An (anul de fabricație al autovehiculului)
e) Preț (prețul autovehiculului la momentul curent)
f) Nota (reprezintă o notă a reprezentanților parcului auto, notă obținută pe bază mai multor factori : ținută drum, numărul de kilometrii parcurși, uzură piese interioare și exterioare, aspectul fizic, istoric, performanțe etc.).
Figura 4.1 Baza de date” parcauto”;
Serverul este creat în limbajul Java, acesta fiind conectat la baza noastră de date: “parcauto”. Conectarea s-a realizat prin funcția connect(), folosind driverul JDBC-ODBC.
Strâng driver = "sun.jdbc.odbc.JdbcOdbcDriver";
Class.forName(driver);
Strâng db = "jdbc:odbc:parcauto";
con = DriverManager.getConnection(db);
st = con.createStatement();
Comunicarea dintre server și client s-a realizat prin 2 metode :
1) RMI (Remote Method Invocation)
2) Folosind clasele Socket – ServerSocket
1) RMI (Remote Method Invocation)
Comunicarea dintre server și client s-a realizat prin metoda RMI (Remote Method Invocation), interfața I_BD reprezentând signatura metodelor serverului.
SERVER:
try
{
Registry reg = LocateRegistry.createRegistry(9999);
reg.bând("au", au);
System.out.println("Serverul a pornit!");
}
catch (Exception x) {System.out.println("Serverul a pornit!");
}
CLIENT:
Scanner sc = new Scanner(System.în);
Strâng url = "rmi://" + "localhost" + ":" + 9999 + "/" + "au";
au = null;
try
{
au = (I_BD) Naming.lookup(url);;
log.append("conectat rmi!" + '\n');
}
catch(Exception e)
{
log.append("Nu s-a putut accesa baza de date!" + '\n');
}
Clasa server conține toate metodele pe care le invocă un client prin intermediul interfeței I_BD pentru obținerea rezultatelor în urma unor interogări SQL asupra bazei de date. Vom prezenta doar o parte din acestea:
Prima metodă este ștergerea unui element din baza de date. Ștergea unui element se va face după marca, model, preț, an și notă. Astfel, un client introduce de la tastatură marca și modelul mașinii, anul de fabricație, prețul și nota acesteia.
try
{
marca = text[0].getText();
modell = text[1].getText();
ann = text[2].getText();
prett = text[3].getText();
nota = text[4].getText();
căutare = text[5].getText();
p = new parcul(i+1,marca,modell,ann,prett,nota);
au.delete(p);
log.append("Element eliminat din baza de date! "+ '\n');
}
Fig. 4.2 Interfața clientului
Fiecare valoare este preluată de un string din Clasa client. Toate string-urile sunt apoi introduse în vectorul parcul și apelată metoda de ștergere de pe server. Server-ul se conectează la baza de date și trimite o instrucțiune SQL bazei de date (instrucțiunea DELETE):
public void delete(parcul p) throws Exception
{
sql = "DELETE * FROM parcul WHERE Marca = '" + p.r2() + "' AND Model = '" + p.r3() + "' " +
"AND An = '" + p.r4() + "' AND Preț = '" + p.r5() + "' AND Nota= '" + p.r6() + "';";
st.executeUpdate(sql);
}
În ceea ce privește căutarea unui element în baza de date, aceasta se poate efectua după marca mașinii, după modelul mașinii, după an, preț sau nota.
Clientul trimite valorile introduse de un utilizator către server printr-un vector (vectorul parcul), și selectează după ce valoare se va realiza căutarea (marca, model, an , preț sau nota).
try
{
marca = text[0].getText();
modell = text[1].getText();
ann = text[2].getText();
prett = text[3].getText();
nota = text[4].getText();
căutare = text[5].getText();
if ( (căutare.equals("marca")) || (căutare.equals("MARCA")) || (căutare.equals("Marca")) )
{
v = au.cautama(marca);
while ( !v.isEmpty() )
{
p = (parcul) v.elementAt(0);
log.append(""+p + '\n');
v.removeElementAt(0);
}
}
else if ( (căutare.equals("model")) || (căutare.equals("Model")) || (căutare.equals("MODEL")) || (căutare.equals("modelul")) || (căutare.equals("Modelul")) )
{
v = au.cautamo(modell);
while ( !v.isEmpty() )
{
p = (parcul) v.elementAt(0);
log.append(""+p + '\n');
v.removeElementAt(0);
}
}
else if ( (căutare.equals("an")) || (căutare.equals("AN")) || (căutare.equals("An")) || (căutare.equals("anul")) || (căutare.equals("Anul")) )
{
v = au.cautaa(ann);
while ( !v.isEmpty() )
{
p = (parcul) v.elementAt(0);
log.append(""+p + '\n');
v.removeElementAt(0);
}
}
else if ( (căutare.equals("preț")) || (căutare.equals("PREȚ")) || (căutare.equals("Preț")) || (căutare.equals("prețul")) || (căutare.equals("Prețul")) )
{
v = au.cautap(prett);
while ( !v.isEmpty() )
{
p = (parcul) v.elementAt(0);
log.append(""+p + '\n');
v.removeElementAt(0);
}
}
else if ( (căutare.equals("nota")) || (căutare.equals("Nota")) || (căutare.equals("NOTA")) )
{
v = au.cautan(nota);
while ( !v.isEmpty() )
{
p = (parcul) v.elementAt(0);
log.append(""+p + '\n');
v.removeElementAt(0);
}
}
}
catch (Exception n)
{
log.append("Eroare la căutare!" + '\n');
}
Serverul trimite apoi, o interogare către baza de date pentru a afișa valorile în funcție de cerințele clientului:
a) După marca:
public Vector cautama(Strâng marca) throws Exception
{
parcul p = null; Vector v = new Vector();
sql = "select * from parcul where Marca = '" + marca + "';";
rs = st.executeQuery(sql);
while ( rs.next() )
{
p = new parcul(rs.getInt("ID"), rs.getString("Marca"), rs.getString("Model"),
rs.getString("An"), rs.getString("Preț"), rs.getString("Nota") );
v.addElement(p);
}
return v;
}
b) După model:
public Vector cautamo(Strâng model) throws Exception
{
parcul p = null; Vector v = new Vector();
sql = "select * from parcul where Model = '" + model + "';";
rs = st.executeQuery(sql);
while ( rs.next() )
{
p = new parcul(rs.getInt("ID"), rs.getString("Marca"),rs.getString("Model"),
rs.getString("An"), rs.getString("Preț"), rs.getString("Nota") );
v.addElement(p); //Vectorul v preia rândurile din baza de date după modelul selectat
}
return v;
}
c) După anul de fabricație:
public Vector cautaa(Strâng an) throws Exception
{
parcul p = null; Vector v = new Vector();
sql = "select * from parcul where An = '" + an + "';";
rs = st.executeQuery(sql);
while ( rs.next() )
{
p = new parcul(rs.getInt("ID"), rs.getString("Marca"), rs.getString("Model"),
rs.getString("An"), rs.getString("Preț"), rs.getString("Nota") );
v.addElement(p);
}
return v;
}
d) După preț:
public Vector cautap(Strâng preț) throws Exception
{
parcul p = null; Vector v = new Vector();
sql = "select * from parcul where Preț = '" + preț + "';";
rs = st.executeQuery(sql);
while ( rs.next() )
{
p = new parcul(rs.getInt("ID"), rs.getString("Marca"), rs.getString("Model"),
rs.getString("An"), rs.getString("Preț"), rs.getString("Nota") );
v.addElement(p);
}
return v;
}
e) După nota:
public Vector cautan(Strâng nota) throws Exception
{
parcul p = null; Vector v = new Vector();
sql = "select * from parcul where Nota = '" + nota + "';";
rs = st.executeQuery(sql);
while ( rs.next() )
{
p = new parcul(rs.getInt("ID"), rs.getString("Marca"), rs.getString("Model"),
rs.getString("An"), rs.getString("Preț"), rs.getString("Nota"));
v.addElement(p);
}
return v; }
După cum putem vedea, introducem marca autovehiculului, apoi căutam după marca, iar serverul va întoarce toate autovehiculele fabricate de marca respectivă din baza de date.
Fig. 4.3 Căutare după marca “Volkswagen”;
Pentru conectare, deconectare la baza de date au fost create două metode:
a) startserver
b) disconnect
public static void startserver() throws Exception
{
Strâng driver = "sun.jdbc.odbc.JdbcOdbcDriver";
Class.forName(driver);
Strâng db = "jdbc:odbc:parcauto";
con = DriverManager.getConnection(db);
st = con.createStatement();
Auto au = new Auto(st);
try
{
Registry reg = LocateRegistry.createRegistry(9999);
reg.bând("au", au);//înregistrează serverul au, constructorul au sub denumire "au";
System.ouț.println("Serverul a pornit!");
}
catch (Exception x) {System.ouț.println("Serverul a pornit!");}
}
public void disconnect() {
try
{
con.close();
System.ouț.println("Serverul s-a oprit");
} catch (Exception e) {}
}
2) Folosind clasele Socket – ServerSocket
Clientul se conecteaza la server folosind clasa Socket si ServerSocket.
SERVER: (serverul initializaza obiectul ss de tipul clasei ServerSocket pe portul 9998 – practic deschide portul 9998 si asteapta conexiuni; de asemenea initializeaza un obiect de tip Socket pentru a trimite catre client informatiile necesare)
public void connect() throws Exception {
try {
String driver = "sun.jdbc.odbc.JdbcOdbcDriver";
Class.forName(driver);
String db = "jdbc:odbc:parcauto";
con = DriverManager.getConnection(db);
st = con.createStatement();
ServerSocket ss = new ServerSocket(9998); Socket cs;
while (b) {
cs = ss.accept(); System.out.println("S-a legat client nou");
new Conexiune(cs,st);
}
System.out.println("Serverul s-a oprit"); ss.close(); con.close();
}
catch(Exception e) { }
}
public void disconnect() {
try
{
con.close();
System.out.println("Serverul s-a oprit");
} catch (Exception e) {}
}
CLIENT: (clientul initializeaza un obiect de tipul Socket ce se conecteaza la un IP + portul 9998 si IP-ul “localhost” sau 127.0.0.1 pentru conectarea la serverul nostru deoarece in acest caz si serverul si clientul se afla pe acelasi sistem de calcul.
try {
Socket sock = new Socket("localhost",9998);
is = sock.getInputStream();
os = sock.getOutputStream();
dis = new DataInputStream(is);
dos = new DataOutputStream(os);
} catch (Exception e) {};
Astfel conexiunea Server-Client este realizata. Aceasta metoda functioneaza astfel: Server-ul citeste intr-o varibila String, numita “buttonclick” informatia primita de la client (String buttonclick = dis.readUTF();).
In functie de butonul apasat, clientul va trimite server-ului o anumita informatie.
Daca se apasa butonul SHOW, clientul va trimite server-ului informatia “afisare”, daca se apasa UPDATE, clientul va trimite “update”, toate butoanele functionand in acelasi fel, exceptie SEARCH.
Server-ul, dupa ce primeste o informatie, o compara cu o anumita valoare definita in program. Astfel daca ceea ce primeste este:
• afisare (rezultat intors in momentul in care se apasa butonul SHOW) -> server-ul va trimite un query catre baza de date pentru a intoarce toate rezultatele, rezultatele fiind trimise catre client;
if (buttonclick.equals("afisare"))
{
String sql = "select * from parcul";
rs = st.executeQuery(sql);
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata"); }
• update (rezultat intors in momentul in care se face click pe butonul UPDATE) -> server-ul va asculta pe portul declarat la inceputul programului inca 5 valori trimise de client (Marca, modelul, pretul, anul si nota), introduse in niste String-uri. Apoi acesta va efectua un query catre baza de date pentru a modifica anul,pretul si nota elementului cu marca si modelul introdus de utilizator cu alte elemente introduse de acelasi utilizator; Dupa update, se va afisa mesajul “Elementul a fost actualizat!”;
else if (buttonclick.equals("update")) {
marcaa = dis.readUTF();
modell = dis.readUTF();
ann = dis.readUTF();
prett = dis.readUTF();
notaa = dis.readUTF();
st.executeUpdate("UPDATE parcul SET An = '"+ann+"',Pret = '"+prett+"', Nota='"+notaa+"' WHERE Marca = '"+marcaa+"' AND Model = '"+modell+"' ");
dos.writeUTF("Elementul a fost actualizat!");
}
• inserare (daca se apasa butonul INSERT) -> in acest caz serverul va accepta inca 5 valori trimise de client (marca, model, an, pret si nota) si le va introduce in baza de date.
else if (buttonclick.equals("inserare")) {
marcaa = dis.readUTF();
modell = dis.readUTF();
ann = dis.readUTF();
prett = dis.readUTF();
notaa = dis.readUTF();
int nr=0;
String s_id ="SELECT MAX(ID) AS rowcount FROM parcul;";
rs = st.executeQuery(s_id);
if(rs.next())
{ nr = rs.getInt(1); }
nr = nr + 1;
st.executeUpdate("INSERT INTO parcul(ID,Marca, Model,An,Pret,Nota) VALUES ('"+nr+"','"+marcaa+"','"+modell+"' , '" +ann+ "' , '" +prett+ "' , '" +notaa+ "' )");
dos.writeUTF("Element introdus in baza de date!"); }
• stergere (atunci cand se efectueaza un click pe DELETE) -> server-ul de asemenea, va accepta inca 5 valori. Pe urma, va efectua un query catre baza de date pentru stergere, in caz de succes returnand mesajul “Elementul a fost sters din BD!”.
else if (buttonclick.equals("stergere")) {
marcaa = dis.readUTF();
modell = dis.readUTF();
ann = dis.readUTF();
prett = dis.readUTF();
notaa = dis.readUTF();
st.executeUpdate("DELETE * FROM parcul WHERE Marca = '"+marcaa+"' AND Model = '"+modell+"' AND An = '"+ann+"' AND Pret = '"+prett+"' AND Nota = '"+notaa+"' ");
dos.writeUTF("Elementul a fost sters din BD!");
}
In continuare vom prezenta butonul SEARCH. Inainte de a apasat acest buton, utilizatorul este nevoit sa aleaga dupa ce se va efectua cautarea. Apoi, este necesara introducerea unei valori in campul text respectiv.
Dupa acest lucru, daca se apasa butonul SEARCH, clientul va trimite catre server sirul “cautare” urmat de elementul dupa care se va efectua cautarea (marca, model, an, pret sau nota). Server-ul va trimite catre baza de date un query alaturi de valorile primite de la client, rezultatul returnand-ul catre client.
else if (buttonclick.equals("cautare")) {
cauta = dis.readUTF();
if (cauta.equals("Marca")) {
dos.writeUTF("Cautare dupa marca autovehiculului: \n");
marcaa = dis.readUTF();
rs = st.executeQuery("select * from parcul where Marca = '" + marcaa + " ' ");
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata");
}
else if (cauta.equals("Model")) {
dos.writeUTF("Cautare dupa modelul autovehiculului: \n");
modell = dis.readUTF();
rs = st.executeQuery("select * from parcul where Model = '" + modell + " ' ");
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata");
}
else if (cauta.equals("An")) {
dos.writeUTF("Cautare dupa anul autovehiculului: \n");
ann = dis.readUTF();
rs = st.executeQuery("select * from parcul where An = '" + ann + " ' ");
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata");
}
else if (cauta.equals("Pret")) {
dos.writeUTF("Cautare dupa pretul autovehiculului: \n");
prett = dis.readUTF();
rs = st.executeQuery("select * from parcul where Pret = '" + prett + " ' ");
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata");
}
else if (cauta.equals("Nota")) {
dos.writeUTF("Cautare dupa nota autovehiculului: \n");
notaa = dis.readUTF();
rs = st.executeQuery("select * from parcul where Nota = '" + notaa + " ' ");
String totul;
while (rs.next()) {
totul = "";
String marca = rs.getString("Marca");
String model = rs.getString("Model");
String an = rs.getString("An");
String pret = rs.getString("Pret");
String nota = rs.getString("Nota");
totul = totul +" "+ marca +" " + model+" "+an+" "+pret+" "+nota;
dos.writeUTF(totul);
} dos.writeUTF("Gata");
} }
4.2 Exemple creare aplicație
Figura 4.2.1 Driverele ODBC Conectarea bazei de date la o sursă ODBC prin înregistrarea ei la driverul ODBC.
Figura 4.2.2 Driverele ODBC pe 32 bit.
În cazul în care folosim Windows 64 bit, nu vom reuși să înregistrăm baza de date la ODBC prin prima variantă(prezentată anterior). Driverul este valabil doar pentru 32 bit. Windows 64 , din fericire, ne permie să pornim ODBC Dată Source și pe 32 de bit. Pentru acest lucru este nevoie să ajungem la directorul “SysWOW64” unde regăsim aplicația odbcad32.
Fig. 4.2.3 Selectarea driverului – Aici vom putea identifica driverul bazei de date *.mdb.
Creearea campurilor bazei de date in Microsoft Access:
Fig. 4.2.4 Câmpurile bazei de date
Popularea bazei de date cu diverse valori:
Fig. 4.2.5 Popularea bazei de date
Creearea serverului RMI:
Fig. -> functia de start a serverului;
Fig. -> crearea interfetei grafice a serverului;
Fig. -> crearea interfetei grafice a serverului;
Creearea clientului RMI:
Fig. -> crearea clientului prin RMI (declararea unui ActionListener);
Fig. -> creearea interfetei utilizatorului (header-ul);
Fig. -> creeare interfetei utilizatorului
Creearea serverului utilizand clasele Socket si ServerSocket;
Fig. -> O parte din codul sursa al serverului;
Creearea clientului utilizand clasele Socket si ServerSocket:
Fig. -> creearea header-ului interfetei grafice a clientului
Fig. -> creearea interfetei grafice a clientului
Fig. -> Interfata cu utilizatorul
4.4 Rularea aplicației
Programul rulează pe orice sistem de operare ce suportă limbajul JAVA. Este necesar ca OS-ul folosit să aibe instalat o versiune de Java.
Primul pas pentru rularea aplicației reprezintă înregistrarea bazei de date la driverul ODBC-JDBC. În cazul în care folosim un sistem de operare pe 32 de biți, înregistrarea se poate face accesând Control Panel -> Administrative Tools -> Dată Sources (ODBC) cu privilegii de administrator. În tabul System DNS, adăugăm driverul Microsoft Access Driver (*mdb, *accdb).
Fig. 4.4.1 -> înregistrarea driverului.
În această fereastră avem mai multe tipuri de drivere ce pot fi instalate în funcție de bază de date sau de aplicația utilizată.După selectarea driverului corespunzător bazei de date folosite, este necesar să introducem calea până la baza de date utilizată (în cazul nostru parcul auto), precum și un nume.
Fig. 4.4.2 -> Selectarea path-ului către BD
În cazul în care folosim un sistem de operare pe 64 de biți, driverul îl putem regăsi în folderul sistemului de operare (Windows), apoi în SysWOW64.Aici vom regăsi aplicația odbcad32, ce este identică cu cea prezentat mai sus.
Aplicația poate fi rulata printr-o linie de comandă (cmd) sau prin folosirea unei aplicații specifice limbajului Java. Este recomandată folosirea aplicației NetBeans pentru o vizualizare mai clară și mai rapidă a rezultatelor.
Aplicația este realizată prin două metode:
a) RMI (Remote Method Invocation)
b) Utilizând clasele Socket si ServerSocket
a) RMI (Remote Method Invocation)
Programul este alcătuit din 5 clase :
• Clasa serverului : Auto
• Clasa parcul (folosită pentru a simplifica programul)
• Clasa I_BD (interfața RMI a serverului)
• Clasa clientului: clientul
• Clasa guiserver (interfața grafică pentru pornire, oprirea serverului -> accesului către baza de date).
În primul rând este necesară pornirea clasei guiserver. Prin această clasă putem porni sau opri serverul de access către baza de date.
În următoarea imagine este apelata clasa guiserver. Aceasta pornește cu serverul oprit.
Fig. 4.4.3 -> Aplicația prin care pornim sau oprim serverul
Dacă se efectuează un click pe butonul “Start” aplicația va apela funcția startserver din clasa serverului, Auto. Dacă se efectuează un clik pe butonul “Stop”, aplicația va apela funcția disconnect din aceeași clasă a serverului, Auto.
În momentul când apăsam unul din aceste butoane, o imagina își va schimba culoarea, pentru a putea identifica ce stare deține serverul în acel moment. Astfel, în momentul când imaginea este albastră, serverul este pornit, altfel, dacă imaginea este roșie, serverul este oprit.
Fig. 4.4.4 -> Serverul este pornit în acest moment.
Bineînțeles, serverul trebuie să fie pornit pentru a putea face interogări asupra bazei de date printr-un client.După acest pas, este necesar să rulăm clasa clientul. Astfel se va afișa următoarea fereastră:
Fig. 4.4.5 -> Interfața clientului;
După cum se poate vedea, interfața are mai multe butoane și câmpuri de introducere a datelor.
De asemenea, deține și un câmp pentru afișarea rezultatelor (rezultatelor primite în urma interogărilor către baza de date).
Fig. 4.4.6 -> Interfața clientului
În figură 4.4.6 putem vizualiza mai clar interfața clientului. Aceasta este formată dintr-un meniu ce include 3 butoane: File, Options și About (încadrate în chenarul galben). De asemenea în cadranul roșu avem campurie de introducere a datelor : Marca, Modelul , Anul de fabricație, Prețul și Notă. În chenarul albastru sunt prezente butoanele, iar în chenarul alb câmpul de afișare a rezultatelor.
Fig. 4.4.7 -> Meniul interfeței
Așa cum s-a precizat anterior, în chenarul galben din figura 4.4.6 avem meniul aplicației format din 3 butoane. Primul buton, File, conține un singur buton de tip meniu: Exit care închide aplicația. Options conține 2 alte butoane de tip meniu: Help și Open DB. La selectarea opțiunii Help, se va deschide un document cu instrucțiuni de rulare, folosire a aplicației precum și diverse informații (pentru deschidere este necesară aplicația Acrobat Reader). La selectarea opțiunii Open DB, se va deschide Baza de date. Pentru deschiderea bazei de date este necesară aplicația Microsoft Access.
Buton About are un o singură opțiunea : About, care reda anumite informații despre aplicație și creatorul aplicației într-o nouă fereastră.
Fig. 4.4.8 -> Informații despre aplicație
Câmpurile de text aflate deasupra celorlalte elemente grafice, sunt folosite pentru înregistrarea datelor introduse de client.
Aplicația deține 6 butoane principale:
Fig. 4.4.9 -> Butoane aplicație
Primul buton este butonul de conectare la server. Este necesar să activăm acest buton pentru a ne conecta server, după care putem lucra pe bază de date.
De asemenea, după apăsarea butonului, vom primi un mesaj:
“Conexiunea realizată cu succes” în cazul în care este totul în ordine, sau “Nu s-a putut accesa baza de date!” în cazul în care apare o problemă (ca de exemplu nu este pornit serverul).
Fig. 4.4.10 -> Butonul conectare
Cel de-al doilea buton este butonul de introducere autovehicul în baza de date.
Este necesar să completăm câmpurile Marca, Model, An, Preț, Nota apoi să apăsăm acest buton pentru că baza de date să înregistreze încă un element.
Fig. 4.4.11 -> Introducerea unui element în baza de date
Fig. 4.4.12 -> Afișarea elementelor și vizualizarea elementului introdus
După cum se poate vedea în figură 4.4.12 elementul introdus a fost adăugat în baza de date.
Elementele bazei de date pot fi vizualizate folosind butonul afișare (ultimul buton).
În momentul acționării acestui buton, se afișează absolut toată baza de date în câmpul text din aplicație.
Cel de-al treilea buton șterge un element în baza de date. Este necesar să introducem elementul pe care dorim să-l eliminăm din baza de date complet (mai exact, trebuie să
completăm toate câmpurile text pentru eliminare).
În cazul în care ștergerea elementului s-a realizat cu succes vom primit mesajul “Element eliminat din baza de date!”.
Acest buton este butonul de actualizare a unui element din baza de date. Este necesar să introducem marca și modelul autevehicului apoi celelalte elemente pentru a fi actualizat.
Fig. 4.4.13 – > Căutarea în baza de date
Ultimul buton este butonul de căutare. Această căutare se poate face după marca , model, an, preț sau nota. Este necesar să bifăm după ce se dorește căutarea și să introducem în câmpul text al celui bifat o valoare. Se vor întoarce toate valorile din baza de date ce au elementul bifat de aceeași valoare cu cel introdus de utilizator.
Fig.4.4.14 -> Exemplu de căutare în baza de date.
După ce va afișa toate valorile, se va afișa și mesajul “Searching completed”. În figură 4.4.14 se poate vizualiza afișarea după nota 9.
b) Utilizând clasele Socket si ServerSocket
Fig. -> Interfata clientului
Pentru a accesa baza de date, este nevoie, bineinteles, de pornirea serverului conectat la acea baza de date. Serverul se porneste fie din aplicatia NetBeans (aplicatie unde s-a creat programul), fie dintr-o linie de comanda. Dupa pornirea serverului, se pot porni mai multe fire de executie, mai exact, mai multi clienti. Toti clientii vor folosi interfata din figura 3.
Ca si versiunea RMI, aceasta interfata detine mai multe elemente grafice: tabele text, butoane, meniu etc.
Fig. -> Interfata clientului
In partea de sus (in chenarul verde) avem meniul aplicatiei. Meniul cuprinde 3 optiuni fiecare avand o suboptiune. In optiunea File regasim butonul Exit pentru parasirea aplicatiei.
In optiunea Options, regasim 2 butoane: -> HELP buton care porneste acest pdf pentru a venit in ajutorul utilizatorilor aplicatiei;
-> Open DB – Open DataBase, buton care deschide baza de date (in Microsoft Access).
Ce-a de-a 3-a optiune prezinta doar un singur buton, si anume butonul About, unde sunt trecute informatii despre creatorul aplicatiei, locul si data crearii acesteia.
In centrul aplicatiei este un TextField, un camp unde sunt afisate valorile din baza de date in functie de actiunile utilizatorului.
Aici vor fi afisate toate elementele bazei de date in cazul in care utilizatorul apasa butonul SHOW, sau doar elementele cautate daca se apasa SEARCH dupa un anumit camp.
In partea de jos avem campurile de introducere a elementelor:
In primul camp, utilizatorul este nevoit sa introduca marca autovehiculului, in cel de-al doilea modelul, apoi anul, pretul si bineinteles, nota.
Aplicatia foloseste 5 butoane: SEARCH, SHOW, UPDATE, INSERT si DELETE;
Butonul SHOW afiseaza toate elementele bazei de date;
Dupa ultimul element din baza de date, se va afisa mesajul : “Toate valorile bazei de date sunt afisare”.
Butonul UPDATE rescrie anul, pretul si nota unui autovehicul in functie de marca si moddelul selectat. Utilizatorul trebuie sa introduca in campul Marca : marca autovehiculului ce se doreste a fi modificat precum si modelul acelui autovehicul in campul Model. Dupa acest lucru, utilizatorul introduce noile valori ale autovehiculului respectiv (An, Pret si Nota) si apasa butonul UPDATE. Clientul va trimite aceasta cerere catre server, iar serverul va modifica in baza de date pe baza aceste cereri si va trimite inapoi catre client raspusul :”Elementul a fost actualizat!”.
Butonul INSERT insereaza un nou autovehicul in baza de date, iar DELETE sterge autovehiculul cu Marca si Modelul introduse de la tastatura din baza de date.
In urma inserarii se va afisa mesajul : “Element introdus in baza de date! “
In urma stergerii se va afisare mesajul : “Elementul a fost sters din baza de date! “.
Butonul SEARCH cauta in baza de date si returneaza toate valorile cautate de utilizator. Mai precis, utilizatorul trebuie sa introduca o valoare in campul dupa care se doreste cautarea.
Dupa aceea, este nevoie sa aleaga dupa ce se efectueaza cautarea : dupa Marca, dupa Model, dupa Pret, Nota sau Anul de fabricatie:
Dupa ce se alege o valoare din campul de selectare, se va afisa un mesaj:
Pentru inchiderea aplicatiei, clientul poate apasa buton X situat in dreapta sus , sau poate utiliza butonul EXIT din optiunea FILE a meniului.
RMI vs. Socket și ServerSocket
Pentru a scoate in evidenta atat avantajele cat si dezavantajelor acestor metode este important sa amintim cateva informatii despre fiecare.
Prin socket si Serversocket se intelege un flux de comunicatie bidirectionale bazata pe adresele unice IP. Se folosesc pe langa adresele IP si porturi, astfel un socket reprezentand o interfata intre aplicatia si implementarea protocolului TCP/IP sau UDP pe un sistem de calcul.
Astfel, cand o informatie vine pe retea, sistemul de operare stie carui proces ii apartine acea informatie.Socketurile au fost folosite pentru prima data in 1971, fiind definite in RFC 147.
5 ani mai tarziu, in 1976, s-a definit conceptul de RPC (fiind descris in RFC 707) – Remote Procedure Call. Un mare avantaj al acestei metode este faptul ca apelul metodelor la distanta nu e diferentiat de apelul metodelor locale, astfel programele devenind mult mai clare.
RMI sau Remote Method Invocation este un derivat al conceptului RPC. Aceasta derivare a avut loc deoarece s-a trecut la conceptul de programare orientata pe obiecte. RMI reprezinta declararea unui obiect remote, metodele sale fiind apelate de catre clienti ca si metode locale. Astfel, RMI este foarte util daca dorim sa implementam anumite servicii gazduite pe un server ce se cere a fi accesibil unor clienti.
Avantajele metodei Socket si Serversocket sunt reprezentate de flexibilitatea unui program precum si de simplitatea acelui program. Chiar si asa, insa, realizarea unei conexiuni mai complexe prin socket-uri devine greoaie si necesita o mare atentie, deoarece orice greseala poate impiedica programul sa functioneaza normal sau chiar sa duca la blocarea acestuia.
Avantajele metodei RMI fata de Socket-rui si ServerSocket-uri sunt reprezentate de faptul ca RMI poate fi folosit de orice alt sistem de calcul apeland metodele acestuia ca si cum ar fi metode locale (este mult mai portabil). Astfel, un program mai complex devine mai simplu de implementat, iar debug-ul decurge mult mai usor. De asemenea, RMI-ul are un comportament dinamic (astfel, cand apare nevoia unui apel, un client doar i-a obiectul de la server si executa metodele dorite, iar daca se doreste o actualizare a solutiei este posibila actualizarea clasei remote de pe server fara a mai altera si clientii, care data viitoare for folosi versiunea actualizata). In plus, fata de socket-uri, RMI este mult mai sigur deoarece utilizeaza mecanismele de securitate implementate de Java.
Concluzii
Programul a fost realizat in limbajul Java. S-a ales acest limbaj datorita simplitatii si performantei sale in comparatie cu alte limbaje de programare. De asemenea, este cel mai folosit limbaj orientat pe obiecte si poate cel mai portabil.
Programul “Lucrul cu baze de date la distanta” a fost creat prin doua metode diferite, fiecare metoda avand interfata grafica proprie. De asemenea, cele doua programe sunt create pe port-uri diferite, astfel acestea functioneaza in mod simultan, fara a avea vreo legatura una cu cealalta.
Metodele folosite sunt:
1) RMI
2) Socket si ServerSocket
In ceea ce priveste realizarea programului prin RMI, datorita avantajelor acestuia, implementarea acestei metode a fost mai facila decat prin Socketuri, astfel permitandu-mi sa lucrez mai mult asupra interfetei grafice. De asemenea, RMI-ul este o metoda care iti permite sa creezi aplicatii mult mai complexe, din motive deja mentionate. Un dezavantaj il reprezinta intelegerea modului de functionare a acestei metode, deoarece necesita anumite cunostine ale limbajului de programare Java.
In ceea ce priveste realizarea programului prin Socket-uri si ServerSocket-uri, chiar daca la prima vedere pare mai simplu, in cadrul unui program mai complex devine destul de complicat de implementat, de inteles si de testat. De asemenea, in cazul in care intampinam vreo eroare, vreo problema cu aplicatia, este dificila identificarea acelei probleme.
Ca o concluzie, ambele metode pot fi solutii posibile pentru implementarea unui program de comunicarea bidirectionale intre un server si un client. In cazul in care acel program este mai simplu, as alege metoda Socket-urilor, in cazul in care devine mai complex, insa, as prefera metoda RMI.
Bibliografie
[1]. GEORGESCU, Horia: Introducere in Universul JAVA, Editura Tehnica, Bucuresti, 2002
[2]. TANASA, Stefan; STEFAN, Andrei; OLARAU Cristian : Java de la 0 la Expert, Editura Polirom, ed. II, 2011
[3]. ECKEL, Bruce: Thinking in JAVA, Editura Prentice Hall, ed. IV, 2006
[4]. HAMAKER, Paul: Java Lessons, http://javalessons.com/cgi-bin/fun/java-tutorials-main
[5]. JAVA Language, http://en.wikipedia.org/wiki
[6]. JAVA Language, www.java.com
[7]. Srini Appikatla, Java learning begins here, http://www.javabeginner.com
Bibliografie
[1]. GEORGESCU, Horia: Introducere in Universul JAVA, Editura Tehnica, Bucuresti, 2002
[2]. TANASA, Stefan; STEFAN, Andrei; OLARAU Cristian : Java de la 0 la Expert, Editura Polirom, ed. II, 2011
[3]. ECKEL, Bruce: Thinking in JAVA, Editura Prentice Hall, ed. IV, 2006
[4]. HAMAKER, Paul: Java Lessons, http://javalessons.com/cgi-bin/fun/java-tutorials-main
[5]. JAVA Language, http://en.wikipedia.org/wiki
[6]. JAVA Language, www.java.com
[7]. Srini Appikatla, Java learning begins here, http://www.javabeginner.com
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Lucrul cu Baze de Date la Distanta Sub Limbajul Java (ID: 149983)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
