Indhold:
Få kontakt til en database fra Java
Kommandoer til en database
Forespørgsler til en database
Kapitlet forudsættes ikke i resten af bogen.
Forudsætter kapitel 14, Undtagelser og lidt kendskab til databaser og databasesproget SQL (Structured Query Language), og at du har en fungerende database, som du ønsker adgang til fra Java.
Adgang til en database fra Java sker gennem et sæt klasser, der under et kaldes JDBC (Java DataBase Connectivity) - en platformuafhængig pendant til Microsoft ODBC (Open DataBase Connectivity). Klasserne ligger i pakken java.sql, så kildetekstfiler, der arbejder med databaser, skal starte med:
import java.sql.*;
At få kontakt til databasen er måske det sværeste skridt. Det består af to led:
Indlæse databasedriveren
Etablere forbindelsen
Indlæsning af driveren sker ved at bede systemet indlæse den pågældende klasse, der derefter registrerer sig selv i JDBC-systemets driver-manager. Er det f.eks. en Oracle-database, skriver man:
Class.forName("oracle.jdbc.driver.OracleDriver");
Ofte skal man have en jar-fil (et Java-ARkiv, en samling klasser pakket i zip-formatet) med en driver fra producenten. Nyeste drivere kan findes på http://java.sun.com/jdbc .
For en Oracle-database hedder filen classes12.zip og passer til en bestemt udgave af Oracle-databasen. Bruger man Oracle JDeveloper, er den som standard med i projektets klassesti. Ellers skal den føjes til CLASSPATH (i JBuilder gøres det under Project Properties, Paths, Required Libraries)
Herefter kan man oprette forbindelsen med (for en Oracle-database):
Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@oracle.cv.ihk.dk:1521:student","brugernavn","adgangskode");
Første parameter er en URL til databasen. Den består af en protokol ("jdbc"), underprotokol ("oracle") og noget mere, der afhænger af underprotokollen. I dette tilfælde angiver det, at databasen ligger på maskinen oracle.cv.ihk.dk port 1521 og hedder student.
Anden og tredje parameter er brugernavn og adgangskode.
Med Java under Windows følger en standard JDBC-ODBC-bro med, så man kan kontakte alle datakilder defineret under ODBC. Denne driver indlæses med:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Når forbindelsen oprettes, angiver man den ønskede datakildes navn. Husk, at datakildens navn (her "datakilde1") først skal være defineret i Windows' Kontrolpanel under ODBC:
Connection forb = DriverManager.getConnection("jdbc:odbc:datakilde1");
Bemærk, at ODBC er en ret langsom protokol. Har du brug for bedre ydelse bør du finde en driver, der kommunikerer direkte med databasen, i stedet for at bruge JDBC-ODBC.
Når vi har en forbindelse, kan vi oprette et "statement"-objekt, som vi kan sende kommandoer og forespørgsler til databasen med:
Statement stmt = forb.createStatement();
Der kan opstå forskellige undtagelser af typen SQLException, der skal fanges.
SQL-kommandoer, der ikke giver et svar tilbage i form af data, som INSERT, UPDATE, DELETE, CREATE TABLE og DROP TABLE, sendes med executeUpdate()-metoden.
Her opretter vi f.eks. tabellen "kunder" og indsætter et par rækker:
import java.sql.*; public class SimpeltDatabaseeksempel { public static void main(String[] arg) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection forb = DriverManager.getConnection("jdbc:odbc:datakilde1"); Statement stmt = forb.createStatement(); stmt.executeUpdate("create table KUNDER (NAVN varchar(32), KREDIT float)" ); stmt.executeUpdate("insert into KUNDER values('Jacob', -1799)"); stmt.executeUpdate("insert into KUNDER values('Brian', 0)"); } }
Oftest har man data gemt i nogle variabler. Så skal man sætte en streng sammen, der giver den ønskede SQL-kommando:
String navn = "Hans"; int kredit = 500; // indsæt data fra variablerne navn og kredit stmt.executeUpdate("insert into KUNDER values('"+navn+"', "+kredit+")");
SQL-forespørgslen SELECT udføres med metoden executeQuery():
ResultSet rs = stmt.executeQuery("select NAVN, KREDIT from KUNDER");
Den giver et ResultSet-objekt, der repræsenterer svaret på forespørgslen (for at få alle kolonner kunne vi også skrive "select * from KUNDER"). Data hentes fra objektet således:
while (rs.next()) { String navn = rs.getString("NAVN"); double kredit = rs.getDouble("KREDIT"); System.out.println(navn+" "+kredit); }
Man kalder altså next() for at få næste række i svaret, læser de enkelte celler ud fra kolonnenavnene (eller kolonnenumrene, regnet fra 1 af), hvorefter man går videre til næste række med next() osv. Når next() returnerer false, er der ikke flere rækker at læse.
Det er en god idé at indkapsle databasearbejdet ét sted, f.eks. i en klasse, sådan at resten af programmet kan fungere, selvom databasens adresse eller struktur skulle ændre sig.
Ofte vil man have en klasse for hver tabel i databasen, sådan at hvert objekt kommer til at svare til en række. Herunder har vi lavet klassen Kunder, svarende til tabellen KUNDER:
public class Kunde { String navn; double kredit; public Kunde(String n, double k) { navn = n; kredit = k; } public String toString() { return navn+": "+kredit+" kr."; } }
Klassen, der varetager forbindelsen til databasen, bør have metoder, der svarer til de kommandoer og forespørgsler, resten af programmet har brug for. Hvis databasen ændrer sig, er det kun denne klasse, der skal rettes i:
import java.sql.*; import java.util.*; public class Databaseforbindelse { private Connection forb; private Statement stmt; public Databaseforbindelse() throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@oracle.cv.ihk.dk:1521:student","brugernavn","kode"); stmt = forb.createStatement(); } public void sletAlleData() throws SQLException { stmt.execute("truncate table KUNDER"); } public void opretTestdata() throws SQLException { try { // hvis tabellen allerede eksisterer opstår der en SQL-udtagelse stmt.executeUpdate( "create table KUNDER (NAVN varchar(32), KREDIT float)" ); } catch (SQLException e) { System.out.println("Kunne ikke oprette tabel: "+e); } stmt.executeUpdate("insert into KUNDER values('Jacob', -1799)"); stmt.executeUpdate("insert into KUNDER values('Brian', 0)"); } public void indsæt(Kunde k) throws SQLException { stmt.executeUpdate("insert into KUNDER (NAVN,KREDIT) values('" + k.navn + "', " + k.kredit + ")"); } public ArrayList hentAlle() throws SQLException { ArrayList alle = new ArrayList(); ResultSet rs = stmt.executeQuery("select NAVN, KREDIT from KUNDER"); while (rs.next()) { // brug kolonneindeks i stedet for kolonnenavn Kunde k = new Kunde( rs.getString(1), rs.getDouble(2)); alle.add(k); } return alle; } }
Klassen lader kalderen om at håndtere de mulige undtagelser. Det er fornuftigt, da det også er kalderen, der skal fortælle fejlen til brugeren og evt. beslutte, om programmet skal afbrydes.
Her er et program, der bruger Databaseforbindelse. Først opretter det forbindelsen og henter alle poster, dernæst sletter det alt og indsætter en enkelt post. Hvis der opstår en fejl, udskrives "Problem med database", og programmet afbrydes.
import java.util.*; public class BenytDatabaseforbindelse { public static void main(String[] arg) { try { Databaseforbindelse dbf = new Databaseforbindelse(); dbf.opretTestdata(); // fjern hvis tabellen allerede findes ArrayList l = dbf.hentAlle(); System.out.println("Alle data: "+l); dbf.sletAlleData(); dbf.indsæt( new Kunde("Kurt",1000) ); System.out.println("Alle data nu: "+ dbf.hentAlle()); } catch(Exception e) { System.out.println("Problem med database: "+e); e.printStackTrace(); } } }
Alle data: [Jacob: -1799.0 kr., Brian: 0.0 kr.] Alle data nu: [Kurt: 1000.0 kr.]
Ændr SimpeltDatabaseeksempel, så den også laver en SQL-forespørgsel.
Udvid Databaseforbindelse, så den kan søge efter en kunde ud fra kundens navn (antag, at navnet er en primærnøgle, så der ikke kan være flere kunder med samme navn).
Udvid Databaseforbindelse, så den kan give en liste med alle kunder med negativ kredit.
Lav et program, der holder styr på en musiksamling vha. en database. Databasen skal have tabellen UDGIVELSER med kolonnerne år, navn, gruppe og pladeselskab. Opret en tilsvarende klasse, der repræsenterer en Udgivelse (int år, String navn, String gruppe, String pladeselskab). Lav en passende Databaseforbindelse og et (evt. grafisk) program, der arbejder med musikdatabasen.
Ret databasen i forrige opgave til at have tabellen UDGIVELSER med kolonnerne år, navn og gruppeID, tabellen GRUPPER med kolonnerne gruppeID, navn, pladeselskab. Hvordan skal Databaseforbindelse ændres? Behøves der blive ændret i resten af programmet? Hvorfor?
Udvid programmet, så hver gruppe har en genre som f.eks. rock, tekno, klassisk (tabellen GRUPPER udvides med genreID, og tabellen GENRER med kolonnerne genreID og navn oprettes).