Tietokantaohjelmointi ja Java-tietokantaliittymä

Sisältö:
Tietokannan suorakäyttö
Tietokannan käyttö ohjelmissa
  Liittymä
  Varautuminen virheisiin
  Vastausjoukon käsittely
Yleiskielen SQL-laajennoksen periaate
JDBC - Java ohjelmointirajapinta
Oheismateriaalia
Verkossa: Oppikirjoja:
  • R. Elmasri and S. B. Navathe, Fundamentals of Database Systems, 4th ed., Luvut 9.3.-9.5. (sivut 261-275)
  • R. Ramakrishnan and J. Gehrke, Database Management Systewms, 3rd ed., Luvut 6.1.-6.3. (sivut 185-205)

Tietokannan suorakäyttö

Tietokannan hallintajärjestelmät tarjoavat yleensä komentotulkin, jonka avulla käyttäjät voivat suorittaa SQL-operaatioita. Komentotulkin avulla voi suorittaa joko interaktiivisesti näppäimistöltä annetun komennon tai tiedostoon talletetun komentosarjan. Operaatioiden tulokset voidaan ottaa näytölle tai ohjata tiedostoon. Joissakin järjestelmissä komentosarjat voi parametroida siten, että sarjoja voi suorittaa eri kerroilla eri parametriarvoin. SQL:n tarjoamat mahdollisuudet tiedon muotoiluun, muokkaukseen ja esittämiseen ovat kuitenkin varsin suppeat.

Tietokannan käyttö ohjelmissa

Tyypillisesti loppukäyttäjät käyttävät tietokantaa sovellusohjelmien välityksellä. Sovellusohjelmat laaditaan jollakin ohjelmointikielellä (Java, C, Cobol, Pascal, Perl, jne). Tietokanta on sovellusohjelmista erillinen kokonaisuus, jonka käsittelystä vastaa tietokannan hallintajärjestelmä. Tietokanta voi sijaita eri laitteistoympäristössä kuin missä sovellusohjelmia ajetaan. Kun siis tietokantaa käytetään sovellusohjelmasta, käytetään hyväksi erillisen ulkopuolisen järjestelmän tarjoamia palveluita. Sovellusohjelmat liitetään tietokannan hallintajärjestelmään liittymäohjelmiston välityksellä.

Liittymä

Liittymäohjelmiston tehtävänä on huolehtia sovellusohjelman ja tietokannan hallintajärjestelmän välisestä tietojen välityksestä. Tähän sisältyy operaatiopyyntöjen välitys sovellusohjelmalta tietokannan hallintajärjestelmälle ja vastakkaiseen suuntaan kyselyjen tulosten välitys sovellusohjelmalle. Liittymäohjelmisto jakautuu asiakas- ja palvelinosiin. Jos sovellusohjelmia ei ajeta tietokantapalvelimessa, käyttää liittymäohjelmisto apunaan jotain tietoliikenneohjelmistoa tietojen välityksessä.

Sovellusohjelman ja tietokannan hallintajärjestelmän välisen liittymän toteutukseen on tarjolla useita tekniikoita (ohjelmointikielestä riippuen):

Varautuminen virheisiin

Tietokannan hallintajärjestelmä on sovellusohjelmasta erillinen ohjelmisto. Sille annettuun palvelupyyntöön saattaa liittyä pyynnön tulkinta, kyselyn kääntäminen ja optimointi, sekä tietoliikennettä. Jokaisen operaatiopyynnön yhteydessä voi tapahtua virheitä, esimerkiksi tietokantapalvelin ei ole toiminnassa, annettu kysely on syntaktisesti väärin, kysely ei löydä mitään tietoa, ylläpito-operaatio rikkoo tietokannan eheysehtoja tai tietoliikenneyhteys katkeaa. Niinpä tietokantaa käyttävää sovellusohjelmaa kirjoitettaessa on jokaisen tietokantaoperaation yhteydessä varauduttava siihen, että operaation yhteydessä tapahtuu jokin virhetilanne. Tätä varten kaikki tietokantaoperaatiot välittävät niitä käyttävälle ohjelmalle palautetietoa toiminnastaan. Tämä palautetieto saadaan eri ohjelmointiympäristöissä käyttöön hieman eri tavoin. Javan JDBC-ympäristössä virhetilanteista ilmoitetaan poikkeuksin. Yleiskielen SQL-laajennusta käytettäessä käytössä ovat erityiset valmiiksi määritellyt tietorakenteet palautetiedon välittämiseen (SQLCODE, SQLSTATE). Palautetietoja on tutkittava jokaisen tietokantaoperaation jälkeen.

Vastausjoukon käsittely

SQL-kyselyt tuottavat tuloksenaan joukon rivejä. Useimmissa ohjelmointikielissä käsittely kuitenkin kohdistuu yhteen alkioon kerrallaan. Kyselyn vastauskin joudutaan käsittelemään ohjelmasilmukassa rivi kerrallaan. Kyselyn käsittelyä varten tietokantaliittymä tarjoaa palvelut

Kääntäminen voidaan tehdä erikseen tai yhdistettynä suorituspyyntöön. Kyselyjä ei kuitenkaan käännetä sovellusohjelman käännöksen yhteydessä vaan vasta sovellusohjelmaa suoritettaessa. Tästä syystä esimerkiksi syntaksivirhe kyselyssä ei paljastu ohjelman käännösvaiheessa vaan vasta sovellusohjelmaa suoritettaessa. Kyselyn käännöksen ja suorituksen hoitaa tietokannan hallintajärjestelmä. Järjestelmä tallentaa omiin tietorakenteisiinsa myös kyselyn vastauksen.

Perinteisesti sovellusohjelma saa kyselyn vastausrivit käyttöönsä pyytämällä yksitellen seuraavaa riviä. Alun perin käytössä on ollut vain yksisuuntaisesti läpikäytävä vastausjoukko. Läpikäynti aloitettiin vastausjoukon alusta ja edettiin 'seuraava rivi' pyynnöllä vastausjoukon loppuun. SQL-92 standardissa tarjolle tuli myös monimuotoisesti läpikäytävä vastausjoukko. Pelkästään seuraavan rivin tarjoavan hakupyynnön lisäksi tarjolle tuli mahdollisuus hakea 'edellinen rivi', 'ensimmäinen rivi', viimeinen rivi' ja 'rivi, joka on annetulla etäisyydellä viimeksi haetusta'. SQL-99 standardissa tarjotaan myös mahdollisuus hakea useita rivejä samanaikaisesti.

Yleiskielen SQL-laajennuksen periaate

Yleiskielen SQL-laajennoksessa kysely määritellään määrittelemällä ns. kursori (cursor) lauseella DECLARE CURSOR. Termi kursori viittaa vastausjoukon läpikäyntiin liittyvään vuorossaolevaan riviin. Määrittelyn yhteydessä annetaan kursorin nimi sekä kytketään siihen kysely. Java-laajennuksessa käytetään kursorin asemasta termiä iteraattori. Java-laajennus poikkeaa muutenkin hieman muiden ohjelmointikielten varsin yhdenmukaisesta laajennustavasta. Kyselyn käännös tehdään PREPARE-lauseella ja suoritus OPEN-lauseella. Rivin hakuun on käytettävissä FETCH-lause. Seuraava esimerkki on laadittu SQL-laajennuksella varustetulla C-kielellä. Sen tarkoituksena on näyttää SQL-laajennuksen käytön yleisperiaatteet sekä miltä laajennokset näyttävät. Laajennuslauseiden yksityiskohtainen syntaksi ei kuulu tämän kurssin asiasisältöön. SQL-laajennus on hyvin samantapainen kaikille ohjelmointikielille, joille se on määritelty. Pieniä kielen rakenteesta johtuvia eroja voi olla. SQL-laajennetun yleiskielen käyttämiseksi tarvitaan tietokannan hallintajärjestelmäkohtainen esikääntäjä.

 /* sisällytetään C.n tarvitsemat .h otsaketiedostot */
 #include <stdio.h>
 #include <string.h>

 /* sisällytetään palautetietoa välittävän SQL communication Area:n */
 /* määrittely */ 
 #include <sqlca.h>
 #define ID_len 20 

 /* Määritellään ns. yhteismuuttujat, joilla välitetään tietoa */
 /* SQL-operaatioihin */
 /* EXEC SQL aloittaa esikääntäjälle tarkoitetun osuuden */ 

 EXEC SQL BEGIN DECLARE SECTION; 
    varchar tunnus[ID_len], salasana[ID_len];
    varchar hakuehto[40];
 EXEC SQL END DECLARE SECTION;


 /* muuttujat, jonne data siirretään kannasta */

 struct {
   varchar nimi[40];
   varchar havainto [20];
 } opiskelijarivi;

 /* Indikaattorimuuttujat, joilla tarkkaillaan vastauskenttien tilaa */
 /* indikaattorimuuttujan arvo poikkeaa nollasta erilaisissa sarake- */
 /* kohtaisissa virhe- tai poikkeustilanteissa */  

 struct {short nimi_ind, havainto_ind; } opiskelija_ind;

 /* opiskelijalaskuri */ 
 int opiskelija_lkm=0;

 /* Määritellään tarpeellisia apufunktioita */ 
 void kysy_tunnus();
 void kysy_hakuehto(); 
 void sql_error(); 

 main() {
   /* Rekisteröidään sql_error() funktio virhekäsittelijäksi. */ 
   EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error \n");

   /* kysytään käyttäjätunnus ja salasana */ 
   kysy_tunnus(&tunnus,&salasana);

   /* Kytkeydytään tietokantaan */
   /* SQL lauseessa kaksoispiste nimen alussa kertoo, */
   /* että kyseessä on viittaus ohjelmamuuttujan arvoon */
   EXEC SQL CONNECT :tunnus IDENTIFIED BY :salasana; 

   /* Määritellään kysely */
   /* hakuehto-muuttujan arvo evaluoidaan vasta kun kysely suoritetaan */ 
   EXEC SQL DECLARE opiskelija_haku CURSOR FOR
     SELECT rpad(substr(sukunimi||' '||etunimet,1,40),40) nimi,
         to_char(viim_havainto_pvm,'DD.MM.YYYY') 
     FROM OPISKELIJA
     WHERE upper(SUKUNIMI) LIKE upper(:hakuehto)
     ORDER BY nimi;

  /* kysytään käyttäjältä hakuehto */   
  kysy_hakuehto(&hakuehto);

  /* suoritetaan kysely */
  /* hakuehto-muuttujan arvo tässä kohdassa määrää haun kohteen */
  EXEC SQL OPEN opiskelija_haku;

  /* määritellään, että poistutaan silmukasta */
  /* kun ei enää ole rivejä jäljellä */ 
  EXEC SQL WHENEVER NOT FOUND DO break;

  /* vastauksen läpikäyntisilmukka */ 
  for (;;) {
     /* seuraavan rivin tiedot ohjelman työalueelle */
     EXEC SQL FETCH opiskelija_haku 
        INTO :opiskelijarivi INDICATOR :opiskelija_ind;

     /* tulostetaan opiskelijan tiedot */
     /* varchar tietotyypissä erillisenä kenttänä arvotaulukko */
     printf(" %s viimeisin kontakti: %s \n", opiskelijarivi.nimi.arr,
        opiskelijarivi.havainto.arr);
     opiskelija_lkm++;
  } 
  printf("\nOpiskelijoita yhteensä %d",opiskelija_lkm);      

  /* päätetään kyselyn käsittely, vapautetaan resurssit */
  ECEC SQL CLOSE opiskelija_haku;

  /* tulostetaan haettujen opiskelijoiden lukumäärä */
  printf("\nHakuehdon täyttäviä opiskelijoita: %d",opiskelija_lkm);

  /* varmuuden vuoksi vaikka tässä onkin vain kysely */
  EXEC SQL COMMIT WORK RELEASE; 

  exit(0); 
}


void kysy_tunnus(varchar *username, varchar *userpasswd) { 
/* kysytään käyttäjältä käyttäjätunnus ja salasana */
/* ohjelmakoodi puuttuu */
} 

void kysy_hakuehto(varchar *ehto) {
/* kysytään sukunimeen perustuva hakuehto */
  char hehto[40];
  printf("\nAnna sukunimi: ");      
  scanf("%s",&hehto);
  strcpy((char*) (ehto->arr), hehto);
  ehto->len = strlen((char*)(ehto.arr));
}

void sql_error(char *msg) {
   /* virheilmoitus */ 
   char err_msg[512]; 
   int buff_len, msg_len; 
   EXEC SQL WHENEVER SQLERROR CONTINUE;
   printf("\n%s\n",msg); 
   /* sqlglm() antaa virheilmoitukseen liittyvän tekstin */ 
   buff_len = sizeof(err_msg); 
   sqlglm(err_msg,&buff_len, &msg_len); 
   printf("%.*s\n", msg_len,err_msg);

   /* Rollback jos on tehty muutoksia */ 
   EXEC SQL ROLLBACK RELEASE; 
   exit(1); 
}
 

JDBC - Java ohjelmointirajapinta

JDBC on tietokannanhallintajärjestelmästä riippumaton ohjelmointirajapinta tietokannan käyttämiseksi Java-ohjelmasta. JDBC-liittymää käytettäessä tarvitaan kuitenkin järjestelmäkohtainen ajuri (driver), joka osaa kommunikoida juuri kyseisen tietokannanhallintajärjestelmän kanssa. JDBC on periaatteiltaan hyvin samankaltainen kuin Microsoftin kehittämä ODBC -rajapinta. ODBC (Open Database Connection) on C-kielen rakenteisiin perustuva funktiorajapinta. JDBC käyttää hyväkseen Javan rakenteita.

Seuraava kuva esittää yleiskuvan JDBC-liittymää käyttävän tietokantakyselyn suorittavan ohjelman rakenteesta tietokantakäsittelyn osalta.

Ohjelmassa rekisteröidään aluksi järjestelmäkohtainen tietokanta-ajuri DriverManager-luokan registerDriver -metodilla. Tämän jälkeen voidaan luoda yhteys tietokantaan. Tietokantayhteyden luonnin yhteydessä on annettava tietokannan tunnus ja käyttäjän käyttäjätunnus sekä salasana. Luotua tietokantayhteyttä hyväksikäyttäen luodaan operaatioiden suoritusympäristö Statement-luokan oliona. Kysely suoritetaan kutsumalla suoritusympäristön executeQuery -metodia siten, että sillä on merkkijonomuotoisena parametrina suoritettavaksi haluttu SQL-kysely. executeQuery -metodi tuottaa tuloksenaan ResultSet -luokan olion. Tämä tarjoaa metodit kyselyn vastausjoukon käsittelyyn.

Vastausjoukko käydään läpi aktivoimalla vuorollaan kukin vastausjoukon riveistä. Totuusarvo tyyppinen funktio next() aktivoi vastausjoukon seuraavana vuorossa olevan rivin. Jos tällainen on olemassa palautetaan totuusarvo true, muuten false. Aktiivisen rivin tiedot on käsittelyä varten haettava ohjelman käyttöön. Tähän tarkoitukseen ResultSet tarjoaa tietotyyppikohtaiset hakufunktiot esim. getString merkkijonon hakuun, getInt kokonaisluvun hakuun ja getDate päiväyksen hakuun. Kaikille näille metodeille voidaan antaa parametrina joko kyselyllä haetun sarakkeen nimi tai järjestysnumero. Metodi palauttaa kyseisessä sarakkeessa aktiivisella rivillä olevan arvon.

Ylläpito- (lisäys, poisto, muutos) ja myös kannan määrittelyoperaatiot suoritetaan muuten samoin kuin yllä on kuvattu, mutta executeQuery-metodin asemesta käytetään metodia executeUpdate. Näiden erona on se, että executeUpdate ei tuota tuloksenaan ResultSet-oliota, koska ylläpito-operaatiot eivät tuota vastausjoukkoa. Ylläpito-operaatioiden yhteydessä executeUpdate palauttaa käsiteltyjen rivien lukumäärän.

Yllä kuvatussa tavassa käsitellä kyselyjä kysely käännetään ja optimoidaan jokaisen executeQuery -pyynnön yhteydessä. Kuitenkin usein rakenteeltaan samanlainen tietokantaoperaatio (kysely tai ylläpito-operaatio) suoritetaan ohjelmassa useaan kertaan siten, että vain jotkin vakioarvot operaatiossa muuttuvat. Esimerkiksi ohjelmassa voitaisiin hakea opiskelijatietoja useasti opiskelijan nimen perusteella. Tällöin suoritettava operaatio on aina nimen perusteella tapahtuva haku, mutta hakukriteerinä käytettävä nimi vaihtelee. Tällaisessa tilanteessa voidaan ohjelman toimintaa tehostaa käyttämällä parametroituja operaatioita.

Parametroiduissa operaatioissa vaihtuvat vakioarvot korvataan parametreilla ja parametroitu kysely toimitetaan tietokantapalvelimelle käännettäväksi. Palvelin kääntää kyselyn vain kertaalleen ja kyselyä voi käyttää uudelleen vaihtamalla vain parametrien arvot. Parametroituja kyselyjä käytettäessä kysely annetaan suoritusympäristön luonnin yhteydessä. Connection luokan prepareStatement -metodille annetaan parametrina parametroitu kysely ja tuloksena on PreparedStatement luokan suoritusympäristö.. Ennen kyselyn suoritusta on parametreille annettava arvot. Tätä varten PreparedStatement suoritusympäristö tarjoaa tietotyyppikohtaiset asetusmetodit, esimerkiksi setInt kokonaislukuparametrin asetukseen ja setString merkkijonoparametrin asetukseen. Parametrin asetusmetodien parametreina ovat parametrin järjestysnumero ja asetettava arvo. Seuraavassa kuvassa on hahmoteltu parametroituja operaatioita käyttävän kyselyohjelman rakenne

JDBC:tä käytettäessä tieto tietokantaoperaation yhteydessä tapahtuneesta virheestä palautetaan SQLException -poikkeuksena. Poikkeusluokalla on metodit, joilla voi kysyä virhekoodia tai järjestelmän tuottamaa virheilmoitusta. Ohjelmoinnissa on aina varauduttava tietokantaoperaatioiden yhteydessä tapahtuviin virhetilanteisiin.

Seuraavat esimerkit havainnollistavat yllä kuvattuja JDBC:n periaatteita. Kurssilla ei ole tarkoitus opetella JDBC:n tarjoamien luokkien ja metodien yksityiskohtia vaan pelkästään yleisperiaate. JDBC-luokkien metodeja on monisteessa kuvattu hieman tarkemmin kuin tässä.

Seuraava esimerkkiohjelma tekee saman kuin ylläoleva C-esimerkki, eli hakee opiskelijoiden nimen ja viimeisen kontaktipäivän annetun hakuehdon perusteella. Ohjelma on alla ensin parametroitua kyselyä käyttävänä ja sitten parametroimatonta kyselyä käyttävänä.

 // Parametroitua kyselyä käyttävä versio
 // Otetaan mukaan tarvittavia kirjastoja 
 // CLASSPATH:ssa täytyy olla polku ajuriin

 #import java.sql.*;
 #import java.io.*;

 public class Esimerkki {

   public static void main(String args[]) throws Exception {
     // tietokantayhteyden määrittelyä - oracle thin ajurilla yhteys
     // koneessa bodbacka.cs.helsinki.fi toimivaan test kantaan
     String url= "jdbc:oracle:thin:@bodbacka.helsinki.fi:1521:test";
     String ajuri= "oracle.jdbc.OracleDriver";

     // kysely - tässä käytetään parametroitua kyselyä 
     // kysymysmerkki osoittaa parametrin
     String opiskelija_haku =
       "SELECT rpad(substr(sukunimi||\' \'||etunimet,1,40),40) nimi,"+
       "    to_char(viim_havainto_pvm,\'DD.MM.YYYY\') vhp "+ 
       "FROM OPISKELIJA "+
       "WHERE upper(SUKUNIMI) LIKE upper(?) "
       "ORDER BY nimi";

     int opiskelija_lkm=0;        // opiskelijoiden lukumäärä
     Connection con=null;         // tietokantayhteysmuuttuja
     PreparedStatement stm= null; // kyselyn suoritusympäristö
     ResultSet rs= null;          // kyselyn vastausjoukko

     // Rekisteröidään ajuri 
     // Ajurit osaavat rekisteröityä itse kun ne ladataan,
     // joten tässä ei tarvita DriverManagerin registerDriver -kutsua 

     try {
        Class.forName(ajuri);
     } catch (ClassNotFoundException) {
        System.out.println("Ajurin lataus ei onnistu");
        return;
     }  

     // kysytään käyttäjätunnus ja salasana  
     BufferedReader stdin =
        new BufferedReader(new InputStreamReader(System.in));
     System.out.print("Käyttäjätunnus: ");
     String tunnus= stdin.readLine();
     System.out.print("Salasana: ");   
     String salasana= stdin.readLine();

     // Luodaan tietokantayhteys
     try {  
       con=DriverManager.getConnection(url,tunnus,salasana);
     } catch (SQLExecption ss) {
        System.out.println("Tietokantayhteyden avaus ei onnistu");
        return;
     } 
   
     try {
       // Luodaan suoritusympäristö ja käännetään kysely 
       stm= con.prepareStatement(opiskelija_haku);

       // kysytään käyttäjältä hakuehto   
       System.out.print("Hakuehto: ");
       String hakuehto= stdin.readLine();

       // asetetaan kyselyn parametri 
       stm.setString(1,hakuehto);

       // suoritetaan kysely 
       rs = stm.executeQuery();

       // käydään läpi vastausrivit 
       while (rs.next()) {
         // haetaan arvot get-funktioilla
         System.out.println(rs.getString("NIMI")+
                             " viimeisin kontakti "+
                             rs.getString("VHP"));
         opiskelija_lkm++;       
       }

       System.out.print("\nOpiskelijoita yhteensä: ");
       System.out.println(opiskelija_lkm);   
    } catch (SQLException e) {
      // tulostetaan tkhj:n tuottama virheilmoitus
      System.out.println("Tietokantavirhe "+ e.getMessage());
    } finally { 
      // lopuksi vapautetaan resurssit   
        try {
          if (rs!=null)
            rs.close();
          if (stm!=null) 
            stm.close();
          con.close();
        } catch (SQLException s) {}    
    }
 }
}

Seuraavan esimerkin edellisestä eroavat rivit on merkitty punaisella

 // Parametroimatonta kyselyä käyttävä versio
 // Otetaan mukaan tarvittavia kirjastoja 
 // CLASSPATH:ssa täytyy olla polku ajuriin

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

 public class Esimerkki2 {

    public static void main(String args[]) throws Exception {
     // tietokantayhteyden määrittelyä - oracle thin ajurilla yhteys
     // koneessa kontti.helsinki.fi toimivaan tktb kantaan
     String url= "jdbc:oracle:thin:@bodbacka.cs.helsinki.fi:1521:test";
     String ajuri= "oracle.jdbc.OracleDriver";

     // kysely - tässä käytetään parametroimatonta kyselyä 
     String opiskelija_haku =
       "SELECT rpad(substr(sukunimi||\' \'||etunimet,1,40),40) nimi,"+
       "    to_char(viim_havainto_pvm,\'DD.MM.YYYY\') vhp "+ 
       "FROM OPISKELIJA "+
       "WHERE upper(SUKUNIMI) LIKE upper(";

     int opiskelija_lkm=0;        // opiskelijoiden lukumäärä
     Connection con=null;         // tietokantayhteysmuuttuja

     Statement stm= null;         // kyselyn suoritusympäristö
     ResultSet rs= null;          // kyselyn vastausjoukko

     // Rekisteröidään ajuri 
     // Ajurit osaavat rekisteröityä itse kun ne ladataan,
     // joten tässä ei tarvita DriverManagerin registerDriver -kutsua 

     try {
        Class.forName(ajuri);
     } catch (ClassNotFoundException) {
        System.out.println("Ajurin lataus ei onnistu");
        return;
     }  

     /* kysytään käyttäjätunnus ja salasana */ 
     BufferedReader stdin =
        new BufferedReader(new InputStreamReader(System.in));
     System.out.print("Käyttäjätunnus: ");
     String tunnus= stdin.readLine();
     System.out.print("Salasana: ");   
     String salasana= stdin.readLine();

     // Luodaan tietokantayhteys
     try {   
       con=DriverManager.getConnection(url,tunnus,salasana);
     } catch (SQLExecption ss) {
        System.out.println("Tietokantayhteyden avaus ei onnistu");
        return;
     } 

     try {  
       // Luodaan suoritusympäristö ja käännetään kysely 
       stm= con.createStatement();

       // kysytään käyttäjältä hakuehto   
       System.out.print("Hakuehto: ");
       String hakuehto= stdin.readLine();

       // rakennetaan kysely lisäämällä muuttujan hakuehto
       // arvo yksinkertaisissa lainausmerkeissä alkuosan perään
       // ja suoritetaan rakennettu kysely 

       rs = stm.executeQuery(opiskelija_haku + "\'" +
                             hakuehto+"\') ORDER BY nimi");

       // käydään läpi vastausrivit 
       while (rs.next()) {
         // haetaan arvot get-funktioilla
         System.out.println(rs.getString("NIMI")+
                             " viimeisin kontakti "+
                             rs.getString("VHP"));
         opiskelija_lkm++;       
       }
       System.out.print("\nOpiskelijoita yhteensä: ");
       System.out.println(opiskelija_lkm);   
    } catch (SQLException e) {
      // tulostetaan tkhj:n tuottama virheilmoitus
      System.out.println("Tietokantavirhe "+ e.getMessage());
    } finally { 
      // lopuksi vapautetaan resurssit   
        try {
          if (rs!=null)
            rs.close();
          if (stm!=null) 
            stm.close();
          con.close();
        } catch (SQLException s) {}
    }
  }
}

Seuraava esimerkki on ylläpitoesimerkki. Tässä esimerkissä käydään läpi ne opettajat, joilla ei ole työpuhelinta ja annetaan käyttäjälle mahdollisuus kirjata numero.

 // Ylläpito-ohjelma
 // Otetaan mukaan tarvittavia kirjastoja 
 // CLASSPATH:ssa täytyy olla polku ajuriin

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

 public class YllapitoEsimerkki {

   public static void main(String args[]) throws Exception {
     // tietokantayhteyden määrittelyä - oracle thin ajurilla yhteys
     // koneessa kontti.helsinki.fi toimivaan tktb kantaan
     String url= "jdbc:oracle:thin:@bodbacka.cs.helsinki.fi:1521:test";
     String ajuri= "oracle.jdbc.OracleDriver";

     // muutoksen kohteet hakeva kysely
     String opettaja_haku =
       "SELECT opetunnus, sukunimi||\' \'||etunimet NIMI "+ 
       "FROM OPETTAJA "+
       "WHERE TYOPUHELIN IS NULL " +
       "ORDER BY NIMI";

     // parametroitu muutosoperaatio
     String muutos =   
       "UPDATE OPETTAJA "+
       "SET TYOPUHELIN = ? "+
       "WHERE OPETUNNUS = ?";

     Connection con=null;         // tietokantayhteysmuuttuja
     Statement stm= null;         // kyselyn suoritusympäristö
     PreparedStatement pstm = null;   //ylläpidon suoritusympäristö;
     ResultSet rs= null;          // kyselyn vastausjoukko
     String tunnus= "MIKKI";
     String salasana= "HIIRI";

     // Rekisteröidään ajuri 
     // Ajurit osaavat rekisteröityä itse kun ne ladataan,
     // joten tässä ei tarvita DriverManagerin registerDriver -kutsua 
     try {
        Class.forName(ajuri);
     } catch (ClassNotFoundException) {
        System.out.println("Ajurin lataus ei onnistu");
        return;
     }  

     BufferedReader stdin =
        new BufferedReader(new InputStreamReader(System.in));

     // Luodaan tietokantayhteys 
     try {
       con=DriverManager.getConnection(url,tunnus,salasana);
     }
     catch (SQLException s1) {
       System.out.println("Tietokantaan ei saada yhteyttä!");
       return;
     }

     try {  
       // Luodaan kyselyn suoritusympäristö
       stm= con.createStatement();

       // käännetään ylläpito-operaatio
       pstm= con.prepareStatement(muutos);

       // Suoritetaan kysely 
       rs = stm.executeQuery(opettaja_haku);

       // tulostetaan otsake
       System.out.println("ANNA TYÖPUHELINNUMEROT");
       SYSTEM.out.println("======================");
       String puhno= null;

       // käydään läpi vastausrivit 
       while (rs.next()) {
         // kysytään numeroa nimellä
         System.out.print(rs.getString("NIMI")+": ");
         puhno= stdin.readLine();
         if (puhno!=null) {
            // asetetaan ylläpito-operaation parametriarvot
            pstm.setString(1,puhno);
            pstm.setString(2,getString("OPETUNNUS"));
            // suoritetaan ylläpito-operaatio
            pstm.executeUpdate();
            // vahvistetaan kukin muutos omana tapahtumanaan 
            con.commit();
         }          
       }   
     } catch (SQLException e) {
        // tulostetaan tkhj:n tuottama virheilmoitus
        System.out.println("Tietokantavirhe "+ e.getMessage());
     } finally { 
        // lopuksi vapautetaan resurssit   
        try {
           if (rs!=null) 
              rs.close();
           if (stm!=null) 
              stm.close(); 
           if (pstm!=null) 
              pstm.close();
           con.close();
        } catch (SQLException s) {}
    }
 }
 

 

Tietokantasovellus, Harri Laine, Helsigin yliopisto, Tietojenkäsittelytieteen laitos