ODBC, driver, Connector/J, DataSource, DriverManager, JDBC API, JDBC Driver API, protocol, cursor sensitivity, cursor concurrency, cursor holdability, ResultSet, transaction, isolation level, dirty read, unrepeatable read, ghost read, commit, rollback, metadata, SQLException, SQLWarning, JdbcRowSet, CachedRowSet, FilteredRowSet, JoinRowSet, WebRowSet, conflicts, conflict resolution
JDBC (Java Database Connectivity) este o interfaţă de programare Java prin intermediul căreia pot fi manipulate informaţiile dintr-o sursă de date. Operaţiile pe care le pune la dispoziţie acest API sunt:
SELECT
, INSERT
, UPDATE
, DELETE
dar şi referitoare la informaţiile din dicţionarul de date), obţinerea rezultatelor aferente comenzilor realizate şi procesarea lor, inclusiv propagarea modificărilor realizate.Componentele pe care le include JDBC sunt:
java.sql
şi javax.sql
, incluse atât în platforma standard (Java SE) cât şi în platforma pentru implementarea aplicaţiilor de întreprinderi (Java EE); în mediile distribuite, există posibilitatea de interacțiune cu mai multe surse de date simultan;DriverManager
în care sunt definite obiectele ce pot conecta aplicaţiile Java la un “driver” JDBC; pachetele javax.naming
şi javax.sql
oferă posibilitatea realizării unei conexiuni către o sursă de date (obiect de tip DataSource
) înregistrată de către serviciul de nume Java Naming and Directory Interface (JNDI).sun.jdbc.odbc.JdbcOdbcDriver
, definind subprotocolul odbc; o astfel de soluție este adecvată în rețelele de calculatoare ale corporațiilor în care instalarea de aplicații pe diferite mașini din cadrul acestora nu reprezintă o problemă sau pentru arhitecturile care utilizează un server de aplicații.Arhitectura JDBC defineşte două modele de procesare pentru accesul la informaţiile din baza de date:
JDBC începe să fie adoptat pe scară largă datorită suportului pentru gestiunea paralelă a conexiunilor, tranzacţii distribuite precum şi posibilităţilor de procesare a informaţiilor deconectate de la sursa de date corespunzătoare.
Un “driver” de conectare la un sistem de gestiune al bazei de date reprezintă o bibliotecă prin care sunt transformate apelurile JDBC (din limbajul de programare Java) într-un format suportat de protocolul de reţea folosit de sistemul de gestiune al bazei de date, permiţând programatorilor să acceseze datele din medii eterogene.
Astfel, “driver-ul” pentru sistemul de gestiune al bazei de date realizează legătura între nivelul de logică a aplicaţiei şi nivelul de date (reprezentat prin baza de date propriu-zisă).
Există patru implementări pentru “drivere” JDBC:
Pentru driverele de tip 1 şi 2 este necesară existenţa unor biblioteci specifice pentru fiecare tip de bază de date, fiind necesar ca acestea să fie puse la dispoziţie de producătorii acestora.
Pentru driverul de tip 3 trebuie instalat un server de aplicaţii care comunică cu sistemul de gestiune pentru baze de date. De regulă acesta este configurat pentru a fi compatibil cu mai multe tipuri de baze de date, iar performanţele sale trebuie să compenseze timpul pentru transferul de informaţii de la şi către el.
Driverul de tip 4 este cel mai flexibil dintre toate întrucât nu necesită utilitare suplimentare, fiind şi independent de platformă.
Arhitectura protocolului Java Database Connectivity este structurată pe două niveluri:
O aplicaţie care gestionează informațiile dintr-o o bază de date folosind protocolul Java Database Connectivity trebuie să urmeze următorii paşi:
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
- existența driver-ului este evaluată la momentul în care se realizează compilarea;Class.forName(“com.mysql.jdbc.Driver”).newInstance()
- existența driver-ului este evaluată la momentul în care se realizează execuția, putând genera o excepție de tip NoClassDefFoundException
;Pentru conectarea la baza de date MySQL, se poate folosi Connector/J, driver nativ pentru Java dezvoltat de Oracle şi distribuit gratuit utilizatorilor.
Pentru utilizarea driver-ului de conectare din limbajul de programare Java împreună cu sistemul de gestiune pentru bazei de date MySQL trebuie descărcată arhiva care conţine Connector/J de la Download Connector/J, despachetată şi indicarea fişierului .jar din rădăcina sa la classpath în momentul în care se compilează aplicaţia.
%System Root%\Program Files [(x86)]\MySQL\Connector J 5.1
.
În linie de comandă, acest lucru poate fi realizat astfel:
C:\Users\Aipi2014> javac -classpath .;mysql-connector-java-5.1.33-bin.jar <nume_fisier>.java aipi2014@ubuntu:~$ javac -classpath .:mysql-connector-java-5.1.33-bin.jar <nume_fisier>.java
C:\Users\Aipi2014> java -classpath .;mysql-connector-java-5.1.33-bin.jar <nume_fisier>.java aipi2014@ubuntu:~$ java -classpath .:mysql-connector-java-5.1.33-bin.jar <nume_fisier>.java
Mai uşor, se pot folosi medii integrate de dezvoltare a aplicaţiilor, precum:
Conexiunea unei aplicaţii la sistemul de gestiune al bazei de date prin intermediul protocolului Java Database Connectivity se poate realiza prin intermediul a două clase:
DataSource
– metodă transparentă de acces la informaţii, un obiect având proprietăţi specificate astfel încât să corespundă unor surse de date particulare;DriverManager
– asigură accesul programului la o sursă de date specificată prin intermediul unui URL.DriverManager
apelează metode din interfaţa Driver
pentru a identifica driver-ul necesar pentru interacţiunea cu sistemul de gestiune al bazei de date. Astfel, este încărcat în mod automat orice driver JDBC >4.0 identificat în classpath care respectă proprietățile identificate.protocol:subprotocol:[nume_baza_de_date][lista_de_proprietati]
Câteva exemple de URL-uri specifice anumitor tipuri de baze de date (pentru o bază de date denumită bookstore
, la care datele de autentificare sunt utilizatorul root
cu parola StudentAipi2014
) sunt:
jdbc:mysql://[host][,failoverhost ...][:port]/[database][?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]... jdbc:mysql://localhost:3306/bookstore?user=root&password=StudentAipi2014
Parametrul failoverhost
indică adresa unei mașini pe care se află o copie a bazei de date, la care se poate realiza conexiunea, în situația în care nu s-a reușit stabilirea unei legături către mașina indicată de parametrul host
.
jdbc:derby:[subprotocol:][databaseName][;attribute=value]* jdbc:derby:bookstore;user=root;password=StudentAipi2014
subprotocol
, deși în general este omis, indică locația bazei de date (director din sistemul de fișiere, memorie, classpath, arhiva .jar);jdbc:oracle:[protocol]:@[database_host]:[port]:[instance] jdbc:oracle:thin:@localhost:1521:orcl
jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=path jdbc:sqlserver://address\\server:port;database=bookstore;user=root;password=StudentAipi2014;
Deschiderea unei conexiuni se face prin metoda statică getConnection
din DriverManager
, care întoarce un obiect conexiune (Connection
) la baza de date, acesta putând fi folosit ulterior pentru diferite interogări:
String DATABASE_CONNECTION = "jdbc:mysql://localhost:3306/bookstore"; Connection dbConnection = DriverManager.getConnection(DATABASE_CONNECTION);
În condiţiile în care se citesc dintr-o interfaţă grafică cu utilizatorul informaţii de tip utilizator şi parolă, stocate, de exemplu în obiectele username
şi password
,conexiunea poate fi obținută şi astfel:
Connection dbConnection = DriverManager.getConnection(DATABASE_CONNECTION, username, password);
Connection dbConnection = DriverManager.getConnection(DATABASE_CONNECTION+"?user="+username+"&password="+password);
Properties connectionProperties = new Properties(); connectionProperties.put("user",username); connectionProperties.put("password",password); Connection dbConnection = DriverManager.getConnection(DATABASE_CONNECTION, connectionProperties);
Dacă driverele gestionate de interfaţa Driver
recunosc URL-ul indicat drept parametru metodei getConnection
, se stabileşte o legătură cu sistemul de gestiune pentru baza de date, întorcându-se o conexiune care poate fi utilizată pentru transmiterea de instrucţiuni Java Database Connection translatate ulterior în interogări către baza de date.
Închiderea unei conexiuni, prin care sunt eliberate toate resursele asociate acesteia, se face prin metoda close
:
dbConnection.close();
Conform specificației Java Database Connectivity, pot fi create mai multe obiecte de tip interogare:
Statement
– folosite pentru interogări SQL fără parametri;PreparedStatement [extends Statement]
– folosite pentru interogări SQL precompilate care pot conţine parametri de intrare;CallableStatement [extends PreparedStatement]
– folosite pentru a executa rutine stocate care pot conţine parametri de intrare şi de ieşire.
Un obiect de tip interogare (Statement
) se obţine prin metoda createStatement()
a unui obiect de tip Connection
:
Statement statement = dbConnection.createStatement();
Începând cu JDBC 4.1 există posibilitatea definirii conexiunii într-un bloc try-with-resources
, ce eliberează toate resursele alocate în secţiunea respectivă în mod automat, indiferent dacă a fost generată sau nu o excepţie SQLException
:
try (Statement statement = dbConnection.createStatement()) { ... }
În continuare, obiectul de tip interogare poate fi utilizat pentru realizarea unei operaţii cu baza de date şi obţinerea unui set de date rezultat în urma executării instrucţiunii. Există mai multe moduri prin care se poate realiza execuţia unei interogări SQL:
execute()
: întoarce true
dacă primul obiect al interogării este de tipul ResultSet
; prin această metodă pot fi obţinute unul sau mai multe (sau nici un) obiect(e) de tipul ResultSet
; obiectele de tip ResultSet
pot fi accesate apelând metoda getResultSet()
a obiectului interogare; String query = "SELECT personal_identifier, first_name, last_name FROM user"; boolean result = statement.execute(query); if (result) ResultSet records = statement.getResultSet();
executeUpdate()
: întoarce un număr întreg având semnificaţia înregistrărilor afectate de expresia SQL; este folosită de regulă pentru instrucţiuni DML de tip INSERT
, UPDATE
, DELETE
, dar şi pentru instrucţiuni de tip DDL precum CREATE TABLE
, ALTER TABLE
, DROP TABLE
: String query = "CREATE TABLE series ( id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL, name VARCHAR(100) NOT NULL, description VARCHAR(1000) );"; int result = statement.executeUpdate(query); // ... String query = "INSERT INTO publishing_house (name, registered_number, description, town, region, country) VALUES('Grupo Planeta', '892687559', '-', 'Rome', 'Lazio', 'Italy');" int result = statement.executeUpdate(query);
Interfaţa ResultSet
pune la dispoziţia utilizatorului o serie de metode pentru lucrul cu informaţiile (seturile de date) obţinute în urma interogării bazei de date. Obiectele având tipul ResultSet
au anumite caracteristici care pot fi modificate între care tipul, gestiunea concurenţei şi posibilitatea de deţinere a cursorului. Caracteristicile pot fi precizate de utilizator în momentul creării unui obiect de tip interogare (Statement
).
Cu privire la modalitatea în care poate fi manipulat cursorul (aspecte ce ţin şi de senzitivitatea cursorului), există următoarele constante:
TYPE_FORWARD_ONLY
(implicit) – cursorul se poate muta doar înainte, ne-existând posibilitatea parcurgerii în ambele sensuri a setului de date obţinut ca rezultat al interogării;TYPE_SCROLL_INSENSITIVE
– cursorul se poate muta înainte şi înapoi, poziţionându-se în diferite locaţii relative faţă de poziţia curentă sau absolute, dar nu este afectat de modificările realizate de alţi utilizatori în timp ce este utilizat; conţine înregistrările care satisfac condiţiile interogării atunci când aceasta este executată sau pe măsură ce sunt obţinute entităţile; TYPE_SCROLL_SENSITIVE
– cursorul se poate muta înainte şi înapoi, poziţionându-se în diferite locaţii relative faţă de poziţia curentă sau absolute, şi este afectat de modificările realizate de alţi utilizatori.ResultSet
. Se poate utiliza metoda DatabaseMetaData.supportsResultSetType
pentru a verifica dacă tipul respectiv este suportat sau nu.
Tipul de concurenţă indică operaţiile pe care utilizatorul are permisiunea de a le realiza:
CONCUR_READ_ONLY
(implicit) – utilizatorul are doar dreptul de a consulta informaţiile, fără a le modifica;CONCUR_UPDATABLE
– utilizatorul poate citi şi poate scrie informaţiile reţinute în setul de date rezultat.DatabaseMetaData.supportsResultSetConcurrency
pentru a verifica dacă această caracteristică este suportată sau nu.
Deţinerea cursorului la realizarea tranzacţiilor (comportamentul cursorului în momentul în care se apelează metoda commit
) se face prin constantele:
HOLD_CURSORS_OVER_COMMIT
– cursorul nu este închis în momentul în care este apelată metoda commit
; un astfel de comportament este necesar atunci când obiectele de tip ResultSet
sunt folosite mai mult pentru citire decât pentru scriere;CLOSE_CURSORS_AT_COMMIT
– cursorul este închis după ce este apelată metoda commit()
; un astfel de comportament poate genera performanţe mai bune pentru unele aplicaţii.
Comportamentul implicit referitor la deţinerea cursorului în cazul tranzacţiilor depinde de sistemul de gestiune pentru baze de date şi poate fi verificat prin metoda DatabaseMetaData.getResultSetHoldability()
.
DatabaseMetaData.supportsResultSetHoldability
spre a verifica dacă un anumit comportament este suportat sau nu.
În cazul în care se doreşte modificarea ordinii în care sunt parcurse înregistrările, obiectul de tip ResultSet
dispune de o metodă setFetchDirection()
prin care se sugerează direcţia de obţinere a tuplurilor corespunzătoare:
FETCH_FORWARD
(implicit) – de la prima înregistrare la ultima;FETCH_REVERSE
– de la ultima înregistrare spre prima;FETCH_UNKNOWN
– ordinea de parcurgere este necunoscută.
Un exemplu de creare a unui obiect de tip interogare realizat pentru obţinerea unui set de date în care cursorul poate fi mutat în ambele direcţii, dar nu poate fi modificat setul de date, menţinând cursorul după realizarea unei tranzacţii prin metoda commit()
este:
Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
Un obiect de tip ResultSet
conţine mai multe (sau nici un) tuplu(ri), în funcţie de condiţiile interogării, având asociat un cursor care indică la orice moment înregistrarea curentă. Iniţial, cursorul se găseşte deasupra primei înregistrări.
ResultSet
este diferit de obiectul de tip cursor definit în MySQL în contextul unei rutine stocate pe server.
Câteva dintre metodele care pot fi utilizate pentru a realiza poziţionări ale cursorului în cadrul setului de date sunt:
metoda | descriere |
---|---|
next() | mută cursorul pe înregistrarea următoare |
previous() | mută cursorul pe înregistrarea precedentă |
first() | mută cursorul pe prima înregistrare |
last() | mută cursorul pe ultima înregistrare |
beforeFirst() | mută cursorul înainte de prima înregistrare |
afterLast() | mută cursorul după prima înregistrare |
relative(int n) | mută cursorul la n poziţii distanţă faţă de poziţia curentă |
absolute(int n) | mută cursorul la poziţia n (absolută) din set |
De regulă, metodele întorc rezultate de tip boolean
, având valoarea true
dacă s-a reuşit poziţionarea dorită şi false
în caz de eşec sau în situaţia în care setul de date nu conţine înregistrări.
În cazul în care tipul cursorului este cel implicit (TYPE_FORWARD_ONLY
), nu se poate apela decât metoda next
.
Obţinerea informaţiilor (valorilor asociate atributelor) se realizează prin metode de tip getter (getString()
, getInt()
, getByte()
, getBoolean()
, getBlob()
, getDate()
) care pot primi ca parametru fie numele (respectiv aliasul) coloanei fie indexul ei în cadrul tabelei din baza de date (această metodă fiind mai eficientă, numerotarea făcându-se începând cu valoarea 1).
getString()
poate fi folosită pentru preluarea oricărui tip de informaţie din baza de date, mai puţin tipul SQL3.
Metodele de tip getter nu ţin cont de capitalizarea şirului de caractere care este oferit drept parametru. Dacă există mai mult de o denumire (sau alias) de coloană care are denumirea respectivă, este întoarsă valoarea corespunzătoare primului atribut identificat. Metoda ar trebui folosită în cazul în care denumirile (sau aliasurile) coloanelor sunt specificate explicit în interogare, nu şi în situaţia când interogarea are forma SELECT * FROM …
.
O rutină de parcurgere a înregistrărilor dintr-o bază de date poate fi:
ResultSet result = statement.executeQuery("SELECT name, registered_number FROM publishing_house"); while (result.next()) { String name = result.getString(1); float registeredNumber = result.getFloat("registered_number"); }
Pentru introducerea de informaţii într-o bază de date folosind un obiect de tip ResultSet
se pot folosi metodele moveToInsertRow()
(care mută cursorul la poziţia corespunzătoare din setul de date - în fapt o zonă de memorie în care poate fi construită o nouă înregistrare înainte de a fi adăugată propriu-zis în tabelă) urmată de specificarea atributelor în acelaşi mod ca pentru o oricare actualizare (folosind metode de tip update…()
) pentru ca ulterior adăugarea să fie realizată prin metoda insertRow()
.
Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet result = statement.executeQuery("SELECT * FROM writer"); result.moveToInsertRow(); result.updateString(1,"Sterne"); result.updateString(2,"Laurence"); result.insertRow();
După introducerea înregistrării în setul de date, este recomandată mutarea cursorului pe o altă poziţie, întrucât realizarea altor operaţii asupra obiectului ResultSet
pot avea rezultate imprevizibile în condiţiile în care cursorul indică asupra valorii care a fost adăugată în tabelă. Frecvent, este apelată metoda beforeFirst()
astfel încât cursorul să se găsească în aceeaşi stare ca cea ulterioară creării sale.
Procesul de actualizare a informaţiilor într-o bază de date printr-un obiect de tip ResultSet
este realizat în două etape:
updateString()
, updateInt()
, updateByte()
, updateBoolean()
, updateBlob()
, updateDate()
; la acest moment, nici o modificare nu este realizată la nivelul tabelei;updateRow()
.Un exemplu de actualizare a informaţiilor în baza de date este:
Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet result = statement.executeQuery("SELECT issue_date, state FROM invoice"); GregorianCalendar today = new GregorianCalendar(); today.setTime(new Date()); while (result.next()) { GregorianCalendar issueDate = result.getDate(issue_date); if (issueDate.before(today)) result.updateString(state,'overdue'); updateRow(); }
cancelRowUpdates()
, înainte însă de a apela metoda updateRow()
.
O înregistrare poate fi ştearsă folosind metoda deleteRow()
.
Atunci când nu toate datele interogării sunt cunoscute la momentul în care este compilată aplicaţia (sau atunci când se impune reutilizarea frecventă a unei interogări, cu diverși parametri), există posibilitatea ca interogarea să fie generică, urmând a fi completată cu informaţii (provenite dintr-o sursă de date sau introduse chiar de către utilizator) atunci când ele sunt disponibile, şi anume la rulare, înainte de execuţia interogării asupra bazei de date.
În momentul în care acestea sunt create, interogările parametrizabile primesc în mod necesar o parte din interogarea propriu-zisă, transmisă sistemului de gestiune al bazei de date care îl precompilează, astfel încât execuţia sa va fi mai rapidă. În momentul în care va fi executată, ea va fi rulată de către sistemul de gestiune pentru baze de date fără a mai fi compilată.
Sunt folosite obiecte de tip PreparedStatement
, derivate din clasa Statement
, informaţiile necunoscute fiind specificate prin caracterul ?
:
String query = "UPDATE user SET type = ? WHERE role = ? "; PreparedStatement preparedStatement = dbConnection.prepareStatement(query);
Înainte de a executa o astfel de interogare, trebuie specificate valorile care corespund atributelor lipsă, lucru care se face prin metode de tip setter:
preparedStatement.setString(1, Integer.parseInt(buffer.readLine()); preparedStatement.setDate(2, Date.valueOf(buffer.readLine());
Execuţia interogării se face folosind metodele specifice clasei Statement
.
preparedStatement.executeUpdate();
De această dată, metodele nu vor mai primi ca parametru comanda SQL, întrucât aceasta a fost deja asociată în momentul în care a fost creat obiectul de tip interogare parametrizată.
Rezultatul metodei executeUpdate()
este o valoare întreagă având semnificaţia numărului de înregistrări care au fost actualizate. Semnificaţia unui rezultat nul este acela că interogarea nu a afectat nici o înregistrare din tabelă sau că instrucţiunea a fost de tip DDL.
Pentru apelarea unei rutine stocate, se folosesc obiecte din clasa CallableStatement
, derivată din PreparedStatement
:
String query = "{? = CALL calculate_invoice_value(?)}"; CallableStatement callableStatement = dbConnection.prepareCall(query); callableStatement.registerOutParameter(1, java.sql.Types.DECIMAL); callableStatement.setString(2,buffer.readLine()); callableStatement.execute(); double result = callableStatement .getDouble(1); callableStatement.close();
Valorile necunoscute (inclusiv rezultatul rutinei stocate, fie parametru, fie valoare întoarsă) sunt marcate în continuare prin caracterul ?
.
Pentru parametrii procedurilor, în cazul în care au tipul IN
sau INOUT
, trebuie specificată valoarea lor pentru ca rutina să poată fi executată. În plus, dacă aceştia au tipul OUT
sau INOUT
, trebuie specificat şi tipul de date aşteptat, folosind metoda registerOutParameter()
. Acelaşi comportament trebuie respectat şi pentru parametrii / rezultatele întoarse ale funcţiilor. Tipurile de date din interfaţa java.sql.Types
au aceeași denumire ca cele din MySQL.
Execuţia rutinei se face cu metoda execute()
, iar valorile întoarse sunt preluate indexat, prin metodele get…()
corespunzătoare.
Java Database Connectivity permite accesarea informaţiilor reţinute în dicţionarul de date, precum structura bazei de date şi a tabelelor ca şi restricţiile de integritate (chei primare, chei străine). Toate aceste informații sunt disponibile prin clasa DatabaseMetaData
, care se obţine pornind de la obiectul Connection
aferent bazei de date respective:
DatabaseMetaData dbMetaData = dbConnection.getMetaData();
Denumirea bazelor de date care pot fi accesate folosind conexiunea respectivă se obţine cu metoda getCatalogs()
, care întoarce un obiect de tip ResultSet
conţinând câte o singură înregistrare pentru fiecare rând, şi anume denumirea catalogului.
Pentru fiecare bază de date pot fi aflate descrierile tabelelor componente, acestea putând fi filtrate în funcţie de numele schemei sau al tabelei (oferindu-se modele pentru acestea) sau al tipului tabelei, prin metoda getTables
. Tipul tabelei poate fi TABLE
, VIEW
, SYSTEM TABLE
, GLOBAL TEMPORARY
, LOCAL TEMPORARY
, ALIAS
, SYNONYM
. În cazul când se doreşte obţinerea tuturor tabelelor dintr-o bază de date, toate aceste criterii pot fi marcate ca null, astfel încât se ignoră orice criteriu care ar fi putut limita rezultatele întoarse.
ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException
Obiectul de tip ResultSet
întors ca rezultat conţine descrierile tabelelor, constând în următoarele informaţii:
poziție | atribut | descriere |
---|---|---|
1 | TABLE_CAT | catalogul tabelei (poate fi null ) |
2 | TABLE_SCHEM | schema tabelei (poate fi null ) |
3 | TABLE_NAME | denumirea tabelei |
4 | TABLE_TYPE | tipul tabelei |
5 | REMARKS | comentariu explicativ asupra tabelei |
6 | TYPE_CAT | catalogul tipurilor (poate fi null ) |
7 | TYPE_SCHEM | schema tipurilor (poate fi null ) |
8 | TYPE_NAME | numele tipului (poate fi null ) |
9 | SELF_REFERENCING_COL_NAME | numele identificatorului desemnat al unei tabele de un anumit tip (poate fi null ) |
10 | REF_GENERATION | specifică modul în care sunt create valorile din SELF_REFERENCING_COL_NAME – SYSTEM , USER , DERIVED (poate fi null ) |
Structura unei tabele se obţine prin metoda getColumns()
în care filtrarea rezultatelor se face după numele schemei, a tabelei şi a coloanelor, omiterea oricăruia dintre criterii făcându-se prin marcarea ca null a parametrului aferent:
ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException
Obiectul de tip ResultSet
întors ca rezultat conţine descrierile tabelelor, constând în următoarele informaţii:
poziție | atribut | descriere |
---|---|---|
1 | TABLE_CAT | catalogul tabelei (poate fi null ) |
2 | TABLE_SCHEM | schema tabelei (poate fi null ) |
3 | TABLE_NAME | numele tabelei |
4 | COLUMN_NAME | numele coloanei |
5 | DATA_TYPE | tipul de dată SQL (din java.sql.Types ) |
6 | TYPE_NAME | numele tipului de dată (dependent de sursa de date) |
7 | COLUMN_SIZE | dimensiunea coloanei ♦ valori numerice – precizia maximă ♦ şiruri de caractere – lungimea (în caractere) ♦ date calendaristice – lungimea reprezentării ca şir de caractere ♦ reprezentare binară / tipul ROWID – dimensiunea (în octeţi)♦ null – N/A |
8 | BUFFER_LENGTH | nu este utilizat |
9 | DECIMAL_DIGITS | numărul de zecimale; null dacă nu este cazul |
10 | NUM_PREC_RADIX | baza (de obicei 10 sau 2) |
11 | NULLABLE | indică posibilitatea de a exista valori null în coloană♦ columnNoNulls – ar putea să nu permită null ♦ columnNullable – sigur permite null ♦ columnNullableUnknown – stare necunoscută |
12 | REMARKS | comentariu ce descrie coloana (poate fi null ) |
13 | COLUMN_DEF | valoarea implicită a coloanei (poate fi null ); pentru şirurile de caractere, aceasta va fi încadrată între caracterele ' şi '. |
14 | SQL_DATA_TYPE | nu este utilizat |
15 | SQL_DATETIME_SUB | nu este utilizat |
16 | CHAR_OCTET_LENGTH | pentru şiruri de caractere – numărul maxim de octeţi dintr-o coloană |
17 | ORDINAL_POSITION | indexul coloanei în cadrul tabelei (începând de la 1) |
18 | IS_NULLABLE | indică posibilitatea de a exista valori null în coloană potrivit regulilor ISO |
19 | SCOPE_CATALOG | catalogul tabelului spre care indică referinţa atributului (null dacă DATA_TYPE nu este REF ) |
20 | SCOPE_SCHEMA | schema tabelului spre care indică referinţa atributului (null dacă DATA_TYPE nu este REF ) |
21 | SCOPE_TABLE | numele tabelului spre care indică referinţa atributului (null dacă DATA_TYPE nu este REF ) |
22 | SOURCE_DATA_TYPE | sursa tipului de dată pentru un tip distinct sau pentru o referinţă generată de utilizator (null dacă DATA_TYPE nu este DISTINCT sau referinţă generată de utilizator) |
23 | IS_AUTOINCREMENT | indică dacă coloana este auto-incrementală |
24 | IS_GENERATED_COLUMN | indică dacă coloana este generată |
Alte metode importante din clasa DatabaseMetadata
sunt cele ce identifică rutinele stocate (funcţii şi proceduri): getFunctionColumns()
şi getProcedureColumns()
, ambele având definiţii similare:
ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException
ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException
Pentru o rutină stocată se întorc mai multe intrări în ResultSet
, corespunzând parametrilor de ieşire şi parametrilor de intrare:
IN
, OUT
, INOUT
;java.sql.Types
asociat (6);null
(12);null
conform regulilor ISO (16).
Cheile primare ale unei tabele pot fi obţinute prin metoda getPrimaryKeys()
:
ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException
null
), vor fi întoarse toate cheile primare din baza de date respectivă.
Denumirea coloanei ce reprezintă cheia primară poate fi obţinut de pe poziţia a patra, împreună cu poziţia pe care o ocupă în cheia primară compusă (într-un astfel de caz) ca şi denumirea pe care o are constrângerea de tip cheie primară.
De asemenea, se poate genera în mod automat un identificator unic pentru o tabelă, folosind metoda getBestRowIdentifier()
. În acest caz, va trebui specificat şi un scop care precizează nivelul la care va fi utilizat identificatorul unic (ale cărui valori pot fi bestRowTemporary
, bestRowTransaction
, bestRowSession
).
Constrângerile de tip cheie străină pot fi identificate în ambele sensuri, astfel că sunt definite:
getExportedKeys()
);getImportedKeys()
). ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException
ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException
Metoda întoarce un set de date conţinând descrierea cheii străine şi anume:
UPDATE
(10) şi DELETE
(11), acestea putând avea valorile:importedNoAction
(nu permite realizarea de modificări asupra unei chei primare care este referită);importedKeyCascade
(propagă modificările asupra cheii primare la nivelul cheii străine);importedKeySetNull
(valoarea cheii străine se schimbă în null
dacă valoarea cheii primare referite se schimbă);importedKeySetDefault
(valoarea cheii străine devine cea implicită în cazul modificării cheii primare);importedKeyRestrict
(la fel cu importedNoAction
)).commit
(14).
Clasa DatabaseMetaData
pune la dispoziţie şi alte metode pentru verificarea capabilităţilor pe care le are driver-ul Java Database Connectivity utilizat.
În momentul când se produc erori în cazul interacţiunii cu o sursă de date este generată o excepţie de tip SQLException
care oferă următoarele informaţii:
getMessage()
a obiectului de tip eroare asociat;getSQLState()
a obiectului de tip eroare asociat;Throwable
care au determinat excepţia SQLException
; lanţul cauzal poate fi parcurs recursiv apelând metoda getCause()
până când este returnată o valoare null
; Throwable cause = exception.getCause(); while (cause != null) { System.out.println("Cauza: "+cause); cause = cause.getClause(); }
getNextException()
.
Clasa SQLException
are mai multe subclase, corespunzând unor excepţii care sunt generate în situaţii particulare, ceea ce face procesul de gestiune a erorilor mult mai portabil.
Avertismentele, reprezentate de obiecte din clasa SQLWarning
, nu opresc execuţia aplicaţiei, informând totuşi utilizatorul ca una sau mai multe operaţii nu s-au desfăşurat aşa cum ar fi trebuit. Un avertisment poate fi raportat pentru obiecte de tip Connection
, Statement
(PreparedStatament
/ CallableStatement
) sau ResultSet
, fiecare dintre acestea dispunând de o metodă getWarnings()
care întoarce un rezultat de tip SQLWarning
. În cazul în care nu este null
, acesta dispune de o metoda getNextWarning()
ce indică şi alte avertismente. Totusi, atunci când se execută o instrucţiune, avertismentele de la instrucţiunea precedentă se pierd în mod automat.
Metodele pe care le pune la dispoziţie clasa SQLWarning
sunt: getMessage()
, getSQLState()
şi getErrorCode()
.
Cel mai frecvent avertisment este de tip DataTruncation
ce indică faptul că tipul de date folosit pentru obţinerea unui rezultat nu este cel corespunzător. Un astfel de obiect oferă posibilitatea investigării coloanei asupra căreia s-a produs eroarea, dacă aceasta corespunde unei operaţii de scriere sau de citire, câţi octeţi ar fi trebuit transferaţi şi câţi au fost transferaţi efectiv.
Java Database Connectivity permite ca mai multe instrucțiuni SQL care nu produc un rezultat de tip ResultSet
să poată fi executate împreună în mod atomic:
void addBatch(String sql) throws SQLException
void clearBatch() throws SQLException
int[] executeBatch() throws SQLException
ResultSet
sau în cazul în care una dintre operaţiile tranzacţiei nu a fost executată cu succes, se va genera excepţia BatchUpdateException
.CREATE TABLE
, ALTER TABLE
, DROP TABLE
), respectiv DML (INSERT
, UPDATE
, DELETE
).
Metoda executeBatch()
întoarce un vector care conţine numărul operaţiilor de tip actualizare realizate cu succes.
După metoda executeBatch()
aplicată unui obiect interogare, se apelează şi metoda commit()
, astfel încât modificările să fie vizibile în cadrul bazei de date. Este important ca la începutul tranzacţiilor să se apeleze Connection.setAutoCommit(false)
pentru a nu se produce modificări în baza de date până când acest lucru nu este specificat explicit prin metoda commit()
. La sfârşitul tranzacţiilor se poate restabili comportamentul implicit (în care fiecare instrucţiune este considerată ca fiind o singură tranzacţie), apelându-se Connection.setAutoCommit(true)
.
dbConnection.setAutoCommit(false); Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); for (ArrayList<String> row:table) { String query = "INSERT INTO book VALUES ("; for(String column: row) query += column+","; query+= ")"; dbConnection.addBatch(query); } int[] result = statement.executeBatch(); dbConnection.commit(); dbConnection.setAutoCommit(true);
Utilizarea tranzacţiilor este şi un mecanism prin care este menţinută integritatea datelor, în contextul accesului concurent. Astfel, în timpul execuţiei unei tranzacţii, sunt specificate drepturile de acces la nivelul tabelei pentru alţi utilizatori care doresc să opereze pe acelaşi set de date. Acestea pot fi specificate prin metoda setTransactionInsolation()
aplicabilă unui obiect de tip Connection
.
Nivel Izolare | Tranzacţii | Citiri murdare | Citiri ne-repetabile | Citiri fantomă |
---|---|---|---|---|
TRANSACTION_NONE | nu | N/A | N/A | N/A |
TRANSACTION_READ_UNCOMMITTED | da | permise | permise | permise |
TRANSACTION_READ_COMMITTED | da | prevenite | permise | permise |
TRANSACTION_REPEATABLE_READ | da | prevenite | prevenite | permise |
TRANSACTION_SERIALIZABLE | da | prevenite | prevenite | prevenite |
DatabaseMetaData.supportsTransactionIsolationLevel()
spre a se verifica dacă este suportat nivelul în cauză.
Se consideră o citire “murdară” (eng. dirty read) o valoare a unui atribut care a fost actualizat dar pentru care nu s-a făcut încă commit()
pentru că există posibilitatea de a se reveni la valorile de dinaintea tranzacţiei.
O citire ne-repetabilă este aceea în care două tranzacţii, A şi B operează asupra aceleiaşi înregistrări (una pentru citire, una pentru scriere) şi în care valorile furnizate sunt diferite.
Similar, o citire fantomă se obţine în situaţia în care obţinerea rezultatelor presupune satisfacerea unei condiţii ce este îndeplinită ca urmare a actualizării astfel că o nouă interogare va furniza mai multe valori.
În contextul tranzacţiilor, se poate salva starea bazei de date înaintea realizării unor modificări, astfel încât dacă produc efecte nedorite la nivelul informaţiilor din tabele, să se poată reveni la informaţiile anterioare:
SavePoint state = dbConnection.setSavePoint(); ... dbConnection.rollback(state);
Metoda rollback()
încheie tranzacţia curentă, astfel încât aceasta va fi apelată întotdeauna la sfârşitul tranzacţiei. De regulă, o astfel de operaţie trebuie utilizată numai atunci când s-a generat o excepţie SQLException
în tranzacţia curentă, astfel încât nu se poate garanta care sunt valorile care au fost stocate în baza de date.
O stare a bazei de date salvată poate fi eliminată din cadrul tranzacţiei folosind metoda releaseSavePoint()
a obiectului de tip Connection
corespunzător.
Java Database Connectivity permite utilizarea unor obiecte de tip RowSet
, derivate din ResultSet
, care oferă programatorilor posibilitatea de a accesa datele mai uşor, având comportament de componente JavaBeans.
RowSetListener
care au fost asociate obiectului de tip RowSet
.
Astfel de obiecte sunt considerate conectate sau deconectate de la sursa de date, după cum menţin conexiunea (printr-un “driver”) la baza de date pe parcursul ciclului de viaţă:
JdbcRowSet
, care oferă o funcţionalitate asemănătoare cu ResultSet
;CachedRowSet
, WebRowSet
, JoinRowSet
şi FilteredRowSet
care se vor conecta la sursa de date doar pentru operaţii de citire şi de scriere, situaţie în care vor trebui să verifice şi conflictele care pot apărea; acestea au şi proprietatea că sunt serializabile ceea ce le face ideale pentru a fi transmise prin intermediul unei reţele de calculatoare.RowSet
atunci când sistemele de gestiune a bazelor de date nu implementează funcţionalitatea de parcurgere sau actualizare a obiectelor de tip ResultSet
, capabilităţi de care aceste clase dispun în mod implicit.
Obiectele JdbcRowSet
pot fi create folosind un obiect ResultSet
, Connection
, utilizând un constructor implicit sau dintr-o instanţă a clasei RowSetFactory
.
În toate aceste cazuri se va folosi clasa JdbcRowSetImpl
.
Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet result = statement.executeQuery("SELECT * FROM book"); JdbcRowSet jdbcRowSet = new JdbcRowSetImpl(result);
Obiectul de tip JdbcRowSet
este echivalent cu cel de tip ResultSet
, având acelaşi conţinut. În cazul când interogarea ar fi fost creată cu parametrii impliciţi obiectul corespunzător nu ar fi putut fi nici parcurs, nici actualizat.
JdbcRowSet jdbcRowSet = new JdbcRowSetImpl(dbConnection); jdbcRowSet.setCommand("SELECT * FROM book"); jdbcRowSet.execute();
Obiectul de tip JdbcRowSet
nu conţine nici un fel de date până la momentul când nu îi este asociată o instrucţiune SQL prin metoda setCommand
, apelată prin metoda execute()
. Implicit, un astfel de obiect poate fi parcurs, iar informaţiile din el pot fi actualizate. Astfel de comportamente pot fi specificate însă şi explicit.
Metoda execute()
realizează conexiunea cu baza de date folosind parametrii conexiunii respective, execută interogarea aferentă proprietăţii command
și gestionează stochează informaţiile din obiectul ResultSet
reţinut în obiectul de tip JdbcRowSet
.
JdbcRowSet jdbcRS = new JdbcRowSetImpl(); jdbcRS.setURL("jdbc:mysql://localhost:3306/bookstore"); jdbcRS.setUsername(username); jdbcRS.setPassword(password); jdbcRS.setCommand("SELECT * FROM book"); jdbcRS.execute();
Pentru fiecare obiect JdbcRowSet
se pot stabili proprietăţile (url
, username
, password
, dataSourceName
). O interogare se poate specifica folosind metoda setCommand()
, iar execuţia se face folosind execute()
, ca şi în cazul ResultSet
.
RowSetFactory rowSetFactory = RowSetProvider.newFactory(); JdbcRowSet jdbcRowSet = rowSetFactory.createJdbcRowSet(); jdbcRowSet.setURL("jdbc:mysql://localhost:3306/bookstore"); jdbcRowSet.setUsername(username); jdbcRowSet.setPassword(password); jdbcRowSet.setCommand("SELECT * FROM book"); jdbcRowSet.execute();
Obiectul de tip RowSetFactory
utilizează implementarea implicită, însă dacă driverul JDBC pune la dispoziţie o implementare proprie, aceasta poate fi utilizată ca parametru transmis metodei newFactory()
. Interfaţa RowSetFactory
conţine metode spre a crea diferite implementări RowSet
: createJdbcRowSet()
, createCachedRowSet()
, createFilteredRowSet()
, createJoinRowSet()
, createWebRowSet()
.
Un obiect JdbcRowSet
creat folosind constructorul implicit va avea următoarele proprietăţi:
type
: ResultSet.TYPE_SCROLL_INSENSITIVE
(poate fi parcurs);concurrency
: ResultSet.CONCUR_UPDATABLE
(poate fi actualizat);escapeProcessing
: true
(poate fi definită o sintaxă care marchează faptul că există un tip de cod care va fi procesat de baza de date);maxRows
: 0
(nu există limită cu privire la numărul de înregistrări);maxFieldSize
: 0
(nu există limită cu privire la numărul de octeţi pentru memorarea valorii unui atribut – aplicabil doar pentru atribute de tip BINARY
, VARBINARY
, LONGVARBINARY
, CHAR
, VARCHAR
şi LONGVARCHAR
);queryTimeout
: 0
(nu există nici o limită impusă referitoare la timpul de execuţie al interogării);showDeleted
: false
(înregistrările şterse nu sunt vizibile);transactionIsolation
: Connection.TRANSACTION_READ_COMMITTED
(pot fi citite numai datele pentru care s-a apelat metoda commit()
);typeMap
: null
(tipul asocierii unui obiect Connection
folosit de acest obiect de tip RowSet
este null
).
Modul în care pot fi manipulate datele folosind un obiect de tip JdbcRowSet
este similar cu cel al obiectelor de tip ResultSet
, metodele respective având aceeaşi sintaxă.
Interfaţa CachedRowSet
desemnează modul de operare deconectat, din ea fiind derivate interfeţele FilteredRowSet
, JoinRowSet
şi WebRowSet
. Denumirea sa indică faptul că datele sunt reţinute într-o zonă de memorie astfel încât procesările se fac pe acestea în loc de informaţiile reţinute în baza de date. Crearea unui obiect de tip CachedRowSet
se face folosind constructorul implicit (CachedRowSetImpl
) sau folosind o instanţă a RowSetFactory
.
Un obiect CachedRowSet
conţine implementarea implicită a SyncProvider
ce este un obiect de tip RIOptimisticProvider
. Acesta oferă un obiect RowSetReader
şi un obiect RowSetWriter
care sunt necesare atunci când trebuie citite sau scrise informaţii din sursa de date. Modul în care operează este transparent.
În cazul în care obiectul va fi folosit pentru actualizarea informaţiilor din baza de date şi se doreşte ca modificările să fie operate şi la nivelul acesteia, trebuie specificate coloanele ce identifică în mod unic setul de înregistrări respective, indicând indexul atributelor corespunzătoare
int[] keys = {1}; cachedRowSet.setKeyColumns(keys);
Obiectul CachedRowSet
nu este populat până nu este apelată metoda execute()
, moment în care obiectul RowSetReader
asociat realizează coneciunea la baza de date folosind proprietăţile corespunzătoare (url
, username
, password
, dataSourceName
), executând interogarea specificată în proprietatea command
. După ce sunt obţinute înregistrările necesare, conexiunea este închisă.
Pentru actualizarea informaţiilor reţinute (adăugare, modificare, ştergere) trebuie apelată metoda acceptChanges()
pentru ca procesările să fie vizibile la nivelul sursei de date. În acest moment, obiectul RowSetWriter
deschide conexiunea cu baza de date în care realizează operaţiile respective după care conexiunea este închisă. În cazul în care este detectat un conflict (informaţiile din sursa de date au fost actualizate între timp de un alt proces), este utilizată implementarea RIOptimisticProvider
a clasei SyncProvider
care foloseşte un model de concurenţă optimist, ce presupune faptul că nu vor exista conflicte sau că numărul acestora va fi redus. În situaţia în care nu sunt detectate conflicte, noile informaţii sunt transferate către baza de date, în cazul că există, actualizările sunt ignorate. Totuşi, există şi posibilitatea ca în cazul identificării unor conflicte, utilizatorul să poată selecta valorile reţinute în baza de date:
try { cachedRowSet.acceptChanges(); } catch (SyncProviderException syncProviderException ) { SyncResolver syncResolver = syncProviderException.getSyncResolver(); while (syncResolver .nextConflict()) { if (syncResolver.getStatus() == SyncResolver.UPDATE_ROW_CONFLICT) { int conflictedRow = syncResolver.getRow(); cachedRowSet.absolute(conflictedRow); int numberOfAttributes = cachedRowSet.getMetaData().getColumnCount(); for (int index = 1; index <= numberOfAttributes; index++) { if (syncResolver.getConflictValue(index) != null) { Object cachedRowSetValue = cachedRowSet.getObject(index); Object resolverValue = syncResolver.getConflictValue(index); // ... syncResolver.setResolvedValue(index,...); } } } } }
În cazul în care au fost detectate conflicte, metoda acceptChanges()
generează o excepţie de tipul SyncProviderException
care pune la dispoziţie obiectul SyncResolver
, un iterator pe conflictele identificate. De fapt, acesta este un obiect RowSet
care conţine doar valorile conflictuale ale unei înregistrări, restul atributelor având valoarea null
. Totodată, dispune de metode precum getStatus()
prin care se verifică tipul conflictului, getRow()
ce identifică indexul înregistrării la care se găseşte conflictul şi getConflictedValue()
ce reţine valoarea care a fost actualizată anterior şi marcată ca atare în baza de date.
Actualizările dintr-un obiect de tip CachedRowSet
pot fi notificate către alte obiecte care implementează interfaţa RowSetListener
, ceea ce presupune definirea metodelor:
cursorMoved()
– defineşte comportamentul obiectului ascultător în cazul când se produc modificări în privinţa cursorului obiectului CachedRowSet
;rowChanged()
– defineşte comportamentul obiectului ascultător în cazul când unul sau mai multe atribute dintr-o înregistrare sunt modificate, când este adăugată sau ştearsă o înregistrare din obiectul CachedRowSet
;rowSetChanged()
– defineşte comportamentul obiectului ascultător în cazul când obiectul CachedRowSet
este populat cu informaţii.
Un obiect ascultător poate fi asociat unui set de date CachedRowSet
prin metoda addRowListener()
. Oprirea notificărilor se face prin removeRowListener()
.
Obiectele de tip FilteredRowSet
oferă posibilitatea de a limita numărul de înregistrări vizibile conform unui criteriu şi de a selecta informaţiile ce pot fi consultate fără a realiza conexiuni la baza de date şi fără a opera modificări la nivelul interogării asociate.
Criteriul indicând înregistrările dintr-un obiect FilteredRowSet
care vor fi vizibile este precizat printr-o clasă ce implementează interfaţa Predicate
, precizând numele sau indexul coloanei după care se face filtrarea şi limitele între care trebuie să se găsească valorile. Clasa va trebui să definească metodele evaluate()
(primind o valoare de comparat şi numele sau indexul coloanei sau un obiect de tip RowSet
).
Asocierea unui filtru (criteriu) pentru un obiect FilteredRowSet
se face prin metoda setFilter()
care primeşte ca argument clasa care defineşte condiţiile respective. Filtrarea propriu-zisă are loc atunci când este apelată metoda next()
, ducând la execuţia metodei evaluate()
corespunzătoare. Există posibilitatea apelării mai multor filtre succesive prin apelarea metodei setFilter()
de mai multe ori, după ce anterior s-a produs selecţia valorilor dorite prin metoda next()
. De asemenea, eliminarea tuturor filtrelor asociate se face apelând metoda setFilter()
cu parametrul null
.
public class PriceFilter implements Predicate { private int lowValue, highValue; private String attributeName = null; private int attributeIndex = -1; public PriceFilter(int lowValue, int highValue, String attributeName) { this.lowValue = lowValue; this.highValue = highValue; this.attributeName = attributeName; } public PriceFilter(int lowValue, int highValue, int attributeIndex) { this.lowValue = lowValue; this.highValue = highValue; this.attributeIndex = attributeIndex; } public boolean evaluate(Object value, String attributeName) { boolean result = true; if (attributeName.equalsIgnoreCase(this.attributeName)) { int attributeValue = ((Integer)value).intValue(); if (attributeValue >= this.lowValue && attributeValue <= this.highValue) return true; return false; } return result; } public boolean evaluate(Object value, int attributeIndex) { boolean result = true; if (attributeIndex == this.attributeIndex)) { int attributeValue = ((Integer)value).intValue(); if (attributeValue >= this.lowValue && attributeValue <= this.highValue) return true; return false; } return result; } public boolean evaluate (RowSet rowSet) { boolean result = false; CachedRowSet cachedRowSet = (CachedRowSet)rowSet; int attributeValue = -1; if (this.attributeName != null) attributeValue = cachedRowSet.getInt(this.attributeName); else if (this.attributeIndex > 0)) attributeValue = cachedRowSet.getInt(this.attributeIndex); else return false; if (attributeValue >= this.lowValue && attributeValue <= this.highValue) result = true; return result; } }
Operaţiile de adăugare, modificare sau ştergere sunt permise numai dacă acestea nu contravin filtrelor asociate obiectului de tip FilteredRowSet
.
Obiectele de tip JoinRowSet
permit realizarea operaţiei de asociere (JOIN
) între obiecte RowSet
care nu sunt conectate la sursa de date, astfel încât sunt economisite resursele necesare realizării uneia sau mai multor conexiuni.
Crearea unui obiect de tip JoinRowSet
se face prin constructorul implicit JoinRowSetImpl
(există şi implementări specifice anumitor drivere JDBC, însă este posibil ca acestea să aibă denumiri şi comportamente diferite faţă de standard). Acesta nu va conţine nici un fel de date până când nu sunt adăugate obiecte RowSet
, specificându-se totodată şi atributul care serveşte drept legătură (cheie străină) în setul de date respectiv. Acest lucru se face prin metoda addRowSet()
care primeşte ca parametru un obiect RowSet
şi indexul sau denumirea coloanei care indică relaţia între tabele. De asemenea, trebuie specificat şi tipul de asociere (JOIN
) care se va realiza între tabele. Implicit, acesta este INNER_JOIN
, însă metoda setJoinType()
poate primi drept parametri şi următoarele tipuri: CROSS_JOIN
, FULL_JOIN
, LEFT_OUTER_JOIN
, RIGHT_OUTER_JOIN
. Alternativ, la crearea unui obiect RowSet
care implementează interfaţa Joinable
, pot fi precizate atributele care vor fi utilizate la realizarea asocierii prin metoda setMatchColumn()
, astfel încât atunci când sunt adăugate la JoinRowSet
nu mai este necesară şi specificarea acestei proprietăţi. Asocierea obţinută va conţine toate atributele seturilor de date din care este formată, astfel încât selectarea anumitor coloane se face “manual”, parcurgând obiectul JoinRowSet
cu afişarea valorilor dorite.
Pentru exemplul folosit, dacă se doreşte vizualizarea facturilor precum şi a conţinutului acestora, se poate folosi un obiect JoinRowSet
:
CachedRowSet invoices = new CachedRowSetImpl(); invoices.setURL("jdbc:mysql://localhost:3306/bookstore"); invoices.setUsername(username); invoices.setPassword(password); invoices.setCommand("SELECT * FROM invoice"); invoices.setMatchColumn("id"); invoices.execute(); CachedRowSet invoices_details = new CachedRowSetImpl(); invoices_details.setURL("jdbc:mysql://localhost:3306/bookstore"); invoices_details.setUsername(username); invoices_details.setPassword(password); invoices_details.setCommand("SELECT * FROM invoice_detail"); invoices_details.setMatchColumn("invoice_id"); invoices_details.execute(); JoinRowSet joinRowSet = new JoinRowSetImpl(); joinRowSet.addRowSet(invoices); joinRowSet.addRowSet(invoices_details);
Un obiect WebRowSet
are capabilitatea de a fi reţinut ca document XML şi totodată de a fi obţinut din acest format. Întrucât limbajul XML este folosit ca standard, mai ales în comunicaţiile între organizaţii, folosind servicii web, obiectul WebRowSet
răspunde unor necesităţi reale.
Crearea unui obiect WebRowSet
se face folosind constructorul implicit WebRowSetImpl
. Acesta va dispune de un obiect SyncProvider
care, spre diferenţă de implementarea standard, va avea asociat un obiect RIXMLProvider
pentru a defini comportamentul în cazul unui conflict.
Descărcarea unui obiect WebRowSet
ca document XML se face fie folosind un obiect OutputStream
(caz în care scrierea se face la nivel de octeţi, suportând mai multe tipuri de date) sau un obiect Writer
(caz în care scrierea se face la nivel de caractere).
java.io.FileOutputStream fileOutputStream = new java.io.FileOutputStream("genres.xml"); genres.writeXml(fileOutputStream);
java.io.FileWriter fileWriter = new java.io.FileWriter("genres.xml"); genres.writeXml(fileWriter);
De asemenea, există posibilitatea populării dintr-un obiect ResultSet
înainte de reţinerea ca fişier XML:
genres.writeXml(resultSet, fileOutputStream); genres.writeXml(resultSet, fileWriter);
Similar, încărcarea conţinutului unui document XML într-un obiect WebRowSet
se face fie folosind un obiect InputStream
, fie folosind un obiect Reader
:
java.io.FileInputStream fileInputStream = new java.io.FileOutputStream("genres.xml"); genres.readXml(fileOutputStream);
java.io.FileReader fileReader = new java.io.FileReader("genres.xml"); genres.readXml(fileReader);
Documentele XML asociate obiectelor de tip WebRowSet
conţin:
<properties> … </properties>
;<metadata> … </metadata>
, care conţin structura tabelei:<column-count>
indică numărul atributelor;<column-definition>
cu proprietăţile lor;<data> … </data>
care conţine, pentru fiecare înregistrare preluată din baza de date <currentRow> … </currentRow>
<columnValue> … </columnValue>
<updateValue>
;<insertRow>
, respectiv <deleteRow>
.writeXml()
/ readXml()
operează în mod transparent.
aipi2014-lab02-eclipse.zip (actualizat 22.10.2014, 18:40)
aipi2014-lab02-netbeans.zip (actualizat 22.10.2014, 18:40)
exerciseX()
) in clasa Bookstore
din pachetul ro.cs.pub.aipi.main
, unde X
reprezinta numarul exercitiului (X = 02 … 11
).
AllTests
din directorul tests
(pachetul ro.pub.cs.aipi.main
), sau se poate defini metoda main()
in clasa Bookstore
din pachetul ro.pub.cs.aipi.main
apelandu-se succesiv metodele exerciseX()
.
Se doreşte proiectarea unei aplicaţii care exploatează o bază de date ce urmează să fie integrată în cadrul unui sistem ERP pentru o librărie care comercializează doar cărţi. Se va porni de la schema conceptuală, respectiv de la structura bazei de date construite în cadrul laboratorului anterior.
Constants
din pachetul ro.pub.cs.aipi.general
, să se modifice valoarea constantei DATABASE_PASSWORD
cu cea corespunzătoare sistemului de gestiune pentru baze de date instalat pe maşina proprie.Laborator2l.sql
(din directorul scripts
) unde se crează structura şi diferite obiecte ale bazei de date bookstore
;getTableNumberOfRows()
din clasa DataBaseWrapperImplementation
a pachetului ro.pub.cs.aipi.dataaccess
, ce determină numărul de înregistrări al unei tabele identificata prin denumire (transmisa ca parametru). public int getTableNumberOfRows(String tableName) throws SQLException;
getTableContent()
, să se creeaze un fişier books.txt
in directorul output
care sa contina lista tuturor cărţilor disponibile în librărie (existente pe stoc).* * *
), titlul, editura precum şi anul apariţiei, separate prin caracterul TAB.user
o înregistrare ale cărei atribute sunt introduse de la tastatură.Scanner scanner = new Scanner(System.in); value = scanner.nextLine(); // ... scanner.close();
updateRecordsIntoTable()
, să se modifice toate comenzile de aprovizionare către editura Harlequin
(id = 50
, registered_number = 510636670
) astfel încât cantităţile să fie crescute cu 20%.state='delivered
').deleteRecordsFromTable()
din clasa DataBaseWrapperImplementation
a pachetului ro.pub.cs.aipi.dataaccess
ce elimină dintr-o tabelă identificată prin denumire (transmisa ca parametru) acele înregistrări care au anumite valori corespunzătoare unor atribute sau care respectă o anumită condiţie. public void deleteRecordsFromTable(String tableName, ArrayList<String> attributes, ArrayList<String> values, String whereClause) throws SQLException, DataBaseException
Dacă attributes = (attribute1, …, attributen)
, respectiv values = (value1, …, valuen)
, atunci vor fi şterse înregistrările pentru care attribute1=value1 AND … AND attributen=valuen
sau pentru care este îndeplinită condiţia whereClause
. În cazul în care sunt precizate attributes
/ values
, parametrul whereClause
va fi ignorat.
Folosind metoda deleteRecordsFromTable()
astfel implementată să se şteargă acele edituri care nu au cărţi comercializate de librarie.
calculate_user_total_invoice_value
să se determine lista utilizatorilor împreună cu valoarea totala a facturilor asociate fiecăruia. Informatiile vor fi plasate sub forma cod numeric personal TAB valoarea totala a facturilor, cate o informatie pe un rand, in fisierul user_total_invoce_value.txt
din directorul output
.calculate_supply_order_value
să se determine lista editurilor către care s-au efectuat cele mai mari plăţi. Informatiile vor fi plasate sub forma denumire factura TAB valoarea totala a comenzilor de aprovizionare, cate o informatie pe un rand, in fisierul publishing_house_total_supply_order_value.txt
din directorul output
, realizandu-se o ordonare descrescatoare in functie de valoarea totala a comenzilor de aprovizionare.getReferrences()
din clasa DataBaseWrapperImplementation
a pachetului ro.pub.cs.aipi.dataaccess
ce determină pentru o tabelă identificata prin denumire (transmisa ca parametru) care sunt tabelele pe care le referă, precum şi atributele care fac obiectul constrângerii de tip FOREIGN KEY
. Metoda intoarce o lista de obiecte de tip Referrence
care contine denumirea tabelei copil si atributul dependent din cadrul acesteia precum si denumirea tabelei parinte si atributul referit din cadrul acesteia.foreign_key_constraints.txt
in directorul output
care contine toate constrangerile de tip cheie straina sub forma tabela_copil/atribut_copil referrences tabela_parinte/atribut_parinte
, cate o inregistrare pe un rand.RowSet
deconectat, să se afişeze cărţile comercializate de librarie (precizându-se titlul şi preţul lor), numele editurii care le-a publicat, numele colecţiei şi numele domeniului din care fac parte. Informatiile vor fi stocate in fisierul books_join.txt
din directorul output
sub forma identificator carte, titlu, pret, denumire editura, denumire colectie si denumire domeniu, separate intre ele prin TAB, cate o inregistrare pe linie.books_filtered.txt
din directorul output
, formatul fiind acelasi ca in cazul precedent.Laborator2u.sql
(din directorul scripts
) unde se sterge baza de date bookstore
impreuna cu toate obiectele sale;