380 likes | 489 Views
4.3. Tietokantojen ohjelmointirajapinnat. Engl. API (Application Programming Interface). Ohj.kielen täydennys moduuli-/luokkakirjastoilla SQL/CLI (Call-Level Interface), osa SQL-standardia vuodesta 2003. Tunnetuimpia rajapintoja:
E N D
4.3. Tietokantojen ohjelmointirajapinnat • Engl. API (Application Programming Interface). • Ohj.kielen täydennys moduuli-/luokkakirjastoilla • SQL/CLI (Call-Level Interface), osa SQL-standardia vuodesta 2003. • Tunnetuimpia rajapintoja: • ODBC (Open DataBase Connectivity):SQL/CLI-pohjainen, Microsoftin kehittämä, yleinen C, C++, C# ja Visual Basic –kielten yhteydessä. • JDBC (‘Java DataBase Connectivity’): Sun Micro-systemsin piirissä kehitetty API-kirjasto Javalle. 4-3-OhjRajapinnat Teuhola 2012
JDBC: Yleistä • JDBC on Javan luokka-/rajapintakirjastomääritys, joka mahdollistaa SQL-lauseiden suorituksen Javasta käsin (lauseet metodien parametreina). • Vastaa dynaamista, sulautettua SQL:ää. • Ero SQLJ:hin: JDBC ei ole kielen laajennus,joten standardi Java-kääntäjä riittää. • Hyvin tuettu (‘teollisuusstandardi’) • Javan etu: siirrettävyys (portability). • JDBC:tä käyttävä ohjelma on lähes riippumaton siitä, mikä tietokantajärjestelmä on käytössä (voidaan vaihtaa) 4-3-OhjRajapinnat Teuhola 2012
JDBC-ajuri • JDBC-API toimii sovellusohjelmien rajapintana. Yhteydet tietokantaan hoitaa JDBC-ajuri (driver). • Kuhunkin tietokantajärjestelmään on oma ajurinsa; saatavana järjestelmän toimittajalta, ks. esim. http://jdbc.postgresql.org/download.html. • Ajuri on jar-paketti (esim. postgresql.jar), joka sijoitetaan haluttuun hakemistopolkuun. Polku pitää määritellä CLASSPATH-ympäristömuuttu-jassa. 4-3-OhjRajapinnat Teuhola 2012
Java-ohjelma Asiakas JDBC-ajuri Tietokannanhallintajärjestelmä Palvelin Tietokanta Asiakas/palvelin-arkkitehtuuri 4-3-OhjRajapinnat Teuhola 2012
JDBC:n mukaanotto sovellusohjelmaan • JDBC-pakkaukset (kuuluvat J2SE:hen):import java.sql.*; // Perus-JDBCimport javax.sql.*; // Laajennuksia, JDBC 2.0, 3.0, 4.0DriverManager pitää kirjaa ajureista, joita samassa sovelluksessa voi olla useita. Ajuri pitää ladata ja rekisteröidä Driver Managerille esim. seuraavasti: Class.forName(“org.postgresql.Driver”)missä ajurin tiedostopolku on em. jar-paketissa. 4-3-OhjRajapinnat Teuhola 2012
Yksinkertaisen JDBC-sovelluksen vaiheet • Ladataan ajuri (vrt. ed.) • Muodostetaan yhteys (Connection) tietokantaan • Luodaan SQL-lause (Statement) • Suoritetaan lause • Käsitellään mahdollinen tulosjoukko (ResultSet), tai siepataan ja käsitellään poikkeukset. • Toistetaan kohtia 3-5 tarpeen mukaan • Suljetaan tietokantaoliot (tulosjoukko, yhteys) 4-3-OhjRajapinnat Teuhola 2012
Yhteyden muodostus tietokantaan • Tarvittava metodi löytyy DriverManager-luokasta: DriverManager.getConnection(<url>, <käyttäjä>, <salasana>);missä <url> on muotoa “jdbc:<ajuri>:<datalähde>”, esim. "jdbc:postgresql://kanta.cs.utu.fi:5432/company“ 4-3-OhjRajapinnat Teuhola 2012
SQL-lauseet • Lause-olioita (Statement-rajapinta) tarvitaan edustamaan SQL-lauseita. • Statement-rajapinnassa on mm. metodeita SQL-lauseen suorittamiseksi. • Tärkeä alirajapinta: PreparedStatement:SQL-lause jäsennetään, tarkistetaan ja käännetään vain kerran, mutta suoritetaan ehkä toistuvasti (tehokkaampi). 4-3-OhjRajapinnat Teuhola 2012
Statement-olion luonti ja suoritus • Luonti Connection-olion createStatement-metodilla. • Suoritus executeQuery- tai executeUpdate-metodilla lauseen tyypistä riippuen. Kyselyn tulos on tyyppiä ResultSet. Esimerkki:Connection c = DriverManager.getConnection(url, user, pwd); Statement st = c.createStatement(); ResultSet res = st.executeQuery( "SELECT Ano, Animi FROM Asiakas;"); 4-3-OhjRajapinnat Teuhola 2012
Statement-olion luonti ja suoritus (jatk.) • ResultSet-olio (esimerkissä res) edustaa 2-ulotteista taulukkoa, jota voidaan kulkea iteraattorin tapaan rivi kerrallaan (vrt. SQLJ:n ‘iterator’). • Ensin res osoittaa 1. rivin ‘edelle’, josta res.next()-metodilla saadaan 1., 2., … rivi, kunnes res==NULL. • Yksittäiset rivin alkiot saadaan res.getxxx(sarake)-metodeilla, missä ‘xxx’ on alkion tyyppi (getInt, getLong, getFloat, getDouble, getString, getBoolean, getDate, getTime, getObject, ym.). • ‘Sarake’ voidaan ilmaista joko järjestysnumerolla(1, 2, …) tai tulosattribuutin nimellä. 4-3-OhjRajapinnat Teuhola 2012
Statement-olion luonti ja suoritus (jatk.) • JDBC ei tee eroa 1- ja monirivisten tulos-taulukoiden välillä. Jos tuloksessa tiedetään olevan vain 1 rivi, silmukka voidaan välttää, mutta res.next():llä pitää siirtyä 1. riville. • Poikkeuksista: • JDBC-kutsut voivat nostaa mm. SQLException-poikkeuksia. • Ne voidaan joko käsitellä (try … catch …) tai siirtää kutsujalle. 4-3-OhjRajapinnat Teuhola 2012
JDBC-esimerkki // Etsi annetun paikkakunnan henkilöt relaatiosta// Asiakas (Ano, Animi, Paikka).//import java.io.*;import java.sql.*;class HaeAsiakkaat {publicstaticvoid main(String args[]) throws SQLException, IOException {try { Class.forName (“org.postgresql.Driver”); }catch (ClassNotFoundException cnfe) { System.out.println(“Ajuria ei löydy!”); } // jatkuu seur. sivulla … 4-3-OhjRajapinnat Teuhola 2012
JDBC-esimerkki (jatk.) String tunnus = prompt(“Anna käyttäjätunnus”); // Hoputemetodi String salasana = prompt(“Anna salasana”); Connection conn = DriverManager.getConnection( “jdbc:postgresql://host/tietokanta”, tunnus, salasana); String paikkakunta = prompt(“Anna paikkakunta”); String kys = “SELECT Ano, Animi FROM Asiakas ” + “WHERE Paikka = ‘” + paikkakunta + “’;”; Statement stmt = conn.createStatement(); ResultSet res = stmt.executeQuery(kys);while (res.next()) {int no = res.getInt(1); String nimi = res.getString(2); System.out.println(no + “ “ + nimi); } res.close(); conn.close(); } } 4-3-OhjRajapinnat Teuhola 2012
JDBC: SQL-lauseen toistuva suoritus • Lause kannattaa jäsentää ja optimoida vain kerran ja käyttää ‘käännettyä’ versiota toistuvissa suorituksissa. • PreparedStatement (‘valmisteltu’ lause) on tällainen: • perii Statement-rajapinnan, • luonti Connection-olion prepareStatement()-metodilla, • parametrina SQL-lause. • Mutta: Yleensä eri suorituskerroilla on jotain eroa; SQL-lause on voitava parametrisoida. 4-3-OhjRajapinnat Teuhola 2012
PreparedStatement:SQL-lauseen parametrisointi • JDBC-ratkaisu:SQL-lauseeseen sijoitetaan tuntemattomien osien kohdalle ‘?’-merkkejä, jotka voidaan eri kerroilla sitoa eri parametriarvoihin. • ‘?’-parametrit ajatellaan numeroiduiksi 1, 2, … • Parametrisointi rajoitettua; esim. FROM-osan relaationimiä ei voi parametrisoida. • Parametrien asetus setxxx()-metodeilla (setInt, setFloat, setString, …) 4-3-OhjRajapinnat Teuhola 2012
“Etsi annettujen paikkakuntien asiakkaat” // … Alkuosa kuten edellä. String kys = “SELECT Ano, Animi FROM Asiakas ” + “WHERE Paikka = ?;”; PreparedStatement ps = conn.prepareStatement(kys); String paikka = prompt(“Anna paikkakunta, lopuksi tyhjä”);while (!(paikka.equals(“”))) { System.out.println(“Paikkakunnan ” + paikka + “ asiakkaat”); ps.setString(1, paikka); ResultSet res = ps.executeQuery();while (res.next()) {int no = res.getInt(1); String nimi = res.getString(2); System.out.println(no + “ “ + nimi); } res.close(); paikka = prompt(“Anna paikkakunta, lopuksi tyhjä”); } conn.close(); }} 4-3-OhjRajapinnat Teuhola 2012
JDBC: Päivityslauseet • Metodilla executeQuery() voidaan suorittaa vain SELECT-lauseita. • Päivityslause suoritetaan käskyllä executeUpdate(), joka palauttaa päivitettyjen rivien määrän. 4-3-OhjRajapinnat Teuhola 2012
“Annetun valmistajan tuotteille annetun suuruinen alennus” // Relaatioskeema:// Tuote (Tno, Tnimi, Valmistaja, Hinta) …String valm = prompt(“Anna valmistaja”);int ale = promptInt(“Anna alennusprosentti”);String paiv = “UPDATE Tuote “ + “SET Hinta = Hinta*(100-” + ale + “)/100 “ + “WHERE Valmistaja = ‘“ + valm + “’;”);int lkm = conn.executeUpdate(paiv);System.out.println(lkm + “ tuotetta halpeni”);… 4-3-OhjRajapinnat Teuhola 2012
Yleistetty ResultSet-rajapinta • Mahdollistaa monipuolisemman vierityksen (scroll): first(), last(), next(), previous(), absolute(int), relative(int) • Mahdollistaa nykyistä ResultSet-riviä vastaavan tietokantarivin päivityksen. • Vrt. SQLJ:#sql iterator … implements sqlj.runtime.ForUpdate • Yleistetty tulosjoukko määritellään Statement-olion luonnin yhteydessä. 4-3-OhjRajapinnat Teuhola 2012
Yleistetty ResultSet-rajapinta (jatk.) • Lauseen luonti esim.Statement lause = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); • Lauseen suoritus esim.:ResultSet res = lause.executeQuery( "SELECT Ano, Animi, Paikka FROM Asiakas;"); • Päivitys, esim. kolmanteen tulosriviin uusi paikka:res.absolute(3); // Siirtyminen 3. rivilleres.updateString(“Paikka", “Parainen"); // Uusi arvores.updateRow(); // Päivittää lähderelaation vastinrivin 4-3-OhjRajapinnat Teuhola 2012
Esimerkki ResultSet-päivityksestä // Relaatio: Tuote(Tno, Tnimi, Valmistaja, Hinta) // Laske ‘Vipu Oy:n’ tuotteiden hintoja 5 €:lla. // public static void laskeHintoja(Connection conn) throws Exception { String kys = "SELECT * FROM Tuote WHERE Valmistaja='Vipu Oy‘;"; Statement lause = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet res = lause.executeQuery(kys); while (res.next()) { res.updateInt("hinta", res.getInt("hinta") - 5); res.updateRow(); } res.close(); conn.commit(); conn.close(); } 4-3-OhjRajapinnat Teuhola 2012
Ehtoja ResultSet-päivitykselle • Tulosrivien alkuperä tietokannassa pitää pystyä automaattisesti ja yksikäsitteisesti selvittämään • Päivitys mahdollinen, jos • Tulosjoukko on peräisin yhdestä relaatiosta (ei esim. liitoksia) • Relaation pääavain on mukana tulosattribuuteissa. • Huom! Kyseessä on täsmälleen sama ongelma kuin näkymän (view) päivityksessä.Resultset on itse asiassa eräs näkymä kantaan 4-3-OhjRajapinnat Teuhola 2012
JDBC: Mielivaltaiset lauseet • Jos lauseesta ei etukäteen tiedetä, onko se kysely vai päivitys (esim. käyttäjä on syöttänyt sen suorituksen aikana), käytetään execute()-metodia, joka palauttaa boolean-arvon true, jos lause oli kysely, muuten false. Tuloksen poiminta tehdään eri tavoilla tästä riippuen. 4-3-OhjRajapinnat Teuhola 2012
Esimerkki: SQL-lauseen lukeminen näppäimistöltä ja suoritus …String lause = prompt(“Anna SQL-lause”);Statement st = conn.createStatement();boolean tulos = st.execute(lause);if (tulos) { ResultSet rs = st.getResultSet(); …}else {int lkm = st.getUpdateCount(); …}… 4-3-OhjRajapinnat Teuhola 2012
Kyselyn tulokseen liittyvä metadata • Tarvitaan, jos metadata (eli skeemaan liittyvä data) ei ole tiedossa ohjelmaa kirjoitettaessa (vrt. ed. sivun esimerkki).…ResultSet rs = st.getResultSet();ResultSetMetaData rsmd = rs.getMetaData();int sarLkm = rsmd.getColumnCount();for (int sar = 1; sar <= sarLkm; sar++) { System.out.println(“Sarake no “ + sar); System.out.println(“Nimi: “+ rsmd.getColumnName(sar)); System.out.println(“Tyyppi: “ + rsmd.getColumnTypeName(sar)); …} 4-3-OhjRajapinnat Teuhola 2012
Tietokantayhteyteen liittyvä metadata • Toistasataa erilaista tietoa, esim. tiedot ajurista:…Connection conn = DriverManager.getConnection(…);DatabaseMetaData dbmd = conn.getMetaData();System.out.println(“Ajuri: “ + dbmd.getDriverName());System.out.println(“Versio: “ + dbmd.getDriverVersion());… 4-3-OhjRajapinnat Teuhola 2012
Transaktioiden hallinta • Oletusarvoisesti jokainen lause muodostaa oman transaktionsa. • Eksplisiittinen hallinta (conn tyyppiä Connection): conn.setAutoCommit(false); … …Päivityslauseita … conn.commit(); // tai conn.rollback(); • Voi syntyä SQLException, joka on käsiteltävä. 4-3-OhjRajapinnat Teuhola 2012
Ohjelmointitekniikoista • Java-ohjelmoinnissa luokkien suunnittelu on keskeisessä asemassa. • Tietokannan käyttö kannattaa keskittää omiin luokkiinsa/metodeihinsa, esim. seuraavasti: • Ajuriin liittyvät toiminnot • Tietokantayhteyteen liittyvät toiminnot • Relaatioita edustavat ‘kuoriluokat’ (‘wrapper’) • Relaatioiden rivejä (= tietokannan varsinaisia ‘olioita’) edustavat luokat • Varsinaiset sovellukset 4-3-OhjRajapinnat Teuhola 2012
Esimerkki apuluokasta:Ajurin ja tietokantayhteyden hallinta import java.io.*; import java.sql.*; import java.util.*; public class Yhteys { public static Connection luoYhteys(String kanta) throws Exception { String driver = "org.postgresql.Driver"; String url = "jdbc:postgresql://kanta.cs.utu.fi:5432/" + kanta; String user = “ewert"; BufferedReader in = new BufferedReader(new FileReader("sala.txt")); String pwd = in.readLine(); Class.forName(driver); Connection conn = DriverManager.getConnection(url, user, pwd); return conn; } public static void suljeYhteys(Connection conn) throws Exception { conn.close(); } } 4-3-OhjRajapinnat Teuhola 2012
Esimerkki relaation riviä edustavasta luokasta // Asiakas-entiteetti import java.util.*; public class Asiakas { privateint ano; // Yksityisetprivate String animi; // esiintymä-private String paikka; // muuttujat public Asiakas(int no, int ni, String pa) { Ano = no; animi = ni; paikka = pa; // Alustaja}public int getAno() { return ano; } // Ns.public String getAnimi() { return animi; } // getter-public String getPaikka() { return paikka; } // metoditpublic String toString() {return getAno()+”, ’”+getAnimi()+’”, ”’+getPaikka()+”’”; } 4-3-OhjRajapinnat Teuhola 2012
Esimerkki relaation ‘kuoriluokasta’ // Asiakas(Ano, Animi, Paikka)-relaatiota edustava luokka import java.util.*; import java.sql.*; public class Asiakkaat { // Uuden asiakkaan lisäys; yhteys oltava luotu public static void uusiAsiakas(Connection conn, Asiakas a) { try { Statement st = conn.createStatement(); String lause = "INSERTINTO Asiakas VALUES(" + a.toString() + “)”; int riveja = st.executeUpdate(lause); conn.commit(); } catch (SQLException se) { System.out.println("Lisäys pieleen!“ + se.toString()); } } 4-3-OhjRajapinnat Teuhola 2012
Esimerkki relaation ‘kuoriluokasta’ (jatk.) // Paikkakunnan asiakkaiden haku Asiakas-relaatiosta (vrt. aik. esim.)// public static ArrayList<Asiakas> haeAs(Connection conn, String pnimi) { ArrayList<Asiakas> arr = new ArrayList<Asiakas>(); try { Statement st = conn.createStatement(); String lause = “SELECT Ano, Animi, Paikka “ + “FROM Asiakas WHERE Paikka =‘”+pnimi + “‘;”; ResultSet res = st.executeQuery(lause); while (res.next()) { Asiakas a = new Asiakas( res.getInt(“Ano”), res.getString(“Animi”), res.getString(“Paikka”)) arr.add(a); } res.close(); } catch (SQLException se) { System.out.println("Paikanhaku ei onnistunut!“ + se.toString()); } return arr; } 4-3-OhjRajapinnat Teuhola 2012
Esimerkki relaation ‘kuoriluokasta’ (jatk.) // Asiakkaan paikkakunnan muutos publicstaticvoid muutaPaikka( Connection conn, Asiakas a, String uusiPaikka) { try { conn.setAutoCommit(false); // Transaktio hallitaan itse Statement st = conn.createStatement(); String lause = “UPDATE Asiakas “ + “SET Paikka=‘” + uusiPaikka + “‘” + “WHERE Ano=" + a.getAno() + “;”; int riveja = st.executeUpdate(lause); conn.commit(); // Muutoksen vahvistus } catch (SQLException se) { System.out.println("Paikanmuutos epäonnistui:“+se.toString()); try { conn.rollback(); } catch (SQLException re) { // Peruutus System.out.println(“Rollback meni pieleen: “+ re.toString()); } }} 4-3-OhjRajapinnat Teuhola 2012
Esimerkki relaation ‘kuoriluokasta’ (jatk.) // Koko asiakastaulun tulostus ruudulle public static void tulostaKaikki(Connection conn) { try { Statement st = conn.createStatement(); String lause = "SELECT * FROM Asiakas;"; ResultSet res = st.executeQuery(lause); System.out.println(""); System.out.println(" Ano | Animi | Paikka"); System.out.println("-----------------------"); while (res.next()) { System.out.print(res.getInt(1)); System.out.print(" | "); System.out.print(res.getString(2)); System.out.print(" | "); System.out.println(res.getString(3)); } res.close(); } catch (SQLException se) { System.out.println( "Asiakkaiden haku ei onnistunut!“ + se.toString()); } } 4-3-OhjRajapinnat Teuhola 2012
Esimerkki kuoriluokan testiohjelmasta public static void main( String args[ ] ) throws Exception { Connection conn = Yhteys.luoYhteys("til"); // Alustus Asiakkaat.tulostaKaikki(conn); // Rel. sisältö alussa Asiakas a = new Asiakas(444, "Laine", "Naantali”); Asiakkaat.uusiAsiakas(conn, a); // Uuden lisäys Asiakkaat.tulostaKaikki(conn); // Uusi sisältö ArrayList<Asiakas> arr = Asiakkaat.haePaikka(conn, "Turku"); // Kysely System.out.println("Turkulaiset asiakkaat: "); // Vastaus for (int i=0; i<arr.size(); i++) { System.out.println(arr.get(i).toString()); } // Turkulaiset Asiakkaat.muutaPaikka(conn, a, "Raisio"); // Paikkakunnan vaihto Asiakkaat.tulostaKaikki(conn); // Muutettu sisältö Yhteys.suljeYhteys(conn); // Lopetus } 4-3-OhjRajapinnat Teuhola 2012
Tietokantasovelluksen laatimisesta • Voidaan käyttää erilaisia ohjelmistoratkaisuja, eli ns. suunnittelumalleja. • Yleinen periaate: tietokantaa käsittelevien toimintojen eristäminen omiin luokkiinsa, jolloin niiden ylläpito selkeytyy. • Esim. Data Access Objects (DAO) –malli:Kerrosmainen arkkitehtuuri: • Käyttöliittymä • Sovelluslogiikka • Tietokantaa käsittelevät osat sovelluksesta • JDBC-rajapinta • Tietokanta 4-3-OhjRajapinnat Teuhola 2012
Tietokantaa käsittelevien luokkien rakenteellisia vaihtoehtoja • Muodostetaan yksi (iso) luokka, johon kerätään kaikki tietokantaa käsittelevät metodit. • Muodostetaan yksi luokka kutakin tietokannan entiteettikokoelmaa (eli relaatiota) kohti, johon kootaan ko. relaatioon liittyvät metodit, mutta mahdollisesti myös esim. liitosoperaatiot muiden relaatioiden kanssa. • Muodostetaan yksi luokka kutakin entiteettityyppiä kohti, jonka instanssit vastaavat relaation yksittäisiä rivejä. Yllä tilaus-asiakas-sovelluksessa käytettiin lähinnä kakkosvaihtoehtoa (luokka ’Asiakkaat’). Harjoitustyön malliesimerkissä on piirteitä 1- ja 3-vaihtoehdoista. 4-3-OhjRajapinnat Teuhola 2012
Object-relational mapping (ORM) • Relaatiokannan sovellusohjelmointi sisältää runsaasti saman kaavan mukaisia metodeja, jotka muuntavat Javan olioesityksen relaatiomallin mukaiseksi taulukkoesitykseksi ja päinvastoin. • Muunnos on mahdollista osittain automatisoida, mikä nopeuttaa sovelluskehitystä ja ylläpitoa. • Tarvitaan muunnoksen kuvaus loogisella tasolla.Tämä tehdään usein erillisenä kuvaustiedostona(tyypillisesti XML-kielellä). • Ei poista tietokannan suunnittelutarvetta. • Esimerkki ORM-tuotteesta: Hibernate 4-3-OhjRajapinnat Teuhola 2012