Java Database Connectivity (JDBC) predstavlja Java interfejs za programiranje aplikacija (API) koji standardizuje načine za pristup i upravljanje bazama podataka. JDBC kod je jednostavno portabilizan između nekoliko RSUBP proizvođača. Jedina promena u kodu koja se očekuje kada se JDBC kod portuje za neki drugi RSUBP u odnosu na onaj za koji je prvobitno napisan jeste koji JDBC drajver se učitava i niska za konekciju na bazu podataka. JDBC koristi isključivo dinamičko izvršavanje SQL naredbi i veoma je popularan u praksi.
Pre nego što se izvršavaju SQL naredbe u bilo kom JDBC programu, aplikacija se mora povezati na bazu podataka.
Iako postoji nekoliko tipova JDBC drajvera kao što su tipovi 1, 2, 3 i 4, tipovi 1 i 3 nisu često korišćeni, i Db2 podrška za ove tipove je uklonjena iz novijih verzija ovog SUBP. Za tip 2 postoje dva drajvera, kao što ćemo videti, ali jedan od njih je takođe zastareo, te bi ga trebalo izbegavati.
Kao što je vidljivo iz naredne tabele, tipovi 2 i 4 JDBC drajvera su podržani Db2 SUBP. Tip 2 drajver zahteva instalaciju Db2 klijenta, s obzirom da se on koristi kako bi drajver kreirao komunikacioni kanal ka bazi podataka. Sa druge strane, tip 4 je čist Java klijent, te nije potrebno instalirati Db2 klijent, ali sam drajver mora biti instaliran na mašini gde se JDBC aplikacija pokreče.
Tip drajvera | Ime drajvera | Datoteka | Podržava | Najmanja verzija Java SDK |
---|---|---|---|---|
Tip 2 | DB2 JDBC Type 2 Driver for Linux, UNIX® and Windows (Deprecated) | db2java.zip |
JDBC 1.2, JDBC 2.0 | 1.4.2 |
Tip 2, Tip 4 | IBM Data Server Driver for JDBC and SQLJ | db2jcc.jar i sqlj.zip |
JDBC 3.0 saglasni | 1.4.2 |
Tip 2, Tip 4 | IBM Data Server Driver for JDBC and SQLJ | db2jcc4.jar i sqlj4.zip |
JDBC 4.0 i ranije | 6 |
Kao što je pomenuto ranije, a takođe je prikazano u tabeli iznad, JDBC drajver tipa 2 je dostupan u dva različita
drajvera. Ipak, Db2 JDBC Type 2 Driver for Linux, UNIX and Windows, čija je implementacija data datotekom db2java.zip
je zastareo.
Kada se instalira Db2 server, Db2 klijent ili IBM Data Server Driver za JDBC i SQLJ, tj. datoteke db2jcc.jar
i sqlj.zip
koje su saglasne sa JDBC 3.0 automatski su dodate u “classpath”.
Kolekcija ka bazi podataka se može dobiti korišćenjem klase DriverManager
iz java.sql
paketa.
Paket java.sql
definiše klase i interfejse koji su neophodni kako bi JDBC program pristupio
podacima koji su skladišteni u relacionim bazama podataka. Ovaj API se može koristiti za
povezivanje na relacionu bazu podataka i upravljanje podacima (čitanje, unos, ažuriranje, brisanje i dr.)
koji su skladišteni u formi tabela prema SQL standardu. Interfejsi koji su definisani u ovom paketu
implementirani su u klasa odgovarajućih drajvera i njihova definicija se može razlikovati između
proizvođaca.
Pre ostvarivanja konekcije, potrebno je učitati klase iz drajvera i registrovati ih za upotrebu korišćenjem klase
DriverManager
. Proizvoljan broj drajvera se može učitati u registrovati klasom DriverManager
korišćenjem metoda forName
:
class ProgramName {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// ...
}
Java podržava poseban blok, nazvan statički blok (koji se takođe naziva statička klauzula) koji se može koristiti za statičke inicijalizacije klase. Ovaj kod unutar statičkog bloka se izvršava samo jednom: prvi put kada napravite objekat te klase ili prvi put kada pristupite statičkom članu te klase (čak i ako nikada ne napravite objekat te klase). Koristićemo statičke blokove u našim programima za učitavanje i registraciju DB2 JDBC drajvera, kao što je prikazano u gornjem primeru koda.
Metod forName
kao argument očekuje nisku čija vrednost je naziv paketa koji implementira
interfejse definisane paketom java.sql
.
Konekcija ka bazi podataka se može ostvariti pozivom metoda getConnection
klase DriverManager
.
Ovaj metod prihvata nisku (URL) kao ulaz, koji sadrži informacije potrebne za povezivanje sa bazom podataka.
Metod vraca objekat interfejsa Connection
, koji se može koristiti za upravljanje konekcijom ka bazi podataka.
Tipičan format URL-a za drajver tipa 4 je:
jdbc:db2://<naziv servera>:<broj porta>/<ime baze podataka>
Konekcija ka bazi podataka se raskida pozivom metoda close
nad objektom interfejsa Connection
:
public static void main(String argv[]) {
Connection con = null;
String url = "jdbc:db2://localhost:50000/stud2020";
// Povezivanje na bazu podataka
con = DriverManager.getConnection(url, "student", "abcdef");
// Kod programa...
// Raskidanje konekcije sa bazom podataka
con.close();
}
Kao što znamo iz ranijih poglavlja, naredbe za povezivanje i raskidanje konekcije
(kao i druge SQL naredbe), mogu proizvesti SQL greške. U JDBC aplikacijama,
kao i u svim drugim Java aplikacijama, obrada grešaka se vrši pomoću izuzetaka i
try-catch
blokovima. JDBC aplikacija ispaljuje objekat klase SQLException
svaki put
kada dođe do SQL greške ili SQLWarning
svaki put kada dođe do SQL upozorenja
prilikom izvršavanja SQL naredbi. Takođe, svi metodi JDBC API-ja koji mogu dovesti
do SQL grešaka u svom potpisu sadrže klauzulu throws SQLException
, što znači
da je pozive ovakvih metoda neophodno obuhvatiti nekim try-catch
blokom u programu.
Objekat klase SQLException
sadrži razne korisne informacije o SQL grešci koja je
podignuta u SUBP, a neke od njih su navedene u narednoj tabeli:
Informacija | Opis | Metod koji se koristi za dohvatanje informacije |
---|---|---|
Poruka | Tekstualna reprezentacija SQL greške. | getMessage |
SQLSTATE | SQLState niska. | getSQLState |
SQLCODE | Celobrojna vrednost koja indikuje vrstu greške koja je podignuta u SUBP. | getErrorCode |
Pored navedenih informacija, Db2 JCC drajver pruža dodatni interfejs com.ibm.db2.jcc.DB2Diagnosable
.
Ovaj interfejs daje više informacija u vezi sa greškom koja se dogodila prilikom pristupanja Db2 bazi podataka.
Primer obrade SQLExpection
izuzetaka dat je narednim kodom:
Connection con = null;
try {
// Povezivanje na bazu podataka
con = DriverManager.getConnection(url, "student", "abcdef");
// Kod programa...
// Raskidanje konekcije sa bazom podataka
con.close();
}
catch (SQLException e) {
e.printStackTrace();
System.out.println(
"SQLCODE: " + e.getErrorCode() + "\n" +
"SQLSTATE: " + e.getSQLState() + "\n" +
"PORUKA: " + e.getMessage());
// Treba da zatvorimo konekciju ka bazi podataka i u slučaju greške.
// S obzirom da metod close takođe može ispaliti SQLException,
// potrebno ga je uhvatiti, ali biramo da ga ne obrađujemo dalje.
try {
if (null != con) {
con.close();
}
} catch (SQLException e2) {
}
System.exit(2);
}
catch (Exception e) {
e.printStackTrace();
// Isto kao u catch bloku iznad.
try {
if (null != con) {
con.close();
}
} catch (SQLException e2) {
}
System.exit(3);
}
S obzirom da objekti interfejsa Connection
implementiraju interfejs AutoCloseable
, možemo koristiti i try-with-resources
naredbu u okviru koje ćemo otvoriti konekciju. Naredba se stara o otvorenim resursima i na kraju ih automatski zatvara.
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (
Connection con = DriverManager.getConnection(url, "student", "abcdef");
) {
// Kod programa...
} catch (SQLException e) {
e.printStackTrace();
System.out.println(
"SQLCODE: " + e.getErrorCode() + "\n" +
"SQLSTATE: " + e.getSQLState() + "\n" +
"PORUKA: " + e.getMessage());
System.exit(2);
}
catch (Exception e) {
e.printStackTrace();
System.exit(3);
}
}
U slučaju da se više izuzetaka tipa SQLExceptions
ispali, oni su ulančani.
Informacija o narednom izuzetku se može dobiti pozivom metoda getNextException
nad
trenutnim SQLException
objektom koji se obrađuje. Ovaj metod vraća null
ako je
tekući SQLException
objekat poslednji u lancu izuzetaka. Zbog toga se može
koristiti while
petlja u catch
bloku programa kako bi se obradili SQLException
objekti
jedan-po-jedan.
Nakon ostvarivanja konekcije ka bazi podataka, podaci se mogu dohvatiti, unositi, ažurirati
ili obrisati iz relacionih tabela korišćenjem SQL naredbi. JDBC drajver implementira dva
interfejsa, Statement
i PreparedStatement
za ove potrebe. Objekti jednog od ova dva interfejsa
su neophodni kako bi se SQL naredbe izvršile nad bazom podataka. U nastavku govorimo o
slučajevima upotrebe ovih interfejsa, ali pre toga ćemo reći nešto o tipovima podataka.
Da bismo napisali efikasne JDBC programe, potrebno je da koristimo najbolja preslikavanja između Java tipova podataka i tipova kolona tabela.
Naredna tabela sumira mapiranja Db2 tipova podataka u Java tipove podataka za familiju metoda
ResultSet.getXXX
u JDBC programima. Ova tabela ne navodi tipove Java numeričkih omotača,
koji se dohvataju pomoću metoda ResultSet.getObject
.
SQL tip podataka | Preporučeni Java tip podataka | Drugi podržani Java tipovi podataka |
---|---|---|
SMALLINT | short | byte, int, long, float, double, java.math.BigDecimal, boolean, java.lang.String |
INTEGER | int | short, byte, long, float, double, java.math.BigDecimal, boolean, java.lang.String |
BIGINT | long | int, short, byte, float, double, java.math.BigDecimal, boolean, java.lang.String |
DECIMAL(p,s) or NUMERIC(p,s) | java.math.BigDecimal | long, int, short, byte, float, double, boolean, java.lang.String |
DECFLOAT(n) | java.math.BigDecimal | long, int, short, byte, float, double, java.math.BigDecimal, boolean, java.lang.String |
REAL | float | long, int, short, byte, double, java.math.BigDecimal, boolean, java.lang.String |
DOUBLE | double | long, int, short, byte, float, java.math.BigDecimal, boolean, java.lang.String |
CHAR(n) | java.lang.String | long, int, short, byte, float, double, java.math.BigDecimal, boolean, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.io.InputStream, java.io.Reader |
VARCHAR(n) | java.lang.String | long, int, short, byte, float, double, java.math.BigDecimal, boolean, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.io.InputStream, java.io.Reader |
DATE | java.sql.Date | java.sql.String, java.sql.Timestamp |
TIME | java.sql.Time | java.sql.String, java.sql.Timestamp |
TIMESTAMP, TIMESTAMP(p), TIMESTAMP WITH TIME ZONE, TIMESTAMP(p) WITH TIME ZONE | java.sql.Timestamp | java.sql.String, java.sql.Date, java.sql.Time, java.sql.Timestamp |
Naredna tabela sumira preslikavanja Java tipova podataka u Db2 tipove podataka za familije metoda PreparedStatement.setXXX
i ResultSet.updateXXX
u JDBC programima. Kada je navedeno više od jednog Java tipa podataka, prvi tip podataka je preporučeni tip podataka.
Java tip podataka | Db2 tip podataka |
---|---|
short, java.lang.Short | SMALLINT |
boolean, byte, java.lang.Boolean, java.lang.Byte | SMALLINT |
int, java.lang.Integer | INTEGER |
long, java.lang.Long | BIGINT |
java.math.BigInteger | BIGINT |
java.math.BigInteger | CHAR(n) |
float, java.lang.Float | REAL |
double, java.lang.Double | DOUBLE |
java.math.BigDecimal | DECIMAL(p,s) |
java.math.BigDecimal | DECFLOAT(n) |
java.lang.String | CHAR(n) |
java.lang.String | VARCHAR(n) |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP, TIMESTAMP(p), TIMESTAMP WITH TIME ZONE, TIMESTAMP(p) WITH TIME ZONE |
java.util.Date | CHAR(n) |
java.util.Date | VARCHAR(n) |
java.util.Date | DATE |
java.util.Date | TIME |
java.util.Date | TIMESTAMP, TIMESTAMP(p), TIMESTAMP WITH TIME ZONE, TIMESTAMP(p) WITH TIME ZONE |
Statement
Objekti interfejsa Statement
mogu se iskoristiti za izvršavanje SQL naredbi koji ne
sadrže parametarske oznake. Ovi objekti se kreiraju pozivom metoda createStatement
nad
objektima interfejsa Connection
. Moguće je kreirati proizvoljan broj objekata Statement
nad jednim objektom Connection
i svi ti objekti će biti vezani za konekciju koja je
opisana istim objektom Connection
(drugim rečima, sve te SQL naredbe će biti izvršene
nad istom bazom podataka).
Interfejs Statement
definiše dve vrste metoda za izvršavanje SQL naredbi. Oba metoda
kao prvi parametar očekuju nisku koja sadrži validnu SQL naredbu. U zavisnosti od vrste
SQL naredbe, koriste se naredni metodi:
executeQuery
se koristi kada se kao rezultat izvršavanja SQL naredbe očekuje kursor (na primer, prilikom izvršavanja naredbe SELECT
). Metod executeQuery
vraća objekat interfejsa ResultSet
, koji predstavlja skup rezultata koji se vraća naredbom SELECT
. Objekti interfejsa ResultSet
mogu se koristiti za prolazak kroz redove rezultujuće tabele.executeUpdate
se koristi za ažuriranje podataka (na primer, INSERT
, UPDATE
, DELETE
i MERGE
naredbama). Metod executeUpdate
vraća celobrojnu vrednost, koja indikuje broj redova koji je unet, ažuriran ili obrisan u bazi podataka, u zavisnosti od tipa SQL naredbi. Ovaj metod se koristi i u slučaju izvršavanja DDL naredbi, kao što su CREATE
, ALTER
, DROP
, GRANT
, REVOKE
i dr.U nastavku dajemo dve procedure koje opisuju upotrebu opisanih metoda.
Da bismo iterirali kroz rezultujuću tabelu naredbe SELECT
koja ne sadrži parametarske oznake, potrebno je da ispratimo naredne korake:
String sql = "SELECT ...";
Statement
pozivom metoda Connection.createStatement()
.
Statement stmt = con.createStatement();
ResultSet
pozivom metoda Statement.executeQuery(String sql)
.
ResultSet kursor = stmt.executeQuery(sql);
while (kursor.next()) {
// ...
}
ResultSet
iz familije metoda ResultSet.getXXX(int columnIndex)
za dohvatanje vrednosti iz kolone rezultujuće tabele sa indeksom columnIndex
. Na primer:
while (kursor.next()) {
int indeks = kursor.getInt(1);
String ime = kursor.getString(2);
// ...
}
NULL
vrednost (videti sekciju o nedostajućim vrednostima).kursor.close()
radi zatvaranja kursora.stmt.close()
radi zatvaranja objekta naredbe.Da bismo izmenili podatke u bazi podataka nekom SQL naredbom bez parametarskih oznaka, potrebno je da ispratimo naredne korake:
String sql = "DELETE ...";
Statement
pozivom metoda Connection.createStatement()
.
Statement stmt = con.createStatement();
stmt.executeUpdate(String sql)
.
int brojRedova = stmt.executeUpdate(sql);
stmt.close()
radi zatvaranja objekta naredbe.Naredni zadaci ilustruju opisane procedure na konkretnim primerima.
Zadatak 8.1: Napisati Java program u kojem se naredbe izvršavaju dinamički koji izlistava oznake i nazive svih predmeta koji imaju više od 20 ESPB bodova.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_1/Main.java
:
package zadatak_8_1;
import java.sql.*;
public class Main {
// Staticki blok koji sluzi za ucitavanje JDBC DB2 drajvera.
// Izvrsava se kada se prvi put instancira objekat ove klase,
// ili kada se prvi put pozove staticki metod.
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
// URL za JDBC konekciju tipa 4
String url = "jdbc:db2://localhost:50000/stud2020";
// Objekat koji ce sadrzati konekciju
// Kreiramo konekciju na bazi podataka zadatoj u promenljivoj url,
// koriscenjem metoda DriverManager.getConnection.
// Argumenti za korisnicko ime i lozinku su obavezni!
// try-with-resources vodi racuna o otvorenim resursima
// i zatvara ih na kraju try-catch bloka
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
// Kreiramo objekat naredbe (Statement)
Statement stmt = con.createStatement();
// SQL upit ili naredbu koju zelimo da izvrsimo
// zapisujemo kao nisku <-> DINAMICKI SQL
String queryStr =
"SELECT OZNAKA, " +
" NAZIV " +
"FROM DA.PREDMET " +
"WHERE ESPB > 20";
// Za izvrsavanje upita koristimo Statement.executeQuery metod.
// Ovaj metod vraca objekat klase ResultSet,
// koji sadrzi rezultate upita (kursor).
ResultSet rs = stmt.executeQuery(queryStr);
// Ispisujemo zaglavlje
System.out.printf("%-15s %-50S\n\n", "OZNAKA", "NAZIV");
// Metodom ResultSet.next pozicioniramo kursor na naredni red.
// Ako vise nema redova, metod ce vratiti vrednost false.
// Inace, vratice true, pa mozemo da ga koristimo kao uslov u petlji
while (rs.next()) {
// Izdvajamo podatke koriscenjem familije metoda
// ResultSet.getXXX,
// gde je XXX neki tip podataka,
// a argument je broj kolone koja se dohvata iz projekcije
// upita.
String oznaka = rs.getString(1);
String naziv = rs.getString(2);
System.out.printf("%-15s %-50S\n", oznaka.trim(), naziv.trim());
}
// Zatvaramo kursor
rs.close();
// Zatvaramo naredbu
stmt.close();
}
// Obrada SQL gresaka
catch (SQLException e) {
// Ispisujemo sve informacije na standardni izlaz
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
// Signaliziramo neuspesan zavrsetak programa
System.exit(1);
}
// Obrada drugih gresaka
catch (Exception e) {
e.printStackTrace();
// Signaliziramo neuspesan zavrsetak programa
System.exit(2);
}
}
}
Zadatak 8.2: Napisati Java program u kojem se naredbe izvršavaju dinamički koji u tabelu PREDMET
unosi podatak o predmetu čiji je identifikator 2001, oznaka Pred1, naziv Predmet 1 i nosi 5
ESPB bodova.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_2/Main.java
:
package zadatak_8_2;
import java.sql.*;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
// Zelimo da izvrsimo INSERT naredbu
// u kojoj su sve informacije poznate.
// To znaci da mozemo koristiti kombinaciju
// interfejsa Statement i metoda executeUpdate().
String sql =
"INSERT INTO DA.PREDMET " +
"VALUES (2001, 'Pred1', 'Predmet 1', 5)";
Statement stmt = con.createStatement();
System.out.println("Unosim podatke u tabelu PREDMET...");
// Metod executeUpdate() se koristi ne samo za azuriranje,
// vec i za unos, brisanje, i slicne naredbe.
int insertCount = stmt.executeUpdate(sql);
System.out.println("Broj unetih redova: " + insertCount);
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
}
PreparedStatement
Objekti interfejsa PreparedStatement
obavezno se koriste u slučaju kada SQL naredbe sadrže
parametarske oznake. PreparedStatement
objekti se kreiraju pozivom metoda prepareStatement
nad objektima Connection
.
Za razliku od metoda createStatement
, metod prepareStatement
očekuje jedan parametar,
a to je niska koja predstavlja SQL naredbu sa parametarskim oznakama, za koju će biti izvršena priprema.
Pun potpis ovog metoda je:
PreparedStatement prepareStatement(String sql) throws SQLException
Postavljanje vrednosti parametarskim oznakama se vrši pozivom nekog metoda iz familije
PreparedStatement.setXXX
, gde XXX
označava tip parametarske oznake koja se postavlja.
Neki od primera setXXX
metoda su:
setInt
,setString
,setDouble
,setBytes
,setClob
,setBlob
Nakon postavljanja vrednosti parametara, SQL naredba se može izvršiti pozivom nekog
od već opisanih metoda executeQuery
ili executeUpdate
, zavisno od vrste SQL naredbe.
Za razliku od istoimenih metoda interfejsa Statement
, ovi metodi se pozivaju bez prosleđivanja
SQL naredbe (s obzirom da smo naredbu već prosledili prilikom konstrukcije PreparedStatement
objekta).
Naredne procedure opisuju korake neophodne za korišćenje interfejsa PreparedStatement
.
Da bismo iterirali kroz rezultujuću tabelu naredbe SELECT
koja sadrži parametarske oznake, potrebno je da ispratimo naredne korake:
String sql = "SELECT ... WHERE INDEKS = ? AND IME = ? ...";
PreparedStatement
pozivom metoda Connection.prepareStatement(String sql)
.
PreparedStatement stmt = con.prepareStatement(sql);
PreparedStatement.setXXX(int parameterIndex, XXX x)
za postavljanje vrednosti parametarske oznake u naredbi. Na primer:
stmt.setInt(1, 20200134);
stmt.setString(2, "Ivana");
ResultSet
pozivom metoda PreparedStatement.executeQuery()
.
ResultSet kursor = stmt.executeQuery();
while (kursor.next()) {
// ...
}
ResultSet.getXXX(int columnIndex)
za dohvatanje vrednosti
iz kolone rezultujuće tabele sa indeksom columnIndex
. Na primer:
while (kursor.next()) {
int indeks = kursor.getInt(1);
String ime = kursor.getString(2);
// ...
}
NULL
vrednost (videti sekciju o nedostajućim vrednostima).kursor.close()
radi zatvaranja kursora.stmt.close()
radi zatvaranja objekta naredbe.Da bismo izmenili podatke u bazi podataka nekom SQL naredbom sa parametarskim oznakama, potrebno je da ispratimo naredne korake:
String sql = "DELETE ... WHERE INDEKS = ? ...";
PreparedStatement
pozivom metoda Connection.prepareStatement(String sql)
.
PreparedStatement stmt = con.prepareStatement(sql);
PreparedStatement.setXXX(int parameterIndex, XXX x)
za postavljanje vrednosti parametarske oznake u naredbi. Na primer:
stmt.setInt(1, 20200134);
stmt.executeUpdate()
.
int brojRedova = stmt.executeUpdate();
stmt.close()
radi zatvaranja objekta naredbe.Naredni zadaci demonstriraju upotrebu interfejsa PreparedStatement
.
Zadatak 8.3: Napisati Java program u kojem se naredbe izvršavaju dinamički koji učitava dva cela broja, X i Y, a zatim svim predmetima koji imaju X ESPB bodova, postavlja broj bodova na Y. Nakon toga ispisati broj ažuriranih redova.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_3/Main.java
:
package zadatak_8_3;
import java.sql.*;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (
Connection con = DriverManager.getConnection(url, "student", "abcdef");
Scanner ulaz = new Scanner(System.in);
) {
// JDBC koristi iskljucivo dinamicke SQL naredbe,
// tako da se u JDBC ne koriste maticne promenljive.
// S obzirom da su vrednosti za bodove u klauzama SET i WHERE
// nepoznate do faze izvrsavanja,
// potrebno je da koristimo parametarske oznake na tim mestima.
String updateStr =
"UPDATE DA.PREDMET " +
"SET ESPB = ? " +
"WHERE ESPB = ?";
// Zbog toga moramo da koristimo interfejs PreparedStatement,
// jer interfejs Statement ne radi sa parametarskim oznakama.
PreparedStatement pUpd = con.prepareStatement(updateStr);
// Postavljamo odgovarajuce vrednosti za parametarske oznake
// na osnovu procitanih vrednosti sa standardnog ulaza.
int x, y;
System.out.println("Unesite X:");
x = ulaz.nextInt();
System.out.println("Unesite Y:");
y = ulaz.nextInt();
// Prvu parametarsku oznaku menjamo celim brojem y.
pUpd.setInt(1, y);
// Drugu parametarsku oznaku menjamo celim brojem x.
pUpd.setInt(2, x);
// Izvrsavamo naredbu metodom executeUpdate()
// koji vraca broj azuriranih redova.
int numRows = pUpd.executeUpdate();
System.out.println("Broj azuriranih redova: " + numRows);
pUpd.close();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
}
Zadatak 8.4: Napisati Java program u kojem se naredbe izvršavaju dinamički koji sa standardnog ulaza učitava ceo broj N i izdvaja indeks, ime, prezime i naziv studijskog programa svih studenata koji su položili tačno N predmeta, kao i spisak tih predmeta (naziv i ocena).
Rešenje: S obzirom da je SQL upit koji se koristi u ovom zadatku nešto složeniji, da ga ne bismo zapisivali u Java kodu kao nisku, taj upit ćemo začuvati u datoteci upit.sql
u istom direktorijumu (paketu) kao i Main.java
datoteku koja sadrži rešenje ovog zadatka. Dodatno, u Java kodu ćemo pronaći ovu datoteku na sistemu datoteka i učitati njen sadržaj kao nisku da bismo je zatim izvršili. Posebno treba obratiti pažnju na parametarsku oznaku u samom upitu, koja će biti zamenjena odgovarajućom vrednošću u fazi izvršavanja programa.
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_4/upit.sql
:
WITH BROJ_POLOZENIH_ISPITA AS (
SELECT INDEKS
FROM DA.ISPIT
WHERE OCENA > 5 AND
STATUS = 'o'
GROUP BY INDEKS
HAVING COUNT(*) = ?
)
SELECT D.INDEKS,
IME,
PREZIME,
S.NAZIV,
P.NAZIV,
I.OCENA
FROM DA.DOSIJE D JOIN
BROJ_POLOZENIH_ISPITA BPI ON D.INDEKS = BPI.INDEKS JOIN
DA.STUDIJSKIPROGRAM S ON D.IDPROGRAMA = S.ID JOIN
DA.ISPIT I ON D.INDEKS = I.INDEKS JOIN
DA.PREDMET P ON I.IDPREDMETA = P.ID
WHERE I.OCENA > 5 AND
I.STATUS = 'o'
ORDER BY D.INDEKS,
IME,
PREZIME,
S.NAZIV,
P.NAZIV
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_4/Main.java
:
package zadatak_8_4;
import java.io.File;
import java.io.FileNotFoundException;
import java.sql.*;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
ispitiStudenteIIspite(con);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static void ispitiStudenteIIspite(Connection con) throws FileNotFoundException, SQLException {
String sql = ucitajSqlIzDatoteke();
int brojPredmeta = ucitajBrojPredmeta();
PreparedStatement pstmt = con.prepareStatement(sql.toString());
pstmt.setInt(1, brojPredmeta);
ResultSet result = pstmt.executeQuery();
int brojRedova = 0;
int indeks = 0;
while (result.next()) {
// Ispisujemo informacije o studentu
// samo kad naidjemo na novog studenta
if (indeks != result.getInt(1)) {
if (indeks != 0) {
System.out.print("\n\n\n");
}
System.out.println("Indeks: " + result.getInt(1) + "\n" + "Ime: " + result.getString(2).trim()
+ "\n" + "Prezime: " + result.getString(3).trim() + "\n" + "Studijski program: "
+ result.getString(4).trim() + "\n");
brojRedova = 1;
}
// Ispisujemo informacije o i-tom predmetu
System.out.println(brojRedova + ". predmet: " + result.getString(5).trim() + "\n\t" + "ocena: "
+ result.getInt(6));
// Uvecavamo broj reda
++brojRedova;
// Pamtimo tekuci indeks za narednu iteraciju
indeks = result.getInt(1);
}
pstmt.close();
}
private static int ucitajBrojPredmeta() {
int brojPredmeta;
System.out.println("Unesite broj predmeta:");
try (Scanner ulaz = new Scanner(System.in)) {
brojPredmeta = ulaz.nextInt();
}
return brojPredmeta;
}
private static String ucitajSqlIzDatoteke() throws FileNotFoundException {
// Eclipse aplikacije se pokrecu iz direktorijuma projekta,
// dakle, u podrazumevanom slucaju,
// ~/IBM/rationalsdp/workspace/poglavlje_8/.
// Mozemo koristiti sistemsko svojstvo
// System.getProperty("user.dir")
// da dohvatimo putanju u kojoj se pokrece aplikacija.
// Takodje, prilikom kompiliranja .java datoteka u .class datoteke,
// Eclipse alat ce kopirati sve druge datoteke iz src/ direktorijuma u bin/ direktorijum.
String putanja = "./bin/zadatak_8_4/upit.sql";
StringBuilder sql = new StringBuilder("");
String linija = null;
try (Scanner skenerFajla = new Scanner(new File(putanja), "utf-8")) {
while (skenerFajla.hasNextLine()) {
linija = skenerFajla.nextLine();
sql.append(linija);
sql.append("\n");
}
}
return sql.toString();
}
}
Što se tiče rada sa podacima koji su potencijalno nedostajući, na raspolaganju nam
je metod ResultSet.wasNull
, koji je potrebno pozvati odmah nakon pozivanja metoda dohvatača za neku kolonu. Ukoliko je vrednost za tu kolonu bila NULL
, onda će metod
wasNull
vratiti vrednost true
, a inače će vratiti false
. Napomenimo da se poziv wasNull
metoda vezuje samo za poslednji poziv metoda getXXX
, te samim tim i na poslednju dohvaćenu kolonu. Zbog toga je potrebno više puta pozivati ovaj metod ukoliko ima više
potencijalno nedostajućih kolona:
ResultSet res = ...
while (res.next()) {
// Vrednosti u ovoj koloni ne mogu biti NULL,
// pa nemamo ni proveru za nju.
int kolona1_notnull = res.getInt(1);
// Vrednosti u ovoj koloni mogu biti NULL,
// pa zato odmah nakon dohvatanja vrednosti,
// pozivamo metod wasNull() da proverimo da li je NULL.
int kolona2_nullable = res.getInt(2);
boolean kol2IsNull = res.wasNull();
// Vrednosti u ovoj koloni mogu biti NULL,
// pa zato odmah nakon dohvatanja vrednosti,
// pozivamo metod wasNull() da proverimo da li je NULL.
// Ovaj poziv metoda wasNull() se odnosi na kolonu 3, a ne na 2.
Date kolona3_nullable = res.getDate(3);
boolean kol3IsNull = res.wasNull();
...
}
Zadatak 8.5: Napisati Java program u kojem se naredbe izvršavaju dinamički koji izdvaja ime, prezime i datum diplomiranja za sve studentkinje (pol = ‘z’) programa čiji je identifikator 202 iz tabele DOSIJE
. Ukoliko datum diplomiranja nije poznat, ispisati Nije diplomirala
.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_5/Main.java
:
package zadatak_8_5;
import java.sql.*;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
Statement stmt = con.createStatement();
String queryStr =
"SELECT IME, " +
" PREZIME, " +
" DATDIPLOMIRANJA " +
"FROM DA.DOSIJE " +
"WHERE POL = 'z' AND " +
" IDPROGRAMA=202";
ResultSet rs = stmt.executeQuery(queryStr);
System.out.printf("%-25s %-25s %-15s \n\n", "IME", "PREZIME", "DATUM DIPLOMIRANJA");
while (rs.next()) {
String ime = rs.getString(1).trim();
String prezime = rs.getString(2).trim();
String datum = rs.getString(3);
// Ukoliko je u tekucem redu kursora datum bio NULL,
// onda ce rs.wasNull() biti true
boolean datumIsNull = rs.wasNull();
System.out.printf("%-25s %-25s %-15s \n", ime, prezime, (datumIsNull) ? "Nije diplomirala" : datum.trim());
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
}
Objekat interfejsa ResultSet
održava kursor koji pokazuje na red u rezultujućoj tabeli. Moguće je pomerati kursor na specifičan red kako bismo dohvatili podatke o tom redu ili izmenili vrednosti kolona. Kursor može da pokazuje na tačno jedan red u nekom trenutku. Red na koji kursor pokazuje se naziva tekući red kursora. U zavisnosti od podešavanja kursora, moguće je pomerati kursor na različite načine.
Pre nego što se upustimo u konkretne primere, važno je da prodiskutujemo o narednim svojstvima svakog kursora:
Usmerenost definiše operacije kojima kursor prolazi kroz redove. Podrazumevano, kursor je usmeren samo unapred, odnosno, kursorom je moguće prolaziti samo od prvog do poslednjeg reda. Kada dođemo do poslednjeg reda, onda se ne možemo vratiti unazad, već je potrebno ponovo da otvorimo kursor. Ipak, moguće je definisati kursor koji je usmeren unapred i unazad, odnosno, moguće je kretati se proizvoljno kroz redove rezultujuće tabele. Ovakav kursor se često naziva dvosmerni kursor.
Dvosmerni kursor ima još jedno svojstvo koje se naziva osetljivost na izmene (eng. update sensitivity). Ovim svojstvom se definiše da li će se izmene u bazi podataka odraziti na redove rezultujuće tabele kroz koji se prolazi (otvorenim) dvosmernim kursorom. Kursor koji je osetljiv na izmene će prikazati izmene napravljene u bazi podataka, dok kursor koji nije osetljiv na izmene neće prikazati takve izmene. Naredne tri konstante interfejsa ResultSet
se koriste za definisanje usmerenosti kursora:
ResultSet.TYPE_FORWARD_ONLY
: Dozvoljava kursoru da prolazi samo unapred kroz redove.ResultSet.TYPE_SCROLL_SENSITIVE
: Kreira dvosmerni kursor koji je osetljiv na izmene.ResultSet.TYPE_SCROLL_INSENSITIVE
: Kreira dvosmerni kursor koji nije osetljiv na izmene. Kada se kursor otvori, skup redova rezultujuće tabele neće biti promenjen dok ovakav kursor prolazi kroz te redove. Ako želimo da dohvatimo izmenjen skup redova, potrebno je da ponovo izvršimo upit (tj. da ponovo otvorimo kursor).Tipiziranost se odnosi na sposobnost kursora da ažurira podatke kroz koje prolazi. Podrazumevano, kursor koji se napravi je ograničen samo za čitanje i ne dozvoljava nam da ažuriramo redove rezultujuće tabele. Ako želimo da koristimo kursor da ažuriramo podatke nad bazom podataka, potrebno je da upit koji je pridružen kursoru vrati skup podataka koji se može ažurirati od strane JDBC drajvera. Naredne dve konstante interfejsa ResultSet
se koriste za definisanje tipiziranosti kursora.
ResultSet.CONCUR_READ_ONLY
: Ograničava kursor samo za čitanje.ResultSet.CONCUR_UPDATABLE
: Omogućava da kursor ažurira podatke.Važno je napomenuti da, ako pokušamo da napravimo kursor koji je ažurirajući, ali nije osetljiv na izmene (tj. koristimo kombinaciju konstanti ResultSet.TYPE_SCROLL_INSENSITIVE
i ResultSet.CONCUR_UPDATABLE
), JDBC drajver će implicitno ograničiti takav kursor samo za čitanje, tako da neće podržavati operacije unosa, brisanja i izmene. Pokušaj da se ovakve operacije izvrše nad takvim kursorom rezultovaće ispaljivanjem SQLException
izuzetka.
Zadrživost kursora se odnosi na stanje kursora nakon potvrđivanja izmena transakcije u kojoj je taj kursor otvoren. Kursor se može ili zatvoriti ili otvoriti u trenutku potvrđivanja izmena transakcije. Podrazumevano ponašanje zavisi od JDBC drajvera i može se proveriti pozivom metoda getHoldability()
nad objektom interfejsa Connection
. Metod vraća jednu od naredne dve konstante, koje se takođe mogu koristiti i za eksplicitno definisanje zadrživosti na nivou pojedinačnih kursora:
ResultSet.HOLD_CURSORS_OVER_COMMIT
: Ostavlja kursor u otvorenom stanju nakon što se izmene potvrde u transakciji.ResultSet.CLOSE_CURSORS_AT_COMMIT
: Zatvara kursor nakon što se izmene potvrde u transakciji.Da bismo definisali ponašanje ResultSet
objekta, potrebno je da prosledimo odgovarajuće opcije metodima createStatement
, odnosno, prepareStatement
, koji su definisani nad objektom interfejsa Connection
, u zavisnosti od toga da li želimo da naredbu izvršimo kroz interfejse Statement
ili PreparedStatement
, redom.
U slučaju kreiranja objekta Statement
, na raspolaganju su nam naredna dva preopterećenja metoda createStatement
:
int resultSetType
— Usmerenost kursora.int resultSetConcurrency
— Tipiziranost kursora.int resultSetType
— Usmerenost kursora.int resultSetConcurrency
— Tipiziranost kursora.int resultSetHoldability
— Zadrživost kursora.U slučaju kreiranja objekta PreparedStatement
, na raspolaganju su nam naredna dva preopterećenja metoda prepareStatement
, sa istim značenjima parametara kao i u slučaju metoda createStatement
:
String sql
— Niska koja sadrži tekstualni oblik SQL naredbe. Može sadržati nula ili više parametarskih oznaka.int resultSetType
int resultSetConcurrency
String sql
int resultSetType
int resultSetConcurrency
int resultSetHoldability
Zadatak 8.6: Napisati Java program u kojem se naredbe izvršavaju dinamički koji ispisuje podatke o ispitnim rokovima koristeći kursor kome je omogućeno kretanje i unazad kroz podatke. Podatke urediti po nazivu rastuće, ali ih ispisivati opadajuće.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_6/Main.java
:
package zadatak_8_6;
import java.sql.*;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
String sql =
"SELECT * " +
"FROM DA.ISPITNIROK " +
"ORDER BY NAZIV";
Statement stmt = con.createStatement(
// Podesavamo da je kursor bidirekcioni i nesenzitivni,
// sto znaci da moze da se krece kroz njega u oba smera,
// i da izmene u bazi podataka nece biti vidljive tokom
// prolazenja kroz kursora
ResultSet.TYPE_SCROLL_INSENSITIVE,
// Podesavamo da se kursor koristi samo za citanje
ResultSet.CONCUR_READ_ONLY);
ResultSet res = stmt.executeQuery(sql);
System.out.printf("%-10s %-15s %-20s %-20s %-20s\n\n",
"GODINA", "OZNAKA", "NAZIV", "DATUM POCETKA", "DATUM KRAJA");
// Pozicioniranje na kraj kursora
res.afterLast();
// Citanje unazad
while (res.previous()) {
int godina = res.getInt(1);
String oznaka = res.getString(2).trim();
String naziv = res.getString(3).trim();
Date datumPocetka = res.getDate(4);
Date datumKraja = res.getDate(5);
System.out.printf("%-10d %-15s %-20s %-20s %-20s\n",
godina, oznaka, naziv, datumPocetka.toString(), datumKraja.toString());
}
res.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
}
Postoje dve procedure za ažuriranje informacija u bazi podataka na osnovu ResultSet
kursora:
UPDATE
naredbe.updateXXX
i updateRow
.Kao što znamo, možemo koristiti pozicionirajuću UPDATE
naredbu za menjanje podataka u bazi podataka, na osnovu tekućeg reda kursora, tako što se na kursor referiše u WHERE CURRENT OF
klauzi naredbe. Da bismo dohvatili naziv ResultSet
kursora, možemo iskoristiti metod ResultSet.getCursorName
, koji vraća nisku sa nazivom kursora koji je vezan za taj ResultSet
. Na primer, naredni fragment koda ilustruje kako možemo ažurirati broj bodova za sve predmete iz tabele PREDMET
korišćenjem pozicionirajuće UPDATE
naredbe:
String upit =
"SELECT OZNAKA, " +
" NAZIV, " +
" ESPB " +
"FROM DA.PREDMET " +
"FOR UPDATE OF ESPB";
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(upit);
String nazivKursora = rs.getCursorName();
String azuriranje =
"UPDATE DA.PREDMET " +
"SET ESPB = ? " +
"WHERE CURRENT OF " + nazivKursora;
PreparedStatement ps = con.prepareStatement(nazivKursora);
try (Scanner scanner = new Scanner(System.in)) {
while (rs.next()) {
System.out.println("Unesite nove ESPB bodove za predmet " + rs.getString(2));
int newESPB = scanner.nextInt();
ps.setInt(1, newESPB);
ps.executeUpdate();
}
}
Drugi pristup podrazumeva korišćenje specifičnih metoda radi ažuriranja podataka u kursoru, čime se efektivno vrši ažuriranje u bazi podataka.
Naredni koraci opisuju postupak ažuriranja postojećih slogova u ResultSet
:
Statement
pozivom metoda
Statement stmt = con.createStatement(..., ResultSet.CONCUR_UPDATABLE, ...);
ResultSet
pozivom metoda
ResultSet kursor = stmt.executeQuery(sql);
while (kursor.next()) {
kursor.absolute(5); // Samo ako kursor nije tipa TYPE_FORWARD_ONLY
// ...
}
ResultSet.updateXXX(int columnIndex, XXX x)
za ažuriranje vrednosti kolone tabele sa indeksom columnIndex
novom vrednošću x
.
kursor.updateInt(1, 100);
kursor.cancelRowUpdates()
ResultSet.updateRow()
. U suprotnom će, prelaskom na drugi red, sve izmene biti ignorisane.
kursor.updateRow();
kursor.close();
stmt.close();
Pogledajmo primer upotrebe u narednom zadatku.
Zadatak 8.7: Napisati Java program u kojem se naredbe izvršavaju dinamički koji ispisuje sadržaj tabele
PREDMET
i, u istoj iteraciji, ukoliko je broj bodova jednak X, postavlja se broj bodova na
Y i ispisuje se poruka da je promena izvršena, zajedno sa ispisom novih podataka o
tom predmetu. Brojevi X i Y se učitavaju sa standardnog ulaza.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_7/Main.java
:
package zadatak_8_7;
import java.sql.*;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
int[] ucitaniESPB = ucitajStareINoveESPB();
ispisiIAzurirajPredmete(con, ucitaniESPB[0], ucitaniESPB[1]);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static void ispisiIAzurirajPredmete(Connection con, int X, int Y) throws SQLException {
String sql =
"SELECT * " +
"FROM DA.PREDMET";
Statement stmt = con.createStatement(
// Dovoljan nam je kursor koji prolazi unapred kroz redove.
ResultSet.TYPE_FORWARD_ONLY,
// Definisemo da je kursor azurirajuci,
// pa su nam dostupni metodi
// ResultSet.updateXXX i ResultSet.updateRow
// koje cemo koristiti za azuriranje tekuceg reda
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
System.out.println("Oznaka: " + rs.getString(2).trim() + ", " + "Naziv:" + rs.getString(3).trim() + ", "
+ "ESPB: " + rs.getInt(4));
if (rs.getInt(4) == X) {
// Azuriramo vrednost u koloni broj 5 (ESPB) iz upita iznad.
rs.updateInt(4, Y);
// Kada zavrsimo sa azuriranjem svih kolona,
// pozivamo metod updateRow
// da bi se sve promene odmah oslikale.
// Ako zaboravimo da pozovemo ovaj metod,
// izmene se nece propagirati i bice izgubljene.
rs.updateRow();
// Sada kada zahtevamo vrednosti u kolonama,
// dobicemo azurirane vrednosti:
System.out.println("------> Izvrsena je naredna promena:");
System.out.println(" Oznaka: " + rs.getString(2).trim() + ", " + "Naziv:" + rs.getString(3).trim()
+ ", " + "ESPB: "
+ rs.getInt(4) /* azurirana vrednost */ + "\n");
}
}
rs.close();
stmt.close();
}
private static int[] ucitajStareINoveESPB() {
int[] rezultat = new int[2];
try (Scanner ulaz = new Scanner(System.in)) {
System.out.println("Unesite stare ESPB:");
rezultat[0] = ulaz.nextInt();
System.out.println("Unesite nove ESPB:");
rezultat[1] = ulaz.nextInt();
}
return rezultat;
}
}
Ono što nismo imali prilike da vidimo jeste da se pomoću kursora mogu uneti novi slogovi u tabelu. Za unos novog sloga u tabelu koristi se specijalni “slog za unos”. Ovaj specijalni slog zapravo predstavlja bafer za konstruisanje novog sloga koji će biti unet u odgovarajuću tabelu.
Konstrukcija novog sloga se vrši u dva koraka:
ResultSet.updateXXX(int columnIndex, XXX x)
Unos baferisanog “slogu za unos” se izvršava pozivom metoda void ResultSet.insertRow()
. Međutim, da bismo zapravo uspeli da konstruišemo ovaj specijalni slog, neophodno je da signaliziramo kursoru da želimo da menjamo baš taj red, a ne tekući red kroz koji se iterira u kursoru. Ovo je moguće uraditi pozivom metoda void ResultSet.moveToInsertRow()
. Prilikom poziva ovog metoda, kursor se pozicionira na “slog za unos” i ujedno se pamti redni broj tekućeg sloga koji se obrađuje u kursoru. Nakon što smo završili proceduru za unos opisanu prethodnim koracima, potrebno je pozvati metod void ResultSet.moveToCurrentRow()
koji će ponovo pozicionirati kursor na prethodno zapamćeni tekući slog koji se obrađuje u kursoru.
Celokupna procedura za unos novog sloga je data narednim koracima:
Statement
za rad sa ažurirajućim kursorima.
Statement stmt = con.createStatement(..., ResultSet.CONCUR_UPDATABLE, ...);
ResultSet
.
ResultSet kursor = stmt.executeQuery(sql);
while (kursor.next()) {
kursor.moveToInsertRow();
// ...
}
ResultSet.updateXXX(int columnIndex, XXX x)
za postavljanje vrednosti kolone “sloga za unos” sa indeksom columnIndex
novom vrednošću x
. Na primer:
kursor.updateInt(1, 100);
ResultSet.insertRow()
. U suprotnom će, prelaskom na drugi red, sve izmene biti ignorisane.
kursor.insertRow();
ResultSet.moveToCurrentRow()
.
kursor.moveToCurrentRow();
kursor.close()
radi zatvaranja kursora.stmt.close()
radi zatvaranja objekta naredbe.Naredni zadatak ilustruje upotrebu opisane procedure.
Zadatak 8.8: Napisati Java program u kojem se naredbe izvršavaju dinamički koji:
UNETIPREDMETI
čije su kolone: (1) identifikator predmeta i (2) broj položenih ispita za taj predmet. Postaviti odgovarajuće primarne i strane ključeve.UNETIPREDMETI
) pronalazi statistiku koja se sastoji od njegovog identifikator i broj položenih ispita.UNETIPREDMETI
. Ukoliko korisnik potvrdi, potrebno je uneti statistiku u datu tabelu i ispisati poruku o uspehu. U suprotnom, ispisati poruku da je korisnik poništio unos.Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_8/Main.java
:
package zadatak_8_8;
import java.sql.*;
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
private static class StatistikaPolaganja {
public int idPredmeta;
public int brojPolaganja;
public StatistikaPolaganja(int idPredmeta, int brojPolaganja) {
this.idPredmeta = idPredmeta;
this.brojPolaganja = brojPolaganja;
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
ArrayList<StatistikaPolaganja> statistike = new ArrayList<>();
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
kreirajTabelu(con);
sakupiStatistiku(con, statistike);
unesiPredmeteIzStatistike(con, statistike);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static void kreirajTabelu(Connection con) throws SQLException {
String sql = "CREATE TABLE DA.UNETIPREDMETI ( "
+ "IDPREDMETA INTEGER NOT NULL, "
+ "BROJPOLOZENIH INTEGER NOT NULL, "
+ "PRIMARY KEY (IDPREDMETA), "
+ "FOREIGN KEY (IDPREDMETA) REFERENCES DA.PREDMET "
+ ")";
Statement stmt = con.createStatement();
try {
stmt.executeUpdate(sql);
} catch (SQLException e) {
// Ignorisemo gresku samo ako je u pitanju greska da tabela vec postoji (-601).
// Sve ostale ispaljujemo main metodu.
if (e.getErrorCode() != -601) {
throw e;
}
}
stmt.close();
}
private static void sakupiStatistiku(Connection con, ArrayList<StatistikaPolaganja> statistike) throws SQLException {
String sql = "SELECT IDPREDMETA, COUNT(OCENA) " +
"FROM DA.ISPIT " +
"WHERE OCENA > 5 AND " +
" STATUS = 'o' AND " +
" IDPREDMETA NOT IN (SELECT IDPREDMETA FROM DA.UNETIPREDMETI) " +
"GROUP BY IDPREDMETA";
Statement stmt = con.createStatement();
ResultSet kursor = stmt.executeQuery(sql);
while (kursor.next()) {
int idPredmeta = kursor.getInt(1);
int brojPolaganja = kursor.getInt(2);
statistike.add(new StatistikaPolaganja(idPredmeta, brojPolaganja));
}
kursor.close();
stmt.close();
}
private static void unesiPredmeteIzStatistike(Connection con, ArrayList<StatistikaPolaganja> statistike) throws SQLException {
String sql =
"SELECT * " +
"FROM DA.UNETIPREDMETI";
Statement stmt = con.createStatement(
// Dovoljan nam je kursor koji prolazi unapred kroz redove.
ResultSet.TYPE_FORWARD_ONLY,
// Definisemo da je kursor azurirajuci,
// pa su nam dostupni metodi
// ResultSet.updateXXX i ResultSet.insertRow
// koje cemo koristiti za konstrukciju i unos novog reda
ResultSet.CONCUR_UPDATABLE);
ResultSet kursor = stmt.executeQuery(sql);
Scanner ulaz = new Scanner(System.in);
for (StatistikaPolaganja statistika : statistike) {
// Pozicioniramo kursor na "slog za unos"
kursor.moveToInsertRow();
// Konstruisemo "slog za unos" navodjenjem vrednost za svaku kolonu
kursor.updateInt(1, statistika.idPredmeta);
kursor.updateInt(2, statistika.brojPolaganja);
// Izvrsavamo ili ponistavamo unos u zavisnosti od korisnikovog odgovora
System.out.println("Da li zelite da unete statistiku za predmet " + statistika.idPredmeta + "? [da/ne]");
String odgovor = ulaz.nextLine();
if (odgovor.equalsIgnoreCase("da")) {
kursor.insertRow();
System.out.println("\tUneta je statistika: " + statistika.idPredmeta + " (" + statistika.brojPolaganja + ")");
}
else {
System.out.println("\tPonistili ste unos!");
}
// Vracamo kursor na zapamceni tekuci red
kursor.moveToCurrentRow();
}
ulaz.close();
kursor.close();
stmt.close();
}
}
Poput ažuriranja podataka, i za brisanje podataka postoje dve procedure:
DELETE
naredbe.deleteRow
.Kao što znamo, možemo koristiti pozicionirajuću DELETE naredbu za brisanje podataka iz
baze podataka, na osnovu tekućeg reda kursora, tako što se na kursor referiše u WHERE CURRENT OF
klauzi naredbe. Procedura za dohvatanje naziva kursora i izvršavanje pozicionirajuće DELETE
naredbe je ekvivalentna prethodno opisanoj proceduri za ažuriranje podataka korišćenjem pozicionirajuće UPDATE
naredbe, te ćemo samo prikazati fragment koda koji ilustruje ovu proceduru:
String upit =
"SELECT OZNAKA, " +
" NAZIV, " +
" ESPB " +
"FROM DA.PREDMET";
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(upit);
String nazivKursora = rs.getCursorName();
String brisanje =
"DELETE FROM DA.PREDMET " +
"WHERE CURRENT OF " + nazivKursora;
Statement ps = con.prepareStatement();
while (rs.next()) {
String nazivPredmeta = rs.getString(2);
ps.executeUpdate(brisanje);
System.out.println("Obrisan je predmet " + nazivPredmeta);
}
Brisanje sloga iz objekta ResultSet
je jednostavnije nego ažuriranje ili unos sloga.
Naredni koraci definišu operaciju brisanja sloga iz kursora:
Statement
za rad sa brišućim kursorima.
Statement stmt = con.createStatement(..., ResultSet.CONCUR_UPDATABLE, ...);
ResultSet
.
ResultSet kursor = stmt.executeQuery(sql);
while (kursor.next()) {
kursor.absolute(5); // Samo ako kursor nije tipa TYPE_FORWARD_ONLY
// ...
}
kursor.deleteRow()
koji će obrisati slog u bazi podatakakursor.close()
radi zatvaranja kursorastmt.close()
radi zatvaranja objekta naredbeMetod deleteRow()
briše onaj red na koji je ResultSet
pozicioniran. Ne postoji način
da se operacija brisanja poništi (osim poništavanjem transakcije, o čemu će biti
više reči u narednom poglavlju).
Zadatak 8.9: Napisati Java program u kojem se naredbe izvršavaju dinamički koji briše sve nepoložene ispite u školskoj godini koja se zadaje sa standarnog ulaza. Nakon svakog brisanja ispita, ispisati naredne informacije o njemu na standardni izlaz: indeks, oznaku roka, školsku godinu i identifikator predmeta.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_9/Main.java
:
package zadatak_8_9;
import java.sql.*;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
izbrisiNepolozeneIspite(con);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static void izbrisiNepolozeneIspite(Connection con) throws SQLException {
int godina = ucitajGodinu();
String selectSql =
"SELECT INDEKS, " +
" OZNAKAROKA, " +
" IDPREDMETA " +
"FROM DA.ISPIT " +
"WHERE SKGODINA = ? AND " +
" OCENA = 5 AND " +
" STATUS = 'o'";
PreparedStatement stmt = con.prepareStatement(selectSql,
ResultSet.TYPE_FORWARD_ONLY,
// Podesavamo kursor da bude za menjanje,
// da bi smo mogli da brisemo redove metodom deleteRow() .
ResultSet.CONCUR_UPDATABLE);
stmt.setInt(1, godina);
ResultSet ispiti = stmt.executeQuery();
while(ispiti.next()) {
int indeks = ispiti.getInt(1);
String oznakaRoka = ispiti.getString(2).trim();
int idPredmeta = ispiti.getInt(3);
ispiti.deleteRow();
System.out.printf("Obrisan je ispit %-10d %-10s %-5d %-10d\n",
indeks, oznakaRoka, godina, idPredmeta);
}
ispiti.close();
stmt.close();
}
private static int ucitajGodinu() {
int godina;
try (Scanner ulaz = new Scanner(System.in)) {
System.out.println("Unesite godinu roka za koju zelite da budu obrisani nepolozeni ispiti:");
godina = ulaz.nextInt();
}
return godina;
}
}
U JDBC aplikacijama se kursori jednostavno ugnežđuju: potrebno je celokupnu obradu unutrašnjeg kursora smestiti u okviru obrade jednog reda iz spoljašnjeg kursora. Naredni primer ilustruje upotrebu ugnežđenih kursora.
Zadatak 8.10: Napisati Java program u kojem se naredbe izvršavaju dinamički koji izdvaja indeks, ime, prezime i naziv studijskog programa svih studenata koji su položili tačno N predmeta, kao i spisak tih predmeta (naziv i ocena). Broj N se učitava sa standardnog ulaza. Za svakog studenta napraviti posebnu sekciju izveštaja.
Rešenje:
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_10/ispiti.sql
:
SELECT D.INDEKS,
TRIM(IME),
TRIM(PREZIME),
TRIM(NAZIV)
FROM DA.DOSIJE D JOIN
DA.STUDIJSKIPROGRAM S ON D.IDPROGRAMA = S.ID JOIN
DA.ISPIT I ON D.INDEKS = I.INDEKS
WHERE OCENA > 5 AND
STATUS = 'o'
GROUP BY D.INDEKS,
IME,
PREZIME,
NAZIV
HAVING COUNT(*) = ?
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_10/predmeti.sql
:
SELECT TRIM(NAZIV),
OCENA
FROM DA.ISPIT I JOIN
DA.PREDMET P ON I.IDPREDMETA = P.ID
WHERE I.INDEKS = ? AND
OCENA > 5 AND
STATUS = 'o'
ORDER BY NAZIV
Datoteka: vezbe/primeri/poglavlje_8/src/zadatak_8_10/Main.java
:
package zadatak_8_10;
import java.io.File;
import java.io.FileNotFoundException;
import java.sql.*;
import java.util.Scanner;
public class Main {
static {
try {
Class.forName("com.ibm.db2.jcc.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String argv[]) {
String url = "jdbc:db2://localhost:50000/stud2020";
try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
prodjiKrozSpoljasnjiKursor(con);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("SQLCODE: " + e.getErrorCode() + "\n" + "SQLSTATE: " + e.getSQLState() + "\n"
+ "PORUKA: " + e.getMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static void prodjiKrozSpoljasnjiKursor(Connection con) throws SQLException, FileNotFoundException {
int ukupnoPredmeta = ucitajBrojPredmeta();
PreparedStatement pstmt;
String sql = ucitajSqlIzDatoteke("ispiti.sql");
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, ukupnoPredmeta);
ResultSet ispiti = pstmt.executeQuery();
while (ispiti.next()) {
System.out.printf("\n\nIndeks: %-10d\nIme: %-10s\nPrezime: %-20s\nNaziv smera: %-30s\n\n", ispiti.getInt(1),
ispiti.getString(2), ispiti.getString(3), ispiti.getString(4));
int indeks = ispiti.getInt(1);
prodjiKrozUnutrasnjiKursor(con, indeks);
}
ispiti.close();
pstmt.close();
}
private static void prodjiKrozUnutrasnjiKursor(Connection con, int indeks) throws SQLException, FileNotFoundException {
PreparedStatement pstmt;
String sql = ucitajSqlIzDatoteke("predmeti.sql");
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, indeks);
ResultSet predmeti = pstmt.executeQuery();
int redniBr = 1;
while (predmeti.next()) {
System.out.printf("\t%d. predmet: %s\n\t\tOcena: %d\n", redniBr, predmeti.getString(1),
predmeti.getShort(2));
++redniBr;
}
predmeti.close();
pstmt.close();
}
private static int ucitajBrojPredmeta() {
int godina;
try (Scanner ulaz = new Scanner(System.in)) {
System.out.println("Unesite broj predmeta N:");
godina = ulaz.nextInt();
}
return godina;
}
private static String ucitajSqlIzDatoteke(String nazivDatoteke) throws FileNotFoundException {
String putanja = "./bin/zadatak_8_10/" + nazivDatoteke;
StringBuilder sql = new StringBuilder("");
String linija = null;
try (Scanner skenerFajla = new Scanner(new File(putanja), "utf-8")) {
while (skenerFajla.hasNextLine()) {
linija = skenerFajla.nextLine();
sql.append(linija);
sql.append("\n");
}
}
return sql.toString();
}
}
Zadatak 8.11: Napisati Java program u kojem se naredbe izvršavaju dinamički koji ispisuje podatke za predmet čiji je identifikator 2027
.
Zadatak 8.12: Napisati Java program u kojem se naredbe izvršavaju dinamički koji ispisuje broj studenata koji su upisali studije u školskoj godini koja se unosi sa standardnog ulaza.
Zadatak 8.13: Napisati Java program u kojem se naredbe izvršavaju dinamički koji za svaku ocenu od 6 do 10 ispisuje ime i prezime studenta koji je poslednji položio neki ispit sa tom ocenom. U slučaju da ima više takvih studenata, klauzom LIMIT 1
naredbe SELECT INTO
se osigurati da bude vraćen najviše 1 red. (Pomoć: Koristiti for
petlju za menjanje vrednosti matične promenljive koja sadrži ocenu, pa u svakoj iteraciji dohvatiti informaciju za tekuću vrednost te matične promenljive.)
Zadatak 8.14: Napisati Java program u kojem se naredbe izvršavaju dinamički kojim se dodaje da je za polaganje predmeta čiji je identifikator 2327
uslov da se položi predmet čiji je identifikator 1588
.
Zadatak 8.15: Napisati Java program u kojem se naredbe izvršavaju dinamički kojim se u tabelu NIVOKVALIFIKACIJE
dodaje novi nivo čiji se identifikator i naziv.
Zadatak 8.16: Napisati Java program u kojem se naredbe izvršavaju dinamički kojim se, za sve položene ispite čiji se naziv predmeta unosi sa standardnog ulaza, ocena uvećava za 1.
Zadatak 8.17: Napisati Java program u kojem se naredbe izvršavaju dinamički kojim se u tabeli USLOVNIPREDMET
brišu sve uslovnosti za predmet čiji se identifikator unosi sa standardnog ulaza.
Zadatak 8.18: Napisati Java program u kojem se naredbe izvršavaju dinamički kojim se brišu svi podaci o ispitima za studenta čiji se indeks unosi sa standardnog ulaza.
Zadatak 8.19: Napisati Java program u kojem se naredbe izvršavaju dinamički koji iz tabele PREDMET
briše podatak o predmetu čija se oznaka unosi sa standardnog ulaza.