8.1 Basisfunktioner i JDBC 130
8.1.1 Indlæse driveren 130
8.1.2 Etablere forbindelsen 130
8.1.3 Kommunikere med databasen 131
8.2 Forpligtigende eller ej? (commit) 132
8.3 Optimering 133
8.3.1 Bruge den rigtige databasedriver 133
8.3.2 Lægge opdateringer i kø (batch-opdateringer) 133
8.3.3 På forhånd forberedt SQL 134
8.3.4 Kalde gemte procedurer i databasen 135
8.4 Avanceret 136
8.4.1 Opdatering og navigering i ResultSet-objekter 136
8.4.2 Metadata 136
8.4.3 Metadata om databasen 136
8.4.4 Metadata om svaret på en forespørgsel 136
8.4.5 Eksempel - udskrive en vilkårlig tabel 137
8.4.6 Persistering af objekter - JDO 138
Dette kapitel forudsætter lidt kendskab til databaser og databasesproget SQL (Structured Query Language), og at man har en fungerende database, som man ønsker adgang til fra Java.
Et program, der bruger JDBC, skal basalt set udføre følgende handlinger: Indlæse driveren, etablere forbindelsen og dernæst kommunikere med databasen.
Det gøres ved at indlæse en driver-klasse:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Denne klasse sørger for at registrere sig hos java.sql.DriverManager.
Med Java under Windows følger en standard JDBC-ODBC-bro med, så man kan kontakte alle datakilder, der er defineret under ODBC.
Er det en anden database, skal man have en jar-fil (et Java-ARkiv, en samling klasser pakket i zip-formatet) med en driver fra producenten, og man skal indlæse en anden driver-klasse fra jar-filen (der skal ligge i CLASSPATH).
De nyeste drivere kan findes på http://java.sun.com/jdbc, men følger ofte med når man anskaffer sig en database.
For at få indlæst en driver til en Oracle-database skriver man:
Class.forName("oracle.jdbc.driver.OracleDriver");
Driver-filen, der følger med Oracles produkter, hedder typisk classes12.zip.
For at få indlæst en driver til en MySQL-database skriver man:
Class.forName("com.mysql.jdbc.Driver")
Driver-filen til MySQL (kaldet Connector/J) kan hentes på http://mysql.com.
Herefter kan man oprette forbindelsen med (for en ODBC-driver):
Connection forb = DriverManager.getConnection("jdbc:odbc:datakilde1");
Parameteren er en URL til databasen, som driver-klassen genkender. Husk, at datakildens navn (her "datakilde1") først skal være defineret i Windows' Kontrolpanel under ODBC.
Er det en Oracle-database, kunne man skrive:
Connection forb = DriverManager.getConnection(
"jdbc:oracle:thin:@ora.javabog.dk:1521:student","jacob","jacob");
Hvorefter man skulle have oprettet forbindelsen til databasen 'student' på maskinen 'ora.javabog.dk' port 1521 med brugernavn 'jacob' og adgangskode 'jacob'.
Er det en MySQL-database, kunne man skrive:
Connection c = DriverManager.getConnection("jdbc:mysql:///jacob","root","xyz");
Hvorefter man skulle have oprettet forbindelsen til databasen 'jacob' på den lokale maskine med brugernavn 'root' og adgangskode 'xyz'.
Når man har oprettet en forbindelse, kan man oprette et Statement-objekt, som man kan sende kommandoer og forespørgsler til databasen med (svarer til en SQL-kommandolinje):
Statement stmt = forb.createStatement();
Her opretter vi f.eks. tabellen KUNDER og indsætter et par rækker:
import java.sql.*; public class Databasekommunikation { public static void main(String[] arg) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); System.out.println("Driver indlæst"); Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@ora.javabog.dk:1521:student","jacob","jacob"); System.out.println("Forbindelse oprettet"); Statement stmt = forb.createStatement(); // forsøg at slette tabel - hvis den ikke findes opstår en fejl som fanges try { stmt.executeUpdate("drop table KUNDER"); } catch (Exception e) {} // opret tabel stmt.executeUpdate("create table KUNDER (NAVN varchar(32), KREDIT number)"); System.out.println("Tabel oprettet"); // tilføj data stmt.executeUpdate("insert into KUNDER values('Jacob', -1799)"); // brug helst navngivne kolonner. Hvis man senere skulle finde på at // udvide tabellen med flere kolonner, vil SQL-kommandoen stadig virke! stmt.executeUpdate("insert into KUNDER(NAVN,KREDIT) values('Brian', 0)"); // indsæt data fra variabler String navn = "Hans"; double kredit = 500; stmt.executeUpdate( "insert into KUNDER(NAVN,KREDIT) values('"+navn+"', "+kredit+")"); // forespørgsler ResultSet rs = stmt.executeQuery("select NAVN, KREDIT from KUNDER"); while (rs.next()) { navn = rs.getString("NAVN"); kredit = rs.getDouble("KREDIT"); System.out.println(navn+" "+kredit); } } }
Driver indlæst Forbindelse oprettet Tabel oprettet Jacob -1799.0 Brian 0.0 Hans 500.0
Se også afsnit 12.8 for en beskrivelse af alle klasserne, der er til rådighed til kommunikation med en database.
Normalt er alle SQL-kommandoer gennem JDBC automatisk forpligtigende (eng. auto-committing), dvs. de kan ikke annulleres, hvis en senere SQL-kommando går galt.
Vil man slå automatisk forpligtigelse fra, kan det gøres ved at kalde setAutoCommit() på forbindelsen. Derefter vil transaktioner (SQL-kommandoer) ikke være forpligtigende, og de kan annulleres ved at kalde rollback() på forbindelsen. Når man ønsker, at transaktionerne skal træde i kraft, kalder man commit(), og først herefter er transaktionerne endeligt udført på databasen og dermed forpligtigende.
Oftest anvendes denne facilitet i forbindelse med flere transaktioner, der enten alle skal gennemføres eller alle annulleres, f.eks.:
import java.sql.*; public class UdenAutocommit { public static void main(String[] arg) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@ora.javabog.dk:1521:student","jacob","jacob"); try { forb.setAutoCommit(false); Statement stmt = forb.createStatement(); stmt.executeUpdate("insert into KUNDER(NAVN,KREDIT) values('Jacob',-17)"); stmt.executeUpdate("insert into KUNDER(NAVN,KREDIT) values('Brian', 0)"); // flere transaktioner ... System.err.println("Alt gik godt, gør ændringerne forpligtigende"); forb.commit(); } catch (Exception e) { e.printStackTrace(); System.err.println("Noget gik galt! Annullerer ændringerne..."); forb.rollback(); } finally { // Husk at sætte auto-commit tilbage, af hensyn til andre transaktioner forb.setAutoCommit(true); } } }
Alt gik godt, gør ændringerne forpligtigende
Ønsker man hurtigere kommunikation med databasen, er der en række ting, man kan gøre.
JDBC-drivere findes i fire typer:
Type 1: JDBC-ODBC-broen. Dette er den langsomste, da den kører gennem ODBC, og den er dermed platformsspecifik (hovedsageligt til Windows).
Type 2: Drivere, hvor JDBC-laget kalder funktioner skrevet til i f.eks. maskinkode, C eller C++ til den specifikke platform (normalt den hurtigste).
Type 3: Platformsuafhængig (ren Java-) driver, der benytter en databaseuafhængig kommunikationsprotokol til at kommunikere med en server, som så kommunikerer videre med den specifikke database. Denne type driver giver stor fleksibilitet.
Type 4: Platformsuafhængig (ren Java-) driver, der er skrevet til at kommunikere via en kommunikationsprotokol med en specifik database.
De hurtigste drivere er type 2, men ofte kan en type 4 driver blive næsten lige så hurtig. En liste med over hundrede tilgængelige drivere kan findes på http://java.sun.com/jdbc.
Skal man lave mange opdateringer, kan det være en fordel at sende dem af sted som én samlet enhed (eng.: batch) i stedet for som normalt at vente på, at hver opdatering skal gennemføres, før den næste kan sendes.
import java.sql.*; public class Batchopdateringer { public static void main(String[] arg) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@ora.javabog.dk:1521:student","jacob","jacob"); Statement stmt = forb.createStatement(); stmt.addBatch("insert into KUNDER(NAVN,KREDIT) values('Jacob', -1799)"); stmt.addBatch("insert into KUNDER(NAVN,KREDIT) values('Brian', 0)"); // send først nu ændringer til databasen stmt.executeBatch(); } }
Det er klart, at der i koden vist ovenfor bruges noget tid på at fortolke SQL-kommandoen hver gang kaldet foretages.
I stedet for at oprette en SQL-kommandolinje med createStatement(), kan man bruge metoden prepareStatement(), hvor man angiver SQL-kommandoen én gang under opstart af programmet, og derefter kan udføre kommandoen flere gange.
import java.sql.*; public class ForberedtSQL { public static void main(String[] arg) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection forb = DriverManager.getConnection( "jdbc:oracle:thin:@ora.javabog.dk:1521:student","jacob","jacob"); // Forbered kommandoerne til databasen i starten af programmet: PreparedStatement indsæt = forb.prepareStatement( "insert into KUNDER(NAVN,KREDIT) values(?, ?)"); PreparedStatement hent = forb.prepareStatement( "select NAVN, KREDIT from KUNDER"); // under programudførelsen kan forberedte kommandoer udføres mange gange: indsæt.setString(1, "Jacob"); indsæt.setInt(2, -1799); indsæt.execute(); indsæt.setString(1, "Brian"); indsæt.setInt(2, 0); indsæt.execute(); // som før kan opdateringerne også lægges i kø: indsæt.setString(1, "Hans"); indsæt.setInt(2, 142); indsæt.addBatch(); indsæt.setString(1, "Grethe"); indsæt.setInt(2, 242); indsæt.addBatch(); // send ændringer til databasen indsæt.executeBatch(); ResultSet rs = hent.executeQuery(); // man løber igennem svaret som man plejer while (rs.next()) { String navn = rs.getString(1); double kredit = rs.getDouble(2); System.out.println(navn+" "+kredit); } } }
Jacob -1799.0 Brian 0.0 Hans 142.0 Grethe 242.0
Større databaser understøtter 'stored procedures' - procedurer gemt i databasen. Disse procedurer kan udføres hurtigere, da databasen på forhånd kan optimere, hvordan SQL-kaldene skal foregå.
En gemt procedure kan kaldes med et CallableStatement (her forestiller vi os, at der på forhånd er oprettet procedurerne indsaetkunde og hentkunder i databasen):
CallableStatement indsætP = forb.prepareCall("call indsaetkunde(?, ?)");
CallableStatement hentP = forb.prepareStatement("?= hentkunder");
Resten af arbejdet foregår som med PreparedStatement:
indsætP.setString(1, "Jacob"); indsætP.setInt(2, -1799); indsætP.execute(); indsætP.setString(1, "Brian"); indsætP.setInt(2, 0); indsætP.execute(); ResultSet rs = hentP.executeQuery(); ...