Prikaži sadržaj

Programiranje baza podataka

9. Napredne tehnike razvijanja Java/SQL aplikacija

Ova stranica je pod konstrukcijom!
Ako pronađete grešku ili propust, molimo Vas da nam skrenete pažnju otvaranjem primedbe na zvaničnom GitHub repozitorijumu.

U ovom poglavlju ćemo demonstrirati rad sa transakcijama, rad u konkurentnom okruženju, kao i povezivanje na više baza podataka u JDBC aplikacijama.

9.1 Transakcioni rad

JDBC podržava naredne koncepte za upravljanje transakcijama:

9.1.1 Režim automatskog potvrđivanja izmena

Prilikom povezivanja na bazu podataka, JDBC drajver podrazumevano postavlja svojstvo objekta interfejsa Connection automatskog potvrđivanja izmena na true. Ako je konekcija podešena u režimu automatskog potvrđivanja izmena, onda će svaka SQL naredba koja se izvrši nad bazom podataka biti u isto vreme i potvrđena (u slučaju uspešnog izvršenja te naredbe) ili poništena (u slučaju neuspešnog izvršenja te naredbe).

Ovo ponašanje nam često nije poželjno, s obzirom da je neophodno da sami definišemo koje sve SQL naredbe predstavljaju deo transakcija u aplikacijama. Zbog toga, potrebno je isključiti režim automatskog potvrđivanja izmena. Ovo se izvršava pozivom metoda Connection.setAutoCommit(boolean autoCommit) i prosle\d jivanjem vrednosti false, nakon što se konekcija ka bazi podataka uspešno uspostavi.

// Dohvatanje konekcije
Connection con = DriverManager.getConnection(dbURL, userId, password);

// Isključivanje režima automatskog potvrđivanja izmena
con.setAutoCommit(false);

Ako se metod setAutoCommit poziva da bi se promenio režim automatskog potvrđivanja izmena u toku izvršavanja neke transakcije, ta transakcija će biti potvrđena u tom trenutku. Zbog toga što ovakvo ponašanje može dovesti do neočekivanih ponašanja u odnosu na poslovnu logiku aplikacije, obično se režim automatskog potvrđivanja izmena postavlja odmah nakon povezivanja na bazu podataka, kao u primeru koda iznad.

Potvrđivanje i poništavanje izmena u transakcijama

Jednog kada je režim automatskog potvrđivanja izmena isključen, na raspolaganju su nam metodi Connection.commit() i Connection.rollback() kojima se upravlja dužina trajanja i uspešnost transakcija.

Uobičajeni kod u JDBC aplikacijama koji izvršava transakciju nad bazom podataka je dat u nastavku:

try (
    // Get a connection
    Connection con = DriverManager.getConnection(dbURL, userId, password);
) {
    // Set the auto-commit off
    con.setAutoCommit(false);

    // Perform database transaction activities here
    
    // Successful scenario:
    con.commit();
}
catch (SQLException e) {
    System.out.println("An error occured: " + e.getMessage());
    System.out.println("Rolling back the transaction");

    try {
        // Unsuccessful scenario:
        con.rollback();
    }
    catch (SQLException e) {}

    System.exit(2);
}
catch (Exception e) {
    // ...
}

Zadatak 9.1: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji redom:

  1. Pronalazi i ispisuje najveći indeks iz tabele ISPIT.
  2. Briše studenta sa pronađenim indeksom iz tabele ISPIT i ispisuje poruku korisniku o uspešnosti brisanja.
  3. Ponovo pronalazi i ispisuje najveći indeks iz tabele ISPIT.
  4. Pita korisnika da li želi da potvrdi ili poništi izmene. U zavisnosti od korisnikovog odgovora, aplikacija potvrđuje ili poništava izmene uz ispisivanje poruke korisniku.
  5. Ponovo pronalazi i ispisuje najveći indeks iz tabele ISPIT.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_1/Main.java:

package zadatak_9_1;

import java.sql.*;
import java.util.Scanner;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static void main(String argv[]) {
        String url = "jdbc:db2://localhost:50000/stud2020";

        try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
            // Iskljucujemo automatsko potvrdjivanje izmena
            con.setAutoCommit(false);
            // Moramo da imamo unutrasnji try-catch blok,
            // kako bismo pozvali metode commit() ili rollback()
            // tik pred zatvaranje konekcije
            try {
                Integer indeks = pronadjiNajveciIndeks(con);
                System.out.println("1. Najveci indeks u tabeli ISPIT je " + indeks);
                obrisiIspite(con, indeks);
                indeks = pronadjiNajveciIndeks(con);
                System.out.println("3. Najveci indeks u tabeli ISPIT je " + indeks);
                potvrdiIliPonistiIzmene(con);
                indeks = pronadjiNajveciIndeks(con);
                System.out.println("5. Najveci indeks u tabeli ISPIT je " + indeks);
                
                // S obzirom da je sve proslo kako treba ako se doslo do ove tacke,
                // potvrdjujemo izmene pre raskidanje konekcije
                con.commit();
            } catch (Exception e) {
                // Ako je bilo gde u kodu u try bloku doslo do gresaka,
                // ovim se osiguravamo da ce rollback() metod biti pozvan
                // pre automatskog zatvaranja konekcije nakon napustanja try-with-resources bloka
                con.rollback();
                throw e;
            }
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static Integer pronadjiNajveciIndeks(Connection con) throws SQLException, Exception {
        String sql = 
            "SELECT  MAX(INDEKS)" +
            "FROM    DA.ISPIT";
        Statement stmt = con.createStatement();
        ResultSet kursor = stmt.executeQuery(sql);
        
        Integer indeks = null;
        boolean imaRezultata = kursor.next();
        if (imaRezultata) {
            indeks = kursor.getInt(1);
        }
        else {
            throw new Exception("Nema ispita u bazi");
        }
        
        kursor.close();
        stmt.close();
        
        return indeks;
    }

    private static void obrisiIspite(Connection con, Integer indeks) throws SQLException {
        String sql = 
            "DELETE  FROM DA.ISPIT " +
            "WHERE   INDEKS = ?";
        PreparedStatement stmt = con.prepareStatement(sql);
        stmt.setInt(1, indeks);
        
        stmt.executeUpdate();
        
        System.out.println("2. Obrisani su ispiti za studenta sa indeksom " + indeks);
    }

    private static void potvrdiIliPonistiIzmene(Connection con) throws SQLException {
        System.out.println("4. Da li zelite da potvrdite izmene [y] ili da ponistite izmene [n]?");
        try (Scanner ulaz = new Scanner(System.in)) {
            String odgovor = ulaz.next();
            if (odgovor.equalsIgnoreCase("y")) {
                // Potvrdjivanje izmena
                con.commit();
                System.out.println("Izmene su potvrdjene!");
            }
            else {
                // Ponistavanje izmena
                con.rollback();
                System.out.println("Izmene su ponistene!");
            }
        }
    }
}

9.1.2 Nivoi izolacije transakcija

ANSI SQL-92 standard definiše četiri nivoa izolacije transakcija u terminima konzistentnosti podataka. Svaki nivo izolacije definiše koje vrste nekonzistentnosti podataka jesu ili nisu dozvoljene. Ovi nivoi su:

JDBC standard definiše naredne četiri statičke konstante interfejsa Connection koji odgovaraju nivoima izolacije ANSI SQL-92 standarda, redom:

Postavljanje nivoa izolacije transakcija za bazu podataka se može izvršiti pozivom metoda Connection.setTransactionIsolation(int level).

// Get a Connection object
Connection con = DriverManager.getConnection(dbURL, userId, password);

// Set the transaction isolation level to read committed
con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

Zadatak 9.2: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji iz tabele UKUPNIBODOVI (videti ispod) izdvaja 10 najuspešnijih studenata. Za svakog studenta ispisati podatke iz te tabele i upitati korisnika da li želi da dodeli tom studentu počasnih 10 ESPB. Ukoliko želi, izvršiti odgovarajuću izmenu. Nakon svih izmena, ispisati izveštaj rada u kojem se vide izmene. Sve izmene i prikaz izveštaja implementirati kao jednu transakciju. Omogućiti da nijedan drugi korisnik ne može da vidi izmene tokom rada ovog programa.

Rešenje: Pre početka izvršavanja, izvršiti naredni skript nad bazom podataka STUD2020 koji će pripremiti podatke:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_2/priprema_baze.sql:

CREATE TABLE DA.UKUPNIBODOVI (
    INDEKS  INTEGER     NOT NULL,
    IME     VARCHAR(25) NOT NULL,
    PREZIME VARCHAR(25) NOT NULL,
    ESPB    INTEGER     NOT NULL,
    PRIMARY KEY(INDEKS),
    FOREIGN KEY(INDEKS) REFERENCES DA.DOSIJE    
);

INSERT      INTO DA.UKUPNIBODOVI
SELECT      D.INDEKS, 
            IME, 
            PREZIME, 
            SUM(CASE 
                WHEN OCENA > 5 AND STATUS = 'o' THEN P.ESPB 
                ELSE 0 
                END) UKUPNO_BODOVA
FROM        DA.DOSIJE D JOIN
            DA.ISPIT I ON D.INDEKS = I.INDEKS JOIN
            DA.PREDMET P ON I.IDPREDMETA = P.ID
GROUP BY    D.INDEKS, IME, PREZIME
ORDER BY    UKUPNO_BODOVA DESC;

S obzirom da ćemo menjati odgovarajuću tabelu, naredbu stmt koja se koristi za kreiranje kursora koji prolazi tabelom moramo kreirati opcijom ResultSet.CONCUR_UPDATABLE. Međutim, da bismo sprečili sve ostale programe da vide izmene, potrebno je da postavimo najstrožiji nivo izolacije, tj. da pozovemo metod setIsolationLevel() sa argumentom Connection.TRANSACTION_SERIALIZABLE.

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_2/Main.java:

package zadatak_9_2;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static void main(String argv[]) {
        String url = "jdbc:db2://localhost:50000/stud2020";

        try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
            con.setAutoCommit(false);
            con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

            try (Scanner ulaz = new Scanner(System.in)) {
                izdvoji10NajuspesnijihStudenata(con, ulaz);
                con.commit();
            } catch (Exception e) {
                con.rollback();
                throw e;
            }
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static void izdvoji10NajuspesnijihStudenata(Connection con, Scanner ulaz)
            throws SQLException, IOException {
        Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        String sql = 
                "SELECT  * " + 
                "FROM    DA.UKUPNIBODOVI " +
                "FETCH   FIRST 10 ROWS ONLY";
        ResultSet kursor = stmt.executeQuery(sql);

        System.out.println("Obradjujem 10 najuspesnijih studenata: \n");
        int i = 1;
        while (kursor.next()) {
            int indeks = kursor.getInt(1);
            String ime = kursor.getString(2).trim();
            String prezime = kursor.getString(3).trim();
            int espb = kursor.getInt(4);

            System.out.println(i + ". " + ime + " " + prezime + "(" + indeks + ") ima osvojenih " + espb + " ESPB.");
            System.out.println("Da li zelite da dodelite 10 pocasnih bodova? [d/n]");
            String odgovor = ulaz.nextLine();
            if (odgovor.equalsIgnoreCase("d")) {
                kursor.updateInt(4, espb + 10);
                kursor.updateRow();
                System.out.println("Uspesno ste dodeliti pocasne poene!");
            }
            System.out.println();
            ++i;
        }
        kursor.close();
        
        System.out.println("Unesite \"I\", pa zatim ENTER za prikaz izvestaja:");
        ulaz.nextLine();
        
        kursor = stmt.executeQuery(sql);
        i = 1;
        while (kursor.next()) {
            int indeks = kursor.getInt(1);
            String ime = kursor.getString(2);
            String prezime = kursor.getString(3);
            int bodovi = kursor.getInt(4);

            System.out.println(i + ". " + ime + " " + prezime + "(" + indeks + ") ima osvojenih " + bodovi + " ESPB.");
            ++i;
        }
        kursor.close();
        
        // Zavrsavam transakciju
        con.commit();
        
        stmt.close();
    }
}

9.1.3 Tačke čuvanja u transakciji

Kao što znamo, transakcija u bazi podataka se sadrži od jedne ili više izmena u okviru jedne jedinice posla. Poništavanje transakcije podrazumevano poništava sve izmene definisane u toj jedinici posla.

Tačka čuvanja predstavlja marker koji određuje tačku u transakciji do koje se mogu poništiti izmene. Drugim rečima, poništavanje transakcije do neke tačke čuvanja će poništiti samo one izmene koje su nastale nakon te tačke čuvanja (a one koje su nastale pre tačke čuvanja će ostati neponištene).

U JDBC aplikacijama, tačke čuvanja se reprezentuju objektima interfejsa Savepoint. Kako bi se markirala tačka u tekućoj transakciji, potrebno je pozvati metod Connection.setSavepoint(). Ovaj metod kreira novu, neimenovanu tačku čuvanja u tekućoj transakciji i vraća objekat interfejsa Statement koji je reprezentuje. Ovaj objekat se koristi za upravljanje tačkom čuvanja nadalje. Naredni primer ilustruje kreiranje nekoliko tačaka čuvanja u okviru transakcije:

Connection con = DriverManager.getConnection(dbURL, userId, password);
con.setAutoCommit(false);

Statement stmt = con.createStatement();

stmt.executeUpdate("insert into person values ('John', 'Doe')");
Savepoint sp1 = con.setSavepoint(); // 1

stmt.executeUpdate("insert into person values ('Jane', 'Doe')");
Savepoint sp2 = con.setSavepoint(); // 2

stmt.executeUpdate("insert into person values ('Another', 'Unknown')");
Savepoint sp3 = con.setSavepoint(); // 3

Nakon kreiranja poslednje tačke čuvanja, korisnik ima opciju da poništi izmene do bilo koje od tačaka čuvanja spX. Da bi se ova akcija ostvarila, potrebno je pozvati metod Connection.rollback(Savepoint savepoint) čiji argument definiše tačku čuvanja do koje se vrši poništavanje transakcije. Na primer, ako bismo želeli da poništimo sve izmene nakon tačke čuvanja 1, iskoristili bismo poziv:

// Rolls back inserts 2 and 3
con.rollback(sp1);

Važno je napomenuti da jednom kad smo poništili izmene do neke tačke čuvanja, (recimo, sp1), sve tačke čuvanja koje su kreirane nakon ove tačke čuvanja (sp2 i sp3) biće oslobođene i ne bi trebalo referisati na njih nadalje. Pokušaj da se oslobodi već oslobođena tačka čuvanja rezultuje ispaljivanjem izuzetka SQLException. Na primer, naredni kod će ispaliti izuzetak SQLException (pod pretpostavkom da prethodno nisu oslobođene tačke čuvanja sp1 i sp2):

con.rollback(sp2); // Will release sp3
con.rollback(sp3); // Will throw an exception: sp3 is already released.

Primetimo da prilikom poništavanja transakcije do neke tačke čuvanja, sama tačka čuvanja do koje se izvršilo poništavanje neće biti oslobođena. Na primer, u primeru koda iznad, tačka čuvanja sp2 je ostala aktivna i korisnik može ponovo poništiti izmene do nje.

con.rollback(sp2); // OK
con.rollback(sp2); // OK

Zadatak 9.3: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji pronalazi indekse i nazive predmeta za sva polaganja koja su bila neuspešna. Sortirati podatke po indeksu rastuće. Obezbediti da aplikacija briše podatke o najviše 10 studenata. Jednu transakciju čine brisanja za sve pronađene studente. Prilikom obrade podataka, ispisati informacije o indeksu studenta, a zatim prikazati nazive predmeta za obrisana polaganja tog studenta. Nakon brisanja podataka o jednom studentu, upitati korisnika da li želi da poništi izmene za tog studenta (voditi računa da brisanja za sve prethodne studente ostanu nepromenjena).

Rešenje: Ono što je potrebno uraditi jeste, pre brisanja slogova za jednog studenta, postaviti tačku čuvanja. Nakon što se detektuje da se dohvatio slog za narednog studenta, aplikacija treba da pita korisnika da li želi da poništi izmene od postavljene tačke čuvanja. Tako se obezbeđujemo da samo obrisani redovi za tekućeg studenta budu poništeni, a ne i za sve prethodne.

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_3/Main.java:

package zadatak_9_3;

import java.io.*;
import java.nio.file.*;
import java.sql.*;
import java.util.*;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static void main(String argv[]) {
        String url = "jdbc:db2://localhost:50000/stud2020";

        try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
            con.setAutoCommit(false);

            try (Scanner ulaz = new Scanner(System.in)) {
                obrisiNeuspesnaPolaganja(con, ulaz);
                con.commit();
            } catch (Exception e) {
                con.rollback();
                throw e;
            }
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static void obrisiNeuspesnaPolaganja(Connection con, Scanner ulaz)
            throws SQLException, IOException {
        // Koristimo TYPE_SCROLL_SENSITIVE jer upit sadrzi ORDER BY klauzu.
        Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        String sql = ucitajSqlIzDatoteke();
        ResultSet kursor = stmt.executeQuery(sql);

        Savepoint s = null;
        int prethodniIndeks = -1;
        int brojStudenta = 0;
        while (kursor.next()) {
            int indeks = kursor.getInt(1);
            String naziv = kursor.getString(2).trim();
            
            // Ako smo procitali podatke za novog studenta
            if (indeks != prethodniIndeks) {
                // Nemamo sta da uradimo ako obradjujemo prvog studenta
                if (0 != brojStudenta) {
                    // Ako smo vec obradili barem jednog studenta,
                    // onda treba da pitamo korisnika da li zeli da ponisti brisanja za prethodnog studenta
                    System.out.printf("Da li zelite da ponistite brisanja? [da/ne] ");
                    String odgovor = ulaz.nextLine();
                    if (odgovor.equalsIgnoreCase("da")) {
                        con.rollback(s);
                    } else {
                        con.releaseSavepoint(s);
                    }
                }
                
                // Ako je redni broj 10, izlazimo iz petlje
                if (10 == brojStudenta) {
                    break;
                }
                // Inace, pre nego sto predjemo na menjanje podataka za novog studenta,
                // moramo da postavimo novu tacku cuvanja i 
                // azuriramo informacije o prethodnom indeksu i rednom broju
                s = con.setSavepoint();
                prethodniIndeks = indeks;
                ++brojStudenta;
                System.out.println("Brisem nepolozena polaganja za " + brojStudenta + ". studenta: " + indeks);
            }

            System.out.printf("    %s\n", naziv);
            kursor.deleteRow();
        }
        kursor.close();        
        stmt.close();
    }
    
    private static String ucitajSqlIzDatoteke() throws IOException {
        StringBuilder sql = new StringBuilder();
        Files.lines(Paths.get(System.getProperty("user.dir") + "/bin/zadatak_9_3/upit.sql"))
            .forEach(linija -> sql.append(linija).append("\n"));
        return sql.toString();
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_3/upit.sql:

SELECT      INDEKS,
            (SELECT NAZIV FROM DA.PREDMET WHERE ID = I.IDPREDMETA) 
FROM        DA.ISPIT I
WHERE       OCENA = 5 AND
            STATUS = 'o' 
ORDER BY    INDEKS

9.2 Rad u višekorisničkom okruženju

U poglavlju 5 smo govorili detaljno o problemima konkurentnog rada sa bazom podataka. Aplikacije pisane u programskom jeziku Java, bilo one SQLJ ili JDBC, takođe “boluju” od istih problema kao i aplikacije koje smo pisali u programskom jeziku C. Zbog toga ćemo imati na umu sve napomene koje smo tada uveli, sa određenim napomenama koje slede u daljem tekstu.

S obzirom da se u programskom jeziku Java greške prijavljuju kroz objekte klase SQLException, proveru da li je došlo do nekog problema konkurentnog okruženja možemo izvršiti proverom koda greške, pozivom metoda getErrorCode() nad objektom klase SQLException koji smo uhvatili. Zašto nam je ova informacija važna? Prisetimo se da, ukoliko dođe do nekog problema konkurentnog okruženja, DB2 SUBP šalje grešku -911 ili -913. Dodatno, u slučaju da aplikacija zahteva ekskalaciju katanaca, ali menadžer baze podataka ne uspe da izvrši tu operaciju, onda će aplikaciji biti prijavljena greška -912. U tom slučaju, potrebno je izvršiti obradu isteka vremena ili pojave mrtve petlje, i poništiti eventualne izmene. Evo jednog primera:

// U main funkciji:

Connection con = null;
String sql = "SELECT ...";
Statement stmt = con.createStatement(
    ResultSet.TYPE_FORWARD_ONLY,
    ResultSet.CONCUR_UPDATABLE,
    ResultSet.HOLD_CURSORS_OVER_COMMIT
);
ResultSet kursor = otvoriKursor(stmt, sql);

// Petlja koja prolazi kroz kursor
while(true) {
    try {
        // Kod koji moze da dovede do problema
        // u visekorisnickom okruzenju
    }
    catch (SQLException e) {
        // Ako je doslo do izuzetka zbog katanaca...
        if (-913 <= e.getErrorCode() && e.getErrorCode() <= -911) {
            // ... onda ih je potrebno obraditi
            kursor.close();
            kursor = obradiCekanje("FETCH, UPDATE, ...", con, stmt, sql);
            continue;
        }
        // Inace, neka druga greska je u pitanju,
        // pa ju je potrebno proslediti kodu za obradu greske
        throw e;
    }
}

// *********************************************************
// Izvan main funkcije:
private static ResultSet otvoriKursor(Statement stmt, String sql) throws SQLException {
    ResultSet kursor = stmt.executeQuery(sql);
    return kursor;
}

private static ResultSet obradiCekanje(String codeHint, Connection con, Statement stmt, String sql) throws SQLException {
    System.out.printf("[%s] Objekat je zakljucan od strane druge transakcije!\n" +
        "Molimo sacekajte!\n", codeHint);

    try {
        con.rollback();
    } catch (SQLException e) {
    }

    return otvoriKursor(stmt, sql);
}

Naredni primeri ilustruju konstrukciju JDBC aplikacija koje koriste transakcioni rad u višekorisničkom okruženju.

Zadatak 9.4: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji za svaki predmet koji je obavezan na studijskom programu čiji je identifikator 103, pita korisnika da li želi da poveća broj ESPB bodova za 1. Ukoliko je odgovor korisnika ”da”, izvršava se odgovarajuća naredba. Zadatak uraditi tako da aplikacija radi u višekorisničkom okruženju. Obrada jednog predmeta treba da predstavlja jednu transakciju. Postaviti istek vremena na 5 sekundi. Omogućiti da drugi korisnici mogu da pristupaju predmetima koje ovaj program trenutno obrađuje.

Rešenje: Da bismo implementirali date zahteve, potrebno je da pamtimo koje smo predmete obrađivali. Zato ćemo kreirati promenljivu obradjeniPredmeti tipa ArrayList<Integer> koja će čuvati informaciju o identifikatorima predmeta koje smo obradili. Zbog toga što se primarni ključ tabele PREDMET sastoji samo od jedne kolone tipa INTEGER, to je šablonski parametar kolekcije ArrayList tipa Integer. U slučaju složenih primarnih ključeva, neophodno je da kreiramo klasu koja sadrži atribute koji odgovaraju kolonama složenog primarnog ključa (videti zadatke za vežbu). Dodatno, potrebno je da postavimo nivo izolovanosti stabilno čitanje, kako bismo omogućili da drugi programi mogu da čitaju slogove koji se ne ažuriraju od strane naše aplikacije.

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_4/Main.java:

package zadatak_9_4;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
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();
            System.exit(-1);
        }
    }

    public static void main(String argv[]) {
        String url = "jdbc:db2://localhost:50000/stud2020";

        try (Connection con = DriverManager.getConnection(url, "student", "abcdef")) {
            con.setAutoCommit(false);
            con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

            // Postavljanje isteka vremena za katance.
            Statement lockStmt = con.createStatement();
            lockStmt.execute("SET CURRENT LOCK TIMEOUT 5");
            
            try {                
                obradiPredmete(con);
                
                con.commit();
            } catch (Exception e) {
                con.rollback();
                throw e;
            } finally {
                con.setAutoCommit(true);
                // Vracanje podrazumevane vrednosti za istek vremena
                lockStmt.execute("SET CURRENT LOCK TIMEOUT NULL");
                lockStmt.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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static void obradiPredmete(Connection con) throws SQLException, IOException {
        try (Scanner ulaz = new Scanner(System.in)) {
            ArrayList<Integer> obradjeniPredmeti = new ArrayList<>();
            String sql = ucitajSql();
                
            Statement stmt = con.createStatement(
                ResultSet.TYPE_FORWARD_ONLY, 
                ResultSet.CONCUR_UPDATABLE,
                // Kursor deklarisemo sa opcijom HOLD_CURSORS_OVER_COMMIT
                // da bi ostao otvoren prilikom izvrsavanja COMMIT naredbe.
                ResultSet.HOLD_CURSORS_OVER_COMMIT);
            
            ResultSet kursor = otvoriKursor(stmt, sql);
            
            // Citanje reda moze dovesti do problema zbog S ili U katanaca,
            // te moramo poziv metoda next() obraditi zasebno,
            // pa zato ide unutar petlje za obradu.
            boolean imaRedova = true;
            while(true) {
                // S ili U katanac
                try {
                    imaRedova = kursor.next();
                }
                catch (SQLException e) {
                    // Obrada katanaca
                    if (-913 <= e.getErrorCode() && e.getErrorCode() <= -911) {
                        kursor.close();
                        kursor = obradiCekanje("FETCH", con, stmt, sql);
                        continue;
                    }
                    throw e;
                }
                
                // Izlaz iz beskonacne petlje
                // ukoliko vise nema redova u kursoru
                if (!imaRedova) {
                    break;
                }
                
                // Inace, dohvatamo podatke
                int idPredmeta = kursor.getInt(1);
                String naziv = kursor.getString(2);
                short espb = kursor.getShort(3);
                
                // Preskacemo one predmete koje smo vec obradili
                if (obradjeniPredmeti.contains(idPredmeta)) {
                    continue;
                }
                
                System.out.printf("\nPredmet %s ima %d bodova\n", naziv.trim(), espb);
                System.out.println("Da li zelite da uvecate broj bodova za 1? [da/ne]");
                
                String odgovor = ulaz.next();
                if (odgovor.equalsIgnoreCase("da")) {
                    // X katanac
                    try {
                        // Ovde koristimo metode updateXXX i updateRow za azuriranje podataka.
                        // Za vezbu uraditi zadatak pozicionirajucom UPDATE naredbom.
                        kursor.updateShort(3, (short) (espb + 1));
                        kursor.updateRow();
                    }
                    catch (SQLException e) {
                        if (-913 <= e.getErrorCode() && e.getErrorCode() <= -911) {
                            kursor.close();
                            kursor = obradiCekanje("UPDATE", con, stmt, sql);
                            continue;
                        }
                        throw e;
                    }
                    
                    System.out.println("Uspesno su azurirani bodovi za tekuci predmet!");
                }
                
                // Evidentiranje obrade tekuceg predmeta
                obradjeniPredmeti.add(idPredmeta);
                
                // Zavrsavamo jednu transakciju
                con.commit();
                
                System.out.println("Da li zelite da zavrsite sa obradom? [da/ne]");
                odgovor = ulaz.next();
                
                if (odgovor.equalsIgnoreCase("da")) {
                    break;
                }
            }
            
            kursor.close();
            stmt.close();
        }
    }

    private static ResultSet otvoriKursor(Statement stmt, String sql) throws SQLException {
        ResultSet kursor = stmt.executeQuery(sql);
        return kursor;
    }
    
    private static ResultSet obradiCekanje(String codeHint, Connection con, Statement stmt, String sql) throws SQLException {
        System.out.printf("[%s] Objekat je zakljucan od strane druge transakcije!\n" +
                "Molimo sacekajte!\n", codeHint);
        
        try {
            con.rollback();
        } catch (SQLException e) {
        }
        
        return otvoriKursor(stmt, sql);
    }
    
    private static String ucitajSql() throws IOException {
        StringBuilder sql = new StringBuilder();
        Files.lines(Paths.get(System.getProperty("user.dir") + "/bin/zadatak_9_4/upit.sql"))
            .forEach(linija -> sql.append(linija).append("\n"));
        return sql.toString();
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_4/upit.sql:

SELECT ID,  
       NAZIV,  
       ESPB 
FROM   DA.PREDMET 
WHERE  ID IN ( 
           SELECT  IDPREDMETA 
           FROM    DA.PREDMETPROGRAMA 
           WHERE   IDPROGRAMA = 103 AND 
                      VRSTA = 'obavezan'
       );

9.3 Povezivanje na više baza podataka

Upravljanje podacima nad više baza podataka u JDBC aplikacijama je značajno jednostavnije nego u C aplikacijama sa ugnežđenim SQL-om.

U JDBC aplikacijama možemo imati proizvoljan broj objekata interfejsa Connection i svaki od njih predstavlja i ostvaruje konekciju ka jednoj bazi podataka. Pretpostavimo da imamo dve konekcije ka bazama podataka X i Y ostvarene kroz objekte conX i conY interfejsa Connection.

Ukoliko je potrebno da izvršimo naredbu nad bazom podataka X, onda je potrebno da kreiramo objekat naredbe (bilo kroz klasu Statement ili PreparedStatement) koristeći objekat conX. Za izvršavanje naredbe nad drugom bazom podataka Y, koristićemo objekat conY za kreiranje objekta naredbe.

Naredni primer ilustruje korišćenje dve baze podataka: VSTUD i MSTUD.

Zadatak 9.5: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji omogućava konekciju na 2 baze (vstud i mstud). Program redom:

  1. Zahteva od korisnika da unese broj bodova B.
  2. Iz baze mstud izdvaja indeks, ime i prezime studenata koji su položili sve predmete koji nose više od B bodova.
  3. Zatim, zahteva od korisnika da unese ocenu O (ceo broj od 6 do 10).
  4. Iz baze vstud izlistava indeks, naziv, ocenu, godinu i oznaku ispitnog roka za sve studente koji nikada nisu dobili ocenu manju nego što je ocena O.
  5. Nakon ispisivanja tih podataka, u bazi mstud, iz tabele ispit briše sva polaganja za studenta sa najmanjim brojem indeksa I iz dosije, i vraća I.
  6. Na kraju, u bazi vstud, u tabeli PREDMET za sve predmete koje je položio student sa brojem indeksa I, uvećava broj bodova za jedan (osim ako je broj bodova veći od 10, tada ostavlja nepromenjeno stanje).

Rešenje: Datoteka Main.java implementira navedene funkcionalnosti, dok prateće *.sql datoteke sadrže SQL naredbe koje se koriste u rešenju.

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_5/Main.java:

package zadatak_9_5;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static void main(String argv[]) {
        String urlVstud = "jdbc:db2://localhost:50000/stud2020";
        String urlMstud = "jdbc:db2://localhost:50001/mstud";

        try (
            Connection conVstud = DriverManager.getConnection(urlVstud, "student", "abcdef");
            Connection conMstud = DriverManager.getConnection(urlMstud, "student", "abcdef");
        ) {
            conVstud.setAutoCommit(false);
            conMstud.setAutoCommit(false);

            try (Scanner ulaz = new Scanner(System.in)) {
                // Program redom:
                // Zahteva od korisnika da unese broj bodova B.

                System.out.println("Unesite broj bodova B:");
                short brojBodova = ulaz.nextShort();

                // Iz baze MSTUD izdvaja indeks, ime i prezime studenata
                // koji su polozili sve predmete koji nose vise od B bodova.

                izlistajStudenteMstud(conMstud, brojBodova);

                // Zatim, zahteva od korisnika da unese ocenu O (ceo broj od 6
                // do 10).

                System.out.println("Unesite ocenu O:");
                short ocena = ulaz.nextShort();

                // Iz baze VSTUD izlistava indeks, naziv, ocenu, godinu i oznaku
                // ispitnog roka
                // za sve studente koji nikada nisu dobili ocenu manju nego sto
                // je ocena O.

                izlistajPolaganjaVstud(conVstud, ocena);

                // Nakon ispisivanja tih podataka, u bazi MSTUD, iz tabele ISPIT
                // brise sva polaganja za studenta sa maksimalnim brojem indeksa
                // I
                // iz DOSIJE, i vraca I.

                int indeks = obrisiPolaganjaIVratiIndeksMstud(conMstud);

                // Na kraju, u bazi VSTUD, u tabeli PREDMET
                // za sve predmete koje je polozio student sa brojem indeksa I,
                // uvecava broj bodova za jedan (osim ako je broj bodova veci od
                // 10,
                // tada ostavlja nepromenjeno stanje).

                uvecajBodoveZaPredmeteVstud(conVstud, indeks);
                
                // Potvrdjivanje izmena mora da se vrsi nad obe baze!
                conVstud.commit();
                conMstud.commit();
            } catch (Exception e) {
                // Ponistavanje izmena mora da se vrsi nad obe baze!
                conVstud.rollback();
                conMstud.rollback();
                throw e;
            } 
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static void izlistajStudenteMstud(Connection con, short brojBodova)
            throws SQLException, IOException {
        String sql = ucitajSqlIzDatoteke("izlistajStudenteMstud.sql");
        PreparedStatement stmt = con.prepareStatement(sql);

        stmt.setShort(1, brojBodova);
        ResultSet rez = stmt.executeQuery();

        System.out.println("\n\nStudenti koji su polozili sve predmete od " + brojBodova + " bodova\n");
        while (rez.next()) {
            System.out.println("Indeks: " + rez.getInt(1) + ", " + "Ime: " + rez.getString(2).trim() + ", "
                    + "Prezime: " + rez.getString(3).trim() + ", ");
        }

        rez.close();
        stmt.close();
    }

    private static void izlistajPolaganjaVstud(Connection con, short ocena) 
            throws SQLException, IOException {
        String sql = ucitajSqlIzDatoteke("izlistajPolaganjaVstud.sql");
        PreparedStatement stmt = con.prepareStatement(sql);

        stmt.setShort(1, ocena);
        ResultSet rez = stmt.executeQuery();

        System.out.println("Polozeni ispiti studenata koji nemaju ocenu manju od " + ocena);
        while (rez.next()) {
            System.out.println("Indeks: " + rez.getInt(1) + ", " + "Naziv: " + rez.getString(2).trim() + ", "
                    + "Ocena: " + rez.getInt(3) + ", " + "Godina roka: " + rez.getInt(4) + ", " + "Oznaka roka: "
                    + rez.getString(5).trim());
        }

        rez.close();
        stmt.close();
    }

    private static int obrisiPolaganjaIVratiIndeksMstud(Connection con) 
            throws Exception {
        int indeks = 0;
        Statement stmt = con.createStatement();
        ResultSet rez = stmt.executeQuery(
                "SELECT  MIN(INDEKS) " + 
                "FROM    DOSIJE");

        boolean dohvacenIndeks = rez.next();
        if (!dohvacenIndeks) {
            stmt.close();
            throw new Exception("Ne postoji nijedan indeks u bazi podataka");
        }

        indeks = rez.getInt(1);
        rez.close();

        int brojObrisanih = stmt.executeUpdate(
                "DELETE  FROM ISPIT " + 
                "WHERE   INDEKS = (SELECT MIN(INDEKS) FROM DOSIJE)");
        System.out.println("Broj obrisanih redova: " + brojObrisanih);

        stmt.close();
        return indeks;
    }

    private static void uvecajBodoveZaPredmeteVstud(Connection con, int indeks)
            throws SQLException, IOException {
        String sql = ucitajSqlIzDatoteke("uvecajBodoveZaPredmeteVstud.sql");
        PreparedStatement stmt = con.prepareStatement(sql);
        stmt.setInt(1, indeks);

        int brojAzuriranih = stmt.executeUpdate();
        System.out.println("Broj azuriranih redova: " + brojAzuriranih);

        stmt.close();
    }

    private static String ucitajSqlIzDatoteke(String nazivDatoteke) 
            throws IOException {
        StringBuilder sql = new StringBuilder();
        Files.lines(Paths.get(System.getProperty("user.dir") + "/bin/zadatak_9_5/" + nazivDatoteke))
            .forEach(linija -> sql.append(linija).append("\n"));
        return sql.toString();
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_5/izlistajStudenteMstud.sql:

SELECT  INDEKS,
        IME,
        PREZIME  
FROM    DOSIJE D  
WHERE   NOT EXISTS (  
            SELECT  *  
            FROM    PREDMET P  
            WHERE   BODOVI = ? AND 
                    NOT EXISTS (  
                        SELECT  *  
                        FROM    ISPIT I  
                        WHERE   I.INDEKS = D.INDEKS AND 
                                I.ID_PREDMETA = P.ID_PREDMETA AND 
                                I.OCENA > 5  
                    )  
        )

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_5/izlistajPolaganjaVstud.sql:

SELECT  INDEKS, 
        P.NAZIV, 
        OCENA, 
        GODINA_ROKA, 
        OZNAKA_ROKA  
FROM    ISPIT I JOIN 
        PREDMET P ON I.ID_PREDMETA = P.ID_PREDMETA  
WHERE   OCENA > 5 AND 
        STATUS_PRIJAVE = 'O' AND 
        NOT EXISTS (  
            SELECT  *  
            FROM    ISPIT I2  
            WHERE   I.INDEKS = I2.INDEKS AND  
                    OCENA < ?  
        )

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_5/uvecajBodoveZaPredmeteVstud.sql:

UPDATE  PREDMET P
SET     BODOVI = BODOVI + 1 
WHERE   BODOVI < 10 AND 
        EXISTS (
            SELECT  *
            FROM    ISPIT I 
            WHERE   I.ID_PREDMETA = P.ID_PREDMETA AND 
                    OCENA > 5
                    AND INDEKS = ?
        )

9.4 Objektno-orijentisani pristup kreiranju JDBC aplikacija

Do sada smo naše JDBC aplikacije pisali u proceduralnom stilu - metod main() nam je služio kao alat koji je izvršavao sve potrebne aktivnosti, kao što su: povezivanje na bazu podataka, kreiranje objekata naredbi, izvršavanje naredbi, procesiranje podataka, oslobađanje resursa i diskonekcija sa baze podataka.

Ovaj pristup nije problematičan prilikom kreiranja jednostavnih aplikacija kao što su to bile aplikacije koje smo videli do sada. Međutim, što se složenost aplikacije povećava, ovakav pristup postaje izuzetno naporan za održavanje - što je i jedna od najvećih mana proceduralne paradigme programiranja. Neke od rešenja ovih problema ćemo prikazati kroz naredni zadatak.

Zadatak 9.6: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji izdvaja studente po studijskim programima. Za svaki studijski program ispisati naziv obim u ESPB i zvanje, a zatim spisak studenata (indeks, ime i prezime) koji su upisali taj program. Zadatak uraditi korišćenjem objektno-orijentisanog pristupa dizajnu.

Rešenje: Jednan od osnovnih koncepata objektno-orijentisane paradigme je da jedna klasa treba da implementira jednu jezgrovitu funkcionalnost.

Za početak, možemo napisati po jednu klasu za svaku tabelu koja će se koristiti. U ovom slučaju to su tabele STUDIJSKIPROGRAM i DOSIJE, a odgovarajuće klase nazvaćemo StudijskiProgram i Student. Svaka klasa treba da sadrži polja koja odgovaraju nekoj od kolona tabele. Jedan objekat klase predstavlja jedan slog odgovarajuće tabele.

Potrebna nam je i klasa, na primer Database, koja implementira osnovne operacije za rad sa bazom. Zatim, ove funkcionalnosti možemo iskoristiti u implementaciji za rad sa bazom STUD2020 tako što ćemo napisati posebnu klasu za ovu bazu, na primer Stud2020, koja će naslediti klasu Database. U ovoj klasi implementiramo zahteve kao metode klase. Time dobijamo naredne klase:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/Database.java:

package zadatak_9_6;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;

/**
 * Database is the abstract base class for all database contexts which allow an
 * application to perform basic operations on a database, using the JDBC API.
 * 
 * In order to use this class, for each database, there needs to exists an
 * inherited class from this class. The inherited class needs to set the
 * following protected members in its constructor method: <code>dbName</code>,
 * <code>url</code>, <code>username</code>, <code>password</code>.
 */
public abstract class Database implements AutoCloseable {
    /**
     * The object which is used for keeping the resources used in a connection
     * to a database.
     */
    protected Connection con;

    /**
     * The name of the database used for printing purposes.
     */
    protected String dbName = null;
    /**
     * The Type 4 JDBC URL used for connecting to a database.
     */
    protected String url = null;
    /**
     * The user which is being used for connecting to a database and executing
     * the statements.
     */
    protected String username = null;
    /**
     * The password for the specified user.
     */
    protected String password = null;

    /**
     * The helper function for getting the name of a database.
     * 
     * @return the name of a database
     */
    protected String getName() {
        return dbName;
    }

    /**
     * The helper function for getting the URL of a database.
     * 
     * @return the URL of a database
     */
    protected String getUrl() {
        return url;
    }

    /**
     * The helper function for getting the username of a database.
     * 
     * @return the username of a database
     */
    protected String getUsername() {
        return username;
    }

    /**
     * The helper function for getting the password of a database user.
     * 
     * @return the password of a database user
     */
    protected String getPassword() {
        return password;
    }

    /**
     * Connects to a database using the information stored in the following
     * protected members: <code>url</code>, <code>username</code>,
     * <code>password</code>. These members must be set in the constructor
     * method of inherited classes.
     * 
     * @throws SQLException
     *             If an SQL error occured while connecting to a database
     */
    public void connect() throws SQLException {
        if (null == con) {
            System.out.println("Povezivanje na " + getName() + "...");
            con = DriverManager.getConnection(getUrl(), getUsername(), getPassword());
            con.setAutoCommit(false);
            System.out.println("Uspesno je ostvarena konekcija!");
        }
    }

    /**
     * Disconnects from a previously connected database. Does nothing if the
     * connection does not exists.
     * 
     * @param successful
     *            whether to commit or rollback potential changes made to the
     *            database
     * @throws SQLException
     *             If an SQL error occured while disconnecting from a database
     */
    public void disconnect(boolean successful) throws SQLException {
        if (null != con) {
            if (successful) {
                commit();
            } else {
                rollback();
            }

            con.close();
            con = null;

            System.out.println("Diskonektovano sa " + getName() + "!");
        }
    }

    /**
     * Commits the potential changes made to the database. Ignores any SQL
     * errors that might occur.
     */
    public void commit() {
        try {
            if (null != con) {
                con.commit();
            }

            System.out.println("Sve izmene su pohranjene u bazi podataka " + getName());
        } catch (SQLException e) {
        }
    }

    /**
     * Rolls back the potential changes made to the database. Ignores any SQL
     * errors that might occur.
     */
    public void rollback() {
        try {
            if (null != con) {
                con.rollback();
            }
            System.out.println("Sve izmene su ponistene u bazi podataka " + getName());
        } catch (SQLException e) {
        }
    }

    /**
     * Reads the SQL statement from the file located at the given pathname
     * <code>filename</code>.
     * 
     * @param filename
     * @return
     * @throws IOException
     */
    protected static String readSQLFromFile(String filename) throws IOException {
        StringBuilder sql = new StringBuilder();
        Files.lines(Paths.get(filename)).forEach(linija -> sql.append(linija).append("\n"));
        return sql.toString();
    }

    /**
     * Closes the connection.
     */
    @Override
    public void close() throws Exception {
        if (null != con) {
            con.close();
        }
    }

}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/Stud2020.java:

package zadatak_9_6;

import java.io.IOException;
import java.sql.*;

public class Stud2020 extends Database {

    public Stud2020() throws SQLException {
        dbName = "STUD2020";
        url = "jdbc:db2://localhost:50000/stud2020";
        username = "student";
        password = "abcdef";
        connect();
    }

    public void izlistajStudentePoStudijskimProgramima() throws SQLException, IOException {
        String sql = readSQLFromFile(System.getProperty("user.dir") + "/bin/zadatak_9_6/studijskiProgrami.sql");
        Statement stmt = con.createStatement();
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            StudijskiProgram sp = new StudijskiProgram(rs.getInt(1), rs.getString(2), rs.getShort(3), rs.getString(4));
            
            System.out.println("\n" + sp);
            
            izlistajStudenteStudijskogPrograma(sp.getId());
        }

        rs.close();
        stmt.close();
    }

    public void izlistajStudenteStudijskogPrograma(int idPrograma) throws SQLException, IOException {
        String sql = readSQLFromFile(System.getProperty("user.dir") + "/bin/zadatak_9_6/studentiStudijskogPrograma.sql");
        PreparedStatement stmt = con.prepareStatement(sql);
        stmt.setInt(1, idPrograma);
        ResultSet rs = stmt.executeQuery();
        
        while(rs.next()) {
            Student student = new Student(rs.getInt(1), rs.getString(2), rs.getString(3));
            System.out.println("\t" + student);
        }

        rs.close();
        stmt.close();
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/StudijskiProgram.java:

package zadatak_9_6;

public class StudijskiProgram {
    private int id;
    private String naziv;
    private short obimespb;
    private  String zvanje;
    
    public StudijskiProgram(int id, String naziv, short obimespb, String zvanje) {
        super();
        this.id = id;
        this.naziv = naziv;
        this.obimespb = obimespb;
        this.zvanje = zvanje;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNaziv() {
        return naziv;
    }

    public void setNaziv(String naziv) {
        this.naziv = naziv;
    }

    public short getObimespb() {
        return obimespb;
    }

    public void setObimespb(short obimespb) {
        this.obimespb = obimespb;
    }

    public String getZvanje() {
        return zvanje;
    }

    public void setZvanje(String zvanje) {
        this.zvanje = zvanje;
    }
    
    @Override
    public String toString() {
        return "Naziv: " + naziv + ", obim: " + obimespb + ", zvanje: " + zvanje;
    }
    
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/Student.java:

package zadatak_9_6;

public class Student {
    private int indeks;
    private String ime;
    private String prezime;
    
    public Student(int indeks, String ime, String prezime) {
        super();
        this.indeks = indeks;
        this.ime = ime;
        this.prezime = prezime;
    }

    public int getIndeks() {
        return indeks;
    }

    public void setIndeks(int indeks) {
        this.indeks = indeks;
    }

    public String getIme() {
        return ime;
    }

    public void setIme(String ime) {
        this.ime = ime;
    }

    public String getPrezime() {
        return prezime;
    }

    public void setPrezime(String prezime) {
        this.prezime = prezime;
    }
    
    @Override
    public String toString() {
        return indeks + " " + ime + " " + prezime;
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/Main.java:

package zadatak_9_6;

import java.sql.SQLException;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public static void main(String[] args) {
        
        try (Stud2020 stud2020 = new Stud2020()) {
            stud2020.izlistajStudentePoStudijskimProgramima();
            stud2020.commit();
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }


    }

}

Naredne SQL datoteke sadrže odgovarajuće naredbe:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/studijskiProgrami.sql:

SELECT  ID,
        NAZIV,
        OBIMESPB,
        ZVANJE
FROM    DA.STUDIJSKIPROGRAM;
       

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_6/studentiStudijskogPrograma.sql:

SELECT  INDEKS,
        IME,
        PREZIME
FROM    DA.DOSIJE
WHERE   IDPROGRAMA = ?

9.5 Zadaci za vežbu

Zadatak 9.7: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji ima mogućnost da upravlja podacima o statistikama upisanih kurseva. Kreirati klasu Main.java. U toj klasi implementirati naredne metode nad datim bazama podataka. Nije dozvoljeno menjati potpise metoda; jedino je moguće dodavati izuzetke koje oni ispaljuju. Nije dozvoljeno implementirati opisane operacije van tela metoda (ali je moguće koristiti pomoćne metode):

(a) Napisati metod private static ArrayList<Predmet> aPronadjiSvePredmete(Connection con, String upitZaNaziv) throws SQLException koji pronalazi identifikatore i nazive svih predmeta čiji naziv predmeta počinje niskom upitZaNaziv. Implementirati klasu Predmet koja ima dva polja koja predstavljaju identifikator i naziv predmeta. Metod vraća listu instanci ove klase.

(b) Napisati metod private static ArrayList<Predmet> bIzdvojiPredmeteKojiIspunjavajuUslov(Connection con, ArrayList<Predmet> predmeti) throws SQLException koji od date liste predmeta predmeti izdvaja samo one predmete koji zadovoljavaju naredna dva uslova:

  • Predmet mora da ima studente koji su ga upisali.
  • Predmet mora da se ne nalazi u tabeli STATISTIKAUPISANIHKURSEVA. Dopustiti da aplikacija može da vidi nepotvrđene izmene drugih aplikacija prilikom provere datih uslova.

(c) Napisati metod private static void cObradiPredmete(Connection con, ArrayList<Predmet> predmeti) throws SQLException koji izdvaja naredne informacije: (1) identifikator predmeta, (2) školsku godinu, (3) broj studenata koji su upisali taj predmet u toj godini, (4) broj polaganja tog predmeta u toj godini, ali samo za one predmete koji zadovoljavaju uslove iz metoda pod (b). Ove informacije je potrebno ispisati na standardni izlaz, a zatim uneti u tabelu STATISTIKAUPISANIHKURSEVA metodom pod (d). Samo u ovom metodu proveravati greške koje se javljaju prilikom izvršavanja aplikacije u višekorisničkom okruženju. Postaviti istek vremena na 5 sekundi. Obrada jednog predmeta (ispis + unos) mora da predstavlja jednu transakciju. Omogućiti da nijedna druga aplikacija ne sme čitati ili menjati podatke tokom obrade predmeta u ovom metodu.

(d) Napisati metod private static void dUnesiNovuStatistiku(Connection con, int idPredmeta, short godina, Integer brojStudenata, Integer brojPolaganja) throws SQLException koji unosi novi slog u tabelu STATISTIKAUPISANIHKURSEVA na osnovu argumenata koji mu se prosleđuju. Za kolonu PONISTENI postaviti vrednost 0.

(e) Napisati metod private static void ePonistiStatistike(Connection con, short godina, Scanner ulaz) throws SQLException koji ponistava sve statistike (tj. postavlja kolonu PONISTENI na vrednost 1) iz tabele STATISTIKAUPISANIHKURSEVA za one statistike iz godine koja se prosleđuje kao argument metoda. Poništavanje svih statistika za datu godinu predstavlja jednu transakciju. Međutim, potrebno je omogućiti da se nakon izmene jednog sloga korisnik pita da potvrdi izmene. Ukoliko ipak želi da odustane od izmene tekuće statistike, omogućiti poništavanje samo poslednje izmenjene statistike.

(f) Napisati metod private static void fObrisiStatistike(Connection con) throws SQLException koji briše sve poništene statistike iz tabele STATISTIKAUPISANIHKURSEVA. Za svaki slog je neophodno ispisati na standardni izlaz identifikator predmeta za slog koji se briše, a zatim se izvršava brisanje tog sloga. Brisanje svih poništenih statistika predstavlja jednu transakciju.

(g) Napisati metod private static void gPrikaziStatistike(Connection con) throws SQLException koji ispisuje informacije iz tabele STATISTIKAUPISANIHKURSEVA. Sortirati ispis po identifikatoru predmeta rastuće.

Aplikacija omogućava korisniku da odabere jednu od narednih 5 opcija. Svaki put kada se opcija završi (osim u slučaju opcije 5), aplikacija ponovo zahteva od korisnika da unese jednu od narednih opcija:

  1. unos: Aplikacija zahteva od korisnika da unese upit za naziv predmeta. Nakon unosa, aplikacija metodom pod (a) pronalazi sve kandidate. Zatim ispisuje sve predmete metodom pod (b). Nakon toga, vrši se obrada metodom pod (c).
  2. ponistavanje: Aplikacija zahteva od korisnika da unese godinu studija. Zatim se metodom pod (e) vrši poništavanje statistika.
  3. brisanje: Aplikacija izvršava metod pod (f).
  4. prikazivanje: Aplikacija izvršava metod pod (g).
  5. dalje: Aplikacija zahteva od korisnika da unese ocenu i poziva metod pod (h). Nakon toga, aplikacija se završava.

Rešenje: Pre pokretanja programa, potrebno je pripremiti bazu STUD2020 izvršavanjem narednog skripta nad tom BP:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_7/pripremaBaze.sql:

DROP TABLE DA.STATISTIKAUPISANIHKURSEVA;

CREATE TABLE DA.STATISTIKAUPISANIHKURSEVA (
    IDPREDMETA INTEGER NOT NULL,
    SKGODINA SMALLINT NOT NULL,
    BROJSTUDENATA INTEGER,
    BROJPOLAGANJA INTEGER,
    PONISTENI SMALLINT NOT NULL,
    PRIMARY KEY (IDPREDMETA, SKGODINA),
    CONSTRAINT FK_PREDMET FOREIGN KEY (IDPREDMETA)
        REFERENCES DA.PREDMET (ID)
        ON DELETE CASCADE
);

Implementacija rešenja se nalazi u narednim datotekama:

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_7/Main.java:

package zadatak_9_7;

import java.sql.*;
import java.util.*;
import java.nio.file.*;
import java.io.*;

public class Main {
    static {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
    
    private static class Predmet {
        public int idPredmeta;
        public String naziv;
        
        public Predmet(int idPredmeta, String naziv) {
            this.idPredmeta = idPredmeta;
            this.naziv = naziv;
        }
    }

    public static void main(String argv[]) {
        String url = "jdbc:db2://localhost:50000/stud2020";

        try (
            Connection con = DriverManager.getConnection(url, "student", "abcdef");
        ) {
            con.setAutoCommit(false);

            try (Scanner ulaz = new Scanner(System.in)) {
                while(true) {
                    System.out.println("Odaberite jednu od narednih opcija: ");
                    System.out.println("  1. unos");
                    System.out.println("  2. ponistavanje");
                    System.out.println("  3. brisanje");
                    System.out.println("  4. prikazivanje");
                    System.out.println("  5. dalje");
                    System.out.println("Vas unos: ");
                    String odgovor = ulaz.nextLine();
                    System.out.println("----------------------------------------");
                    
                    if (odgovor.equalsIgnoreCase("unos")) {
                        System.out.println("Unesite naziv predmeta: ");
                        String naziv = ulaz.nextLine();
                        
                        ArrayList<Predmet> predmeti = aPronadjiSvePredmete(con, naziv);
                        System.out.println("Pronadjeni predmeti su: ");
                        ArrayList<Predmet> odabraniPredmeti = bIzdvojiPredmeteKojiIspunjavajuUslov(con, predmeti);
                        cObradiPredmete(con, odabraniPredmeti);
                    }
                    else if (odgovor.equalsIgnoreCase("ponistavanje")) {
                        System.out.println("Unesite godinu studija: ");
                        short godina = ulaz.nextShort();
                        ulaz.nextLine(); // '\n'
                        ePonistiStatistike(con, godina, ulaz);
                    }
                    else if (odgovor.equalsIgnoreCase("brisanje")) {                        
                        fObrisiStatistike(con);
                    }
                    else if (odgovor.equalsIgnoreCase("prikazivanje")) {                        
                        gPrikaziStatistike(con);
                    }
                    else if (odgovor.equalsIgnoreCase("dalje")) {
                        break;
                    } else {
                        throw new Exception("Odabrali ste nepostojecu opciju: " + odgovor);
                    }
                }
                
                con.commit();
            } catch (Exception e) {
                con.rollback();
                throw e;
            } 
        } 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.out.println("Doslo je do neke greske: " + e.getMessage());

            System.exit(2);
        }
    }

    private static ArrayList<Predmet> aPronadjiSvePredmete(Connection con, String upitZaNaziv) throws SQLException {
        ArrayList<Predmet> predmeti = new ArrayList<>();
        String sql = "SELECT ID, TRIM(NAZIV) FROM DA.PREDMET WHERE NAZIV LIKE ?";
        
        PreparedStatement pStmt = con.prepareStatement(sql);
        upitZaNaziv += "%";
        pStmt.setString(1, upitZaNaziv);
        ResultSet kursor = pStmt.executeQuery();
        
        while (kursor.next()) {
            int idPredmeta = kursor.getInt(1);
            String naziv = kursor.getString(2);
            predmeti.add(new Predmet(idPredmeta, naziv));
        }
        
        kursor.close();
        pStmt.close();
        
        return predmeti;
    }

    private static ArrayList<Predmet> bIzdvojiPredmeteKojiIspunjavajuUslov(Connection con,
            ArrayList<Predmet> predmeti) throws SQLException, IOException {
        ArrayList<Predmet> izdvojeniPredmeti = new ArrayList<>();
        
        String sql = ucitajSqlIzDatoteke("izdvajanjePredmetaSaUslovom.sql");
        PreparedStatement pStmt = con.prepareStatement(sql);
        
        for (Predmet predmet : predmeti) {
            pStmt.setInt(1, predmet.idPredmeta);
            ResultSet kursor = pStmt.executeQuery();
            
            boolean imaRedova = kursor.next();
            if (!imaRedova || kursor.getInt(1) == 0) {
                System.out.println("  Za predmet " + predmet.naziv + " sa identifikatorom " + predmet.idPredmeta + " nema studenata koji su ga upisali");
                continue;
            }
            
            int brojUpisanih = kursor.getInt(1);
            System.out.println("  Predmet " + predmet.naziv + " sa identifikatorom " + predmet.idPredmeta + " je upisalo " + brojUpisanih + " student/studenata");
            izdvojeniPredmeti.add(predmet);
            
            kursor.close();
        }
        
        pStmt.close();
        
        return izdvojeniPredmeti;
    }

    private static void cObradiPredmete(Connection con, ArrayList<Predmet> predmeti) throws SQLException, IOException {
        String sql = ucitajSqlIzDatoteke("statistika.sql");
        PreparedStatement pStmt = con.prepareStatement(sql);
        
        Statement stmt = con.createStatement();
        stmt.execute("SET CURRENT LOCK TIMEOUT 5");
        
        for (Predmet predmet : predmeti) {
            pStmt.setInt(1, predmet.idPredmeta);
            ResultSet kursor = pStmt.executeQuery();
            
            while (true) {
                try {
                    // BEGIN visekorisnicko
                    boolean imaRedova = kursor.next();
                    if (!imaRedova) {
                        break;
                    }
                    
                    int idPredmeta = kursor.getInt(1);
                    short godina = kursor.getShort(2); 
                    int brojStudenata = kursor.getInt(3);
                    boolean brojStNull = kursor.wasNull();
                    int brojPolaganja = kursor.getInt(4);
                    boolean brojPolNull = kursor.wasNull();
                    System.out.println("  Unosim informacije: " + idPredmeta + ", " + godina + ", " + brojStudenata + ", " + brojPolaganja);
                    
                    dUnesiNovuStatistiku(con, idPredmeta, godina, brojStNull ? null : brojStudenata, brojPolNull ? null : brojPolaganja);
                    
                    con.commit();
                    // END visekorisnicko
                } catch (SQLException e) {
                    if (-913 <= e.getErrorCode() && e.getErrorCode() <= -911) {
                        kursor.close();
                        kursor = obradiCekanje(con, stmt, sql);
                        continue;
                    }
                    // Vrati podrazumevanu vrednost za istek vremena, oslobodi resurse 
                    // i prosledi izuzetak main() metodi za obradu
                    stmt.execute("SET CURRENT LOCK TIMEOUT NULL");
                    
                    kursor.close();
                    stmt.close();
                    pStmt.close();
                    
                    throw e;
                }
            }
            
            kursor.close();
        }
        
        stmt.execute("SET CURRENT LOCK TIMEOUT NULL");
        
        stmt.close();
        pStmt.close();
    }
    
    private static void dUnesiNovuStatistiku(Connection con, int idPredmeta, short godina, Integer brojStudenata, Integer brojPolaganja) throws SQLException {
        String sql = "INSERT INTO DA.STATISTIKAUPISANIHKURSEVA VALUES (?, ?, ?, ?, 0)";
        PreparedStatement pStmt = con.prepareStatement(sql);
        
        pStmt.setInt(1, idPredmeta);
        pStmt.setShort(2, godina);
        if (null == brojStudenata) {
            pStmt.setNull(3, java.sql.Types.INTEGER);
        } else {
            pStmt.setInt(3, brojStudenata);
        }
        if (null == brojPolaganja) {
            pStmt.setNull(4, java.sql.Types.INTEGER);
        } else {
            pStmt.setInt(4, brojPolaganja);
        }
        
        pStmt.executeUpdate();
        pStmt.close();
    }
    
    private static void ePonistiStatistike(Connection con, short godina, Scanner ulaz) throws Exception {
        String sql = "SELECT * FROM DA.STATISTIKAUPISANIHKURSEVA WHERE SKGODINA = ?";
        PreparedStatement pStmt = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        pStmt.setShort(1, godina);
        ResultSet kursor = pStmt.executeQuery();
        
        while (kursor.next()) {
            Savepoint s = con.setSavepoint();
            
            int idPredmeta = kursor.getInt(1);
            kursor.updateShort(5, (short)1);
            kursor.updateRow();
            
            System.out.println("  Da li ste sigurni da zelite da ponistite podatke o predmetu sa identifikatorom " + idPredmeta + " u odabranoj godini " + godina + "? [da/ne]");
            String odgovor = ulaz.nextLine();
            if (odgovor.equalsIgnoreCase("ne")) {
                con.rollback(s);
            } else if (odgovor.equalsIgnoreCase("da")) {
                con.releaseSavepoint(s);
            } else {
                throw new Exception("Uneli ste neispravan odgovor: " + odgovor);
            }
        }
        
        kursor.close();
        pStmt.close();
        
        con.commit();
    }

    private static void fObrisiStatistike(Connection con) throws SQLException {
        String sql = "SELECT * FROM DA.STATISTIKAUPISANIHKURSEVA WHERE PONISTENI = 0";
        Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
        ResultSet kursor = stmt.executeQuery(sql);
        
        while (kursor.next()) {
            int idPredmeta = kursor.getInt(1);
            System.out.println("  Brisem podatke o predmetu sa identifikatorom " + idPredmeta + " u odabranoj godini");
            kursor.deleteRow();
        }
        
        kursor.close();
        stmt.close();
        
        con.commit();
    }
    
    private static void gPrikaziStatistike(Connection con) throws SQLException {
        String sql = "SELECT * FROM DA.STATISTIKAUPISANIHKURSEVA ORDER BY IDPREDMETA";
        Statement stmt = con.createStatement();
        ResultSet kursor = stmt.executeQuery(sql);
        
        System.out.println("+--------------------------------------------------------------------+");
        System.out.println("| ID PREDMETA | GODINA | BROJ STUDENATA | BROJ POLAGANJA | PONISTENO |");
        System.out.println("|-------------+--------+----------------+----------------+-----------|");
        
        while (kursor.next()) {
            String idPredmeta = Integer.toString(kursor.getInt(1));
            String godina = Short.toString(kursor.getShort(2)); 
            String brojStudenata = Integer.toString(kursor.getInt(3));
            if(kursor.wasNull()) {
                brojStudenata = "NULL          ";
            }
            String brojPolaganja = Integer.toString(kursor.getInt(4));
            if (kursor.wasNull()) {
                brojPolaganja = "NULL          ";
            }
            String ponisteni = "FALSE    ";
            if (kursor.getShort(5) == 1) {
                ponisteni = "TRUE     ";
            }
            System.out.printf("| %s%s | %s   | %s%s | %s%s | %s |\n",
                    idPredmeta, new String(new char[11 - idPredmeta.length()]).replace("\0", " "), 
                    godina, brojStudenata, new String(new char[14 - brojStudenata.length()]).replace("\0", " "),
                    brojPolaganja, new String(new char[14 - brojPolaganja.length()]).replace("\0", " "),
                    ponisteni);
        }
        
        System.out.println("|-------------+--------+----------------+----------------+-----------|");
        System.out.println("+--------------------------------------------------------------------+");
        
        kursor.close();
        stmt.close();
    }
    
    private static String ucitajSqlIzDatoteke(String nazivDatoteke) throws IOException {
        StringBuilder sql = new StringBuilder();
        Files.lines(Paths.get(System.getProperty("user.dir") + "/bin/zadatak_9_7/" + nazivDatoteke))
            .forEach(linija -> sql.append(linija).append("\n"));
        return sql.toString();
    }
    
    private static ResultSet obradiCekanje(Connection con, Statement stmt, String sql) throws SQLException {
        System.out.println("Objekat je zakljucan od strane druge transakcije! Molimo sacekajte...");
        
        try {
            con.rollback();
        } catch (SQLException e) {}
        
        return stmt.executeQuery(sql);
    }
}

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_7/izdvajanjePredmetaSaUslovom.sql:

SELECT  COUNT(*)
FROM    DA.UPISANKURS UK JOIN
        DA.PREDMET P ON P.ID = UK.IDPREDMETA 
WHERE   P.ID = ? AND
        P.ID NOT IN (SELECT IDPREDMETA FROM DA.STATISTIKAUPISANIHKURSEVA) 
WITH    UR

Datoteka: vezbe/primeri/poglavlje_9/src/zadatak_9_7/statistika.sql:

WITH BROJPOLAGANJAPREDMETA AS (
    SELECT      IDPREDMETA,
                SKGODINA,
                COUNT(*) BROJPOLAGANJA
    FROM        DA.ISPIT
    GROUP BY    IDPREDMETA,
                SKGODINA
)
SELECT      UK.IDPREDMETA,
            UK.SKGODINA,
            COUNT(*) BROJUPISANIH,
            (
                SELECT  BROJPOLAGANJA 
                FROM    BROJPOLAGANJAPREDMETA BPP 
                WHERE   UK.IDPREDMETA = BPP.IDPREDMETA AND
                        UK.SKGODINA = BPP.SKGODINA
            ) BROJPOLAGANJA 
FROM        DA.UPISANKURS UK
WHERE       UK.IDPREDMETA = ?
GROUP BY    UK.IDPREDMETA,
            UK.SKGODINA
WITH        RR

Zadatak 9.8: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji za sve ispitne rokove pronalazi položene predmet u tom ispitnom roku Za svaki predmet program pronalazi koliko je kojih ocena postignuto i te podatke unosi u tabelu ISPITNIROKOVIPOLAGANJA. Kreirati datu tabelu na osnovu SQL koda ispod.

Pre jednog unosa podataka ispisati podatke koji ce biti uneti. Takođe, omogućiti da se podaci unose tako što korisnik mora da odobri unos podataka na svakih 20 redova (tzv. batch unos podataka). Napisati program tako da može da radi u višekorisničkom okruženju. Unos podataka za jedno polaganje predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Obraditi sve moguće greške.

SQL naredbe za kreiranje i brisanje tabele sačuvati u datotekama 2a.sql i 2b.sql, redom, a SELECT naredbu kojim se izdvajaju potrebni podaci sačuvati u datoteci 2c.sql.

CREATE TABLE DA.ISPITNIROKOVIPOLAGANJA (
    GODINA SMALLINT NOT NULL,
    OZNAKA VARCHAR(20) NOT NULL,
    IDPREDMETA INTEGER NOT NULL,
    OCENA SMALLINT NOT NULL,
    BROJ INTEGER NOT NULL,
    NAZIVROKA VARCHAR(50),
    NAZIVPREDMETA VARCHAR(200),
    PRIMARY KEY(GODINA, OZNAKA, IDPREDMETA, OCENA),
    CONSTRAINT FK_PREDMET FOREIGN KEY (IDPREDMETA)
        REFERENCES DA.PREDMET (ID)
        ON DELETE CASCADE,
    CONSTRAINT FK_IR FOREIGN KEY (GODINA, OZNAKA)
        REFERENCES DA.ISPITNIROK (SKGODINA, OZNAKAROKA)
        ON DELETE CASCADE
)

Pomoć pri rešavanju zadatka: Kreirati tabelu OBRADJENAPOLAGANJA na osnovu SQL koda ispod koja će sadržati informacije o već obrađenim polaganjima iz tabele ISPITNIROKOVIPOLAGANJA. Nakon svake obrade jednog polaganja, uneti novi red u ovu tabelu i potvrditi izmene.

CREATE TABLE OBRADJENAPOLAGANJA (
    GODINA SMALLINT NOT NULL,
    OZNAKA VARCHAR(20) NOT NULL,
    IDPREDMETA INTEGER NOT NULL,
    OCENA SMALLINT NOT NULL,
    PRIMARY KEY(GODINA, OZNAKA, IDPREDMETA, OCENA),
    CONSTRAINT FK_POLAGANJA FOREIGN KEY(GODINA, OZNAKA, IDPREDMETA, OCENA) 
        REFERENCES DA.ISPITNIROKOVIPOLAGANJA
        ON DELETE CASCADE
)

Zadatak 9.9: Napisati Java program u kojem se SQL naredbe izvršavaju dinamički koji redom:

  1. Naredbama INSERT unosi podatke o nekoliko stipendija u tabelu STIPENDIJA. Izračunati i broj unetih redova. U slučaju da je broj unetih redova jednak nuli, ispisati poruku ”Nijedan red nije dodat”, a inače ispisati poruku u unetom broju redova. Kreirati datu tabelu na osnovu SQL koda ispod.
  2. Za svaku stipendiju, pita korisnika da li želi da promeni broj studenata za tu stipendiju i ukoliko je odgovor korisnika potvrdan, od korisnika traži da unese novi broj studenata i izvršava odgovarajuću naredbu.
  3. Za svaku stipendiju, pita korisnika da li želi da obriše tu stipendiju i ukoliko je odgovor korisnika potvrdan, izvršava odgovarajuću naredbu.

Aplikacija treba da radi u višekorisničkom okruženju. Obrada jedne stipendije u svim zahtevima treba da predstavlja jednu transakciju. Postaviti istek vremena na 5 sekundi.

CREATE TABLE STIPENDIJA (
    ID INTEGER NOT NULL,
    NAZIV VARCHAR(100) NOT NULL,
    GODINA SMALLINT NOT NULL,
    BROJSTIPENDISTA SMALLINT NOT NULL,
    VISINASTIPENDIJE SMALLINT,
    MINPROSEK FLOAT,
    NAPOMENA VARCHAR(50),
    PRIMARY KEY (ID)
)