19 JDBC - databaseadgang

Indhold:



Kapitlet forudsættes ikke i resten af bogen.

Det forudsætter 13, Undtagelser, og kendskab til databaser og 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.*;

19.1 Kontakt til databasen

At få kontakt til databasen er måske det sværeste skridt. Det består af to led:

  1. Indlæse driveren

  2. 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. De nyeste drivere kan findes på http://www.javasoft.com under 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", "stuk1001", "hemli'");


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.

19.1.1 JDBC-ODBC-broen under Windows

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");


Før du kan oprette forbindelsen, skal datakilden først være oprettet under ODBC i Windows' Kontrolpanel. Man angiver datakildens navn (her "datakilde1"), når forbindelsen oprettes, uden brugernavn og adgangskode:


  Connection forb = DriverManager.getConnection ("jdbc:odbc:datakilde1");


19.2 Kommunikere med databasen

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.

19.2.1 Kommandoer

SQL-kommandoer som INSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE og ALTER TABLE, der ikke giver et svar tilbage i form af data, sendes med executeUpdate()-metoden1.

Her opretter vi f.eks. tabellen "kunder":


stmt.executeUpdate("create table KUNDER (NAVN varchar(32), KREDIT float)" );


... og indsætter et par rækker


stmt.executeUpdate("insert into KUNDER values('Jacob', -1799)");
stmt.executeUpdate("insert into KUNDER values('Brian', 0)");


Oftest har vi data i variabler, så vi skal sætte en streng sammen, der giver den rigtige SQL-kommando:


String navn = "Hans";
int kredit = 500;

// indsæt data fra variabler
stmt.executeUpdate("insert into KUNDER values('"+navn+"', "+kredit+")");


19.2.2 Forespørgsler

SQL-forespørgslen SELECT udføres med metoden executeQuery().


  ResultSet rs = stmt.executeQuery("select NAVN, KREDIT from KUNDER");


Den returnerer et ResultSet-objekt, der repræsenterer svaret på forespørgslen. Data hentes som vist herunder


  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, 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.


19.3 Samlet eksempel

Det er en god ide at indkapsle databasekommunikationen é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 per tabel i databasen, sådan at et objekt kommer til at svare til en række2:


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.


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","stuk1001","hemli'");
    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 Vector hentAlle() throws SQLException
  {
    Vector alle = new Vector();
    ResultSet rs = stmt.executeQuery("select NAVN, KREDIT from KUNDER");
    while (rs.next())
    {
      Kunde k = new Kunde( rs.getString("NAVN"), rs.getDouble("KREDIT"));
      alle.addElement(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
      Vector  v = dbf.hentAlle();
      System.out.println("Alle data: "+v);
      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.]


19.4 Opgaver

  1. 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).

  2. Udvid Databaseforbindelse, så den kan give en liste med alle kunder med negativ kredit.

  3. 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.

  4. 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?

  5. 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).


1SQL fra JDBC er normalt forpligtigende, d.v.s. en kommando kan ikke fortrydes når den først er givet. I Avancered-afsnittet i slutningen af kapitlet vises hvordan automatisk forpligtigelse (eng.: auto-commit) kan slås fra.

2Det kommer lidt an på, i hvor høj grad basen er normaliseret.


Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk