Kapitel 4 JDBC

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Mange applikationer arbejder på data. Disse data gemmes bedst i en database, og hvis man ikke selv har tid til at programmere et databasesystem, kan man bruge et af de kommercielle systemer, der allerede findes. Det giver blot det problem, at man ikke selv har programmeret interfacet til databasen, og man er således afhængig producentens information, når man skal skrive et program, der tilgår databasen. Når man har skrevet programmet, er det knyttet meget tæt til databasen. Hvis man senere finder ud af, at det er bedre at bruge en anden database, skal hovedparten af programmet sandsynligvis skrives om. Dette kan også være tilfældet, hvis man installerer en ny version af den samme database.

 

For at imødekomme dette problem blev ODBC-standarden defineret. Det er en standard, der gør, at man kan tilgå databaser fra forskellige leverandører på en ensartet måde. Fremgangsmåden er den, at man installerer nogle drivere på sin computer. Disse drivere sørger for at oversætte programmernes forespørgsler i ODBC-sprog til et sprog, databasen forstår.

 

I forhold til Java har ODBC imidlertid det problem, at det kræver, at der skal installeres platformsafhængigt software på brugerens computer. For at undgå dette blev JDBC – Java DataBase Connectivity – defineret. JDBC-standarden gør det muligt at tilgå en database, der understøtter JDBC, på en platformsuafhængig måde. Da JDBC blev udviklet, var det imidlertid klart, at det ikke var alle databaseproducenter, der havde mulighed for at understøtte standarden omgående. Derfor blev der udviklet forskellige grader af JDBC, hvor alle ikke behøver at være platformsuafhængige.

 

For at kunne bruge JDBC er det nødvendigt at installere JDBC-drivere til den database, man ønsker adgang til. Disse drivers kan dog skrives i Java og kan således være platformsuafhængig – det afhænger af JDBC-drivernes type.

 

Der findes følgende typer JDBC-drivere:

 

  • JDBC-ODBC-broen giver adgang fra JDBC til ODBC-databaser. Dette kræver, at der installeres ODBC-drivers på brugerens computer. Denne fremgangsmåde er ikke platformsuafhængig.
  • En native-API partiel Java-driver konverterer JDBC-kald direkte til kald i databasens API. Dette kræver, at der bliver installeret software fra producenten på brugerens computer.
  • En ren Java net-protokol konverterer JDBC-kald til en producentuafhængig netprotokol. Beskederne bliver af databaseserveren konverteret til databasens eget sprog.
  • En ren Java native-protokol konverterer JDBC-kald direkte til den protokol, databasen bruger.

 

 

I dette kapitel vil der blive brugt en JDBC-ODBC-bro til at skaffe adgang til en Microsoft Access database, men fremgangsmåden er naturligvis næsten den samme uafhængigt af, hvilken database man bruger. Da der i kapitlet vil blive skrevet forespørgsler og kommandoer i SQL (Structured Query Language), er det en fordel at have kendskab til dette sprog. Har man brug for at få det genopfrisket, kan man bruge appendiks B hertil. Ellers kan appendikset bruges som opslagsværk, når man selv formulerer SQL-forespørgsler.

 

Databasen

 

Den database, der bliver brugt i eksemplerne i dette kapitel, er som nævnt en Access-database. Denne database indeholder en tabel ved navn AdresseTabel. Nedenstående skema viser, hvilke attributter der findes i tabellen.

 

Navn Type
AdresselistID Langt heltal (auto-nummerering)
Fornavn Tekst
Efternavn Tekst
Adresse Tekst
Postnr Tekst
Bynavn Tekst
Telefon Tekst
Mobiltelefon Tekst

 

Driverne

 

Det første, man skal gøre, når man vil skrive en applikation, der benytter en database, er at finde de drivere, der skal bruges til at tilgå den pågældende database. De fleste databaseproducenter lægger driverne på deres hjemmeside, men de findes ofte i flere versioner. Valget står i disse tilfælde ofte imellem en version, der er platformsuafhængig og en, der ikke er. Ofte byder den ikke-uafhængige version på flere muligheder og højere hastighed end den uafhængige, og man må i så fald vurdere, hvor vigtigt platformsuafhængigheden er.

 

ODBC

 

Hvis man vælger at bruge en ODBC-JDBC-bro, skal man indstille sine ODBC-drivere korrekt. Fra Windows 95 gør man det fra det punkt, der hedder 32-bit ODBC, i Kontrolpanelet. Her skal man give den database, man vil anvende, et navn. Dette navn bruges fra de programmer, der tilgår databasen. Fordelen ved denne fremgangsmåde er, at programmerne er uafhængige af, hvor databasen er placeret – bare den er registreret med det samme navn.

 

Registreringen af databasen foregår under fanen System DSN,

 

hvor man skal klikke på knappen Tilføj. Som det fremgår af figur 4.1, bliver man nu præsenteret for en oversigt over alle de ODBC-drivere, der er installeret på computeren. I den næste dialogboks kan man angive datakildenavnet. Det er det navn, der skal benyttes fra ens programmer. Derudover skal man vælge, hvilken databasefil navnet skal referere til. Figur 4.2 viser, hvordan Access ODBC-driver er sat op til de eksempler, der bliver gennemgået i dette kapitel.

 

Opsætning af ODBC-drivere trin 1.

 

 

 

 

 

 

 

 

 

Opsætning af ODBC-drivere trin 2.

 

 

 

 

 

 

 

 

 

 

 

 

Forbindelsen

 

Selve applikationen skal oprette en forbindelse til databasen og herefter sende de SQL-kommandoer, der skal udføres. For at kunne oprette en forbindelse til databasen er det imidlertid et krav, at Java kender eksistensen af den driver, man vil bruge. Derfor skal driveren registreres. Det foretages af nedenstående kode:

 

try

{

Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

}

 

Sætningen Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”) indlæser klassen sun.jdbc.odbc.JdbcOdbcDriver, der er JDBC-driveren til JDBC-ODBC-broen.

 

Class.forName bliver beskrevet yderligere i kapitel Reflections i denne bog. I denne sammenhæng er det tilstrækkeligt at vide, at metodekaldet indlæser driveren, der automatisk registrerer sig selv.

 

Hvis man stræber efter en platformsuafhængig implementering med JDBC, skal man være forsigtig med at angive driverens klassenavn direkte i koden. Det er nemlig ikke alle drivere, der er platformsuafhængige. Emnet platformsuafhængighed beskrives nærmere i kapitlet 100% Pure Java senere i denne bog. På nuværende tidspunkt er det dog relevant at komme med nogle bemærkninger om, hvordan man kan sikre en platformsuafhængig implementering.

 

Det kan man eksempelvis gøre ved at lade brugeren selv vælge, hvilken driver der skal benyttes. Til dette formål skal man bruge en liste over de installerede JDBC-drivere. Denne liste stilles til rådighed af klassen DriverManager. Listen omfatter kun de drivere, der er registreret i manageren. Klassen DriverList viser, hvordan man kan få og udskrive en liste over de installerede drivere.

 

import java.sql.*;

import java.util.*;

 

public class DriverList

{

public static final void main (String args[])

{

try

{

Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);

/*

indlæs flere drivere

*/

}

catch (ClassNotFoundException cnfe)

{

}

Enumeration drivers = DriverManager.getDrivers();

 

while (drivers.hasMoreElements())

{

System.out.println(drivers.nextElement());

}

}

}

 

Metoden getDrivers returnerer et Enumeration-objekt. Dette objekt indeholder en række Driver-objekter. Driver er et interface, der er defineret således:

 

public abstract interface Driver extends java.lang.Object

{

public abstract boolean acceptsURL(java.lang.String);

public abstract java.sql.Connection connect(java.lang.String, java.util.Properties);

 

public abstract int getMajorVersion();

public abstract int getMinorVersion();

public abstract java.sql.DriverPropertyInfo[] getPropertyInfo(java.lang.String, java.util.Properties);

public abstract boolean jdbcCompliant();

}

 

Som det fremgår, kan man ved hjælp af Driver-objektet få en del oplysninger om den pågældende driver. Det gælder blandt andet et array af DriverPropertyInfo­-objekter, der er en række (navn, værdi)-par.

 

Selvom det kan være rart med informationer om de forskellige drivere, er det ikke direkte nødvendigt at kende til dem. Driver-manageren finder nemlig selv den driver, der passer bedst, når man beder den oprette en forbindelse.

 

Det er metoden getConnection, som opretter forbindelsen. Den findes i flere forskellige udgaver. Fælles for dem alle er det dog, at de skal bruge en URL som parameter. Denne defineres på en speciel måde.

 

Begrebet URL anvendes normalt i forbindelse med http-protokollen, men er også en del af JDBC-specifikationen. Her angiver URL’en en unik reference til en database. Alle URL’er i forbindelse med JDBC har følgende syntaks:

 

jdbc:underprotokol:database

 

Underprotokollen er specifik for den driver, der anvendes. Navnet på denne skal således findes i driverens dokumentation. Hvis man anvender den JDBC-ODBC-bro, der følger med Java Development Kit, er navnet på underprotokollen odbc.

 

Navnet på databasen er det navn, databasen er registreret som. Hvis man anvender ODBC er det det navn, man har angivet i ODBC-indstillingerne. Anvender man en JDBC-driver, der slutter sig direkte til en database, skal man angive navnet i databasen.

 

Som det ses af nedenstående signatur, indeholder DriverManager-klassen flere forskellige udgaver af getConnection-metoden. Klassen indeholder også en række metoder, der benyttes af JDBC-drivere.

 

public class DriverManager extends java.lang.Object

{

public static void deregisterDriver(java.sql.Driver);

public static synchronized java.sql.Connection getConnection(java.lang.String);

public static synchronized java.sql.Connection getConnection(java.lang.String, java.lang.String, java.lang.String);

public static synchronized java.sql.Connection getConnection(java.lang.String, java.util.Properties);

public static java.sql.Driver getDriver(java.lang.String);

public static java.util.Enumeration getDrivers();

public static java.io.PrintStream getLogStream();

public static java.io.PrintWriter getLogWriter();

public static int getLoginTimeout();

 

public static void println(java.lang.String);

public static synchronized void registerDriver(java.sql.Driver);

public static void setLogStream(java.io.PrintStream);

public static void setLogWriter(java.io.PrintWriter);

public static void setLoginTimeout(int);

}

 

Fælles for de tre udgaver af getConnection er det, at deres første parameter er en tekststreng indeholdende URL’en. Den udgave, der som parameter også tager et Properties-objekt, giver mulighed for at angive en række properties, der indeholder de drivere, driver-manageren kan vælge imellem, når forbindelsen skal oprettes. Den sidste udgave af getConnection tager tre tekststrenge som parametre – disse angiver henholdsvis URL, brugernavn og kodeord.

 

Metoderne registerDriver og deregisterDriver bruges af JDBC-drivere, når de henholdsvis bliver indlæst og fjernet fra hukommelsen.

 

I forbindelse med fejlfinding er metoden setLogWriter praktisk. Denne angiver det PrintStream-objekt, som status- og fejlmeddelelser udskrives til.

 

Når forbindelsen oprettes, returneres et Connection-objekt. Dette er en instans af en klasse, der implementeret Connection-interfacet, der er defineret således:

 

public abstract interface Connection extends java.lang.Object {

public abstract void clearWarnings();

public abstract void close();

public abstract void commit();

public abstract java.sql.Statement createStatement();

public abstract java.sql.Statement createStatement(int, int);

public abstract boolean getAutoCommit();

public abstract java.lang.String getCatalog();

public abstract java.sql.DatabaseMetaData getMetaData();

public abstract int getTransactionIsolation();

public abstract java.util.Map getTypeMap();

public abstract java.sql.SQLWarning getWarnings();

public abstract boolean isClosed();

public abstract boolean isReadOnly();

public abstract java.lang.String nativeSQL(java.lang.String);

public abstract java.sql.CallableStatement prepareCall(java.lang.String);

public abstract java.sql.CallableStatement prepareCall(java.lang.String, int, int);

public abstract java.sql.PreparedStatement prepareStatement(java.lang.String);

public abstract java.sql.PreparedStatement prepareStatement(java.lang.String, int, int);

public abstract void rollback();

public abstract void setAutoCommit(boolean);

public abstract void setCatalog(java.lang.String);

 

public abstract void setReadOnly(boolean);

public abstract void setTransactionIsolation(int);

public abstract void setTypeMap(java.util.Map);

}

 

 

De vigtigste metoder i interfacet er createStatement, commit og getMetaData. Metoden createStatement bruges til at sende en forespørgsel til databasen, mens commit udfører de forespørgsler, der er i databasens buffer. getMetaData returnerer information – metadata – om forbindelsen. Af de øvrige metoder i interfacet kan prepareCall og prepareStatement nævnes. Disse bruges til at lave stored procedures. Det er forespørgsler, der gemmes i kompileret form.

 

Metoden getMetaData returnerer et DatabaseMetaData-objekt. Dette objekt indeholder en lang række informationer om den database, der benyttes. Nogle af metoderne tager et eller flere mønstre som parameter. I et mønster angiver et procenttegn (%) enhver delstreng på 0 eller flere tegn, mens en understregning (_) angiver ét tegn. Hvis søgemønstret sættes til null, betyder det, at dette kriterium skal ignoreres ved søgningen. Nedenstående signatur viser et udsnit af metoderne i DatabaseMetaData-interfacet.

 

public abstract interface DatabaseMetaData extends java.lang.Object

{

public abstract java.sql.ResultSet getCatalogs();

public abstract java.sql.ResultSet getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String);

public abstract java.sql.Connection getConnection();

public abstract java.sql.ResultSet getCrossReference(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);

public abstract java.lang.String getDatabaseProductName();

public abstract java.lang.String getDatabaseProductVersion();

public abstract int getDefaultTransactionIsolation();

public abstract int getDriverMajorVersion();

public abstract int getDriverMinorVersion();

public abstract java.lang.String getDriverName();

public abstract java.lang.String getDriverVersion();

public abstract java.sql.ResultSet getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String);

public abstract java.sql.ResultSet getProcedureColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String);

public abstract java.sql.ResultSet getProcedures(java.lang.String, java.lang.String, java.lang.String);

public abstract java.sql.ResultSet getTableTypes();

public abstract java.sql.ResultSet getTables(java.lang.String, java.lang.String, java.lang.String, java.lang.String[]);

 

public abstract java.lang.String getURL();

public abstract java.lang.String getUserName();

public abstract boolean supportsExtendedSQLGrammar();

public abstract boolean supportsFullOuterJoins();

public abstract boolean supportsGroupBy();

public abstract boolean supportsGroupByBeyondSelect();

public abstract boolean supportsGroupByUnrelated();

public abstract boolean supportsLikeEscapeClause();

public abstract boolean supportsLimitedOuterJoins();

public abstract boolean supportsOuterJoins();

}

 

Som det ses, giver DatabaseMetaData-objektet mulighed for at finde ud af, hvilke tabeller og procedurer der findes i databasen, information om selve databaseprogrammet og -driveren, og også informationer om, hvilke funktioner der understøttes.

 

Forespørgslen

 

Databasens metadata kan således bruges til at danne sig et overblik over databasens opbygning, og den funktionalitet databasen stiller til rådighed. På den baggrund kan man formulere en forespørgsel i SQL. Det gør man ved hjælp af Connection­-objektets createStatement-metode. Der findes to udgaver af createStatement. Den ene tager ingen parametre, mens den anden tager to int-værdier, der henholdsvis angiver typen af det resultatsæt, forespørgslen genererer, og hvilke rettigheder, man har på resultatet.

 

Klassen ResultSet indeholder tre konstanter, der angiver resultatsættets type:

 

  • TYPE_FORWARD_ONLY
  • TYPE_SCROLL_INSENSITIVE
  • TYPE_SCROLL_SENSITIVE

 

ResultSet-klassen indeholder også to konstanter, som angiver rettigheder:

 

  • CONCUR_READ_ONLY
  • CONCUR_UPDATABLE

 

Hvis man kalder createStatement uden parametre, svarer det til kaldet createStatement (ResultSet.TYPE_FORWARD_ONLY, CONCUR_READ_ONLY).

 

Det Statement-objekt, der returneres af createStatement-kaldet, bruges både til at angive selve forespørgslen og til at opnå referencer til de resultater, forespørgslen genererer.

 

Nedenstående signatur viser de metoder, der findes i Statement-interfacet.

 

public abstract interface Statement extends java.lang.Object

{

public abstract void addBatch(java.lang.String);

public abstract void cancel();

 

public abstract void clearBatch();

public abstract void clearWarnings();

public abstract void close();

public abstract boolean execute(java.lang.String);

public abstract int[] executeBatch();

public abstract java.sql.ResultSet executeQuery(java.lang.String);

public abstract int executeUpdate(java.lang.String);

public abstract java.sql.Connection getConnection();

public abstract int getFetchDirection();

public abstract int getFetchSize();

public abstract int getMaxFieldSize();

public abstract int getMaxRows();

public abstract boolean getMoreResults();

public abstract int getQueryTimeout();

public abstract java.sql.ResultSet getResultSet();

public abstract int getResultSetConcurrency();

public abstract int getResultSetType();

public abstract int getUpdateCount();

public abstract java.sql.SQLWarning getWarnings();

public abstract void setCursorName(java.lang.String);

public abstract void setEscapeProcessing(boolean);

public abstract void setFetchDirection(int);

public abstract void setFetchSize(int);

public abstract void setMaxFieldSize(int);

public abstract void setMaxRows(int);

public abstract void setQueryTimeout(int);

}

 

Ved hjælp af et Statement-objekt kan man få en SQL-forespørgsel udført på flere forskellige måder.

  • Hvis man vil udføre en forespørgsel, der intet returnerer (det vil sige INSERT, UPDATE, DELETE eller lignende), skal man kalde executeUpdate.
  • Hvis man vil udføre en forespørgsel, der kun genererer et enkelt resultat, skal man kalde executeQuery.
  • Hvis man vil udføre en forespørgsel, der kan generere flere resultater, skal man kalde execute.
  • Hvis man vil udføre flere forespørgsler, benyttes addBatch til at tilføje de enkelte forespørgsler, og executeBatch til at udføre forespørgslerne.

 

Indsættelser

 

Den simpleste forespørgsel er en, der intet returnerer. Det vil sige indsættelse af en ny post, opdatering af en eksisterende post eller sletning af en post. Nedenstående eksempel viser, hvordan en ny post kan indsættes i databasen.

 

import java.sql.*;

 

public class SimpleQuery

{

public static final void main (String args[])

 

{

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

Statement s = c.createStatement();

s.executeUpdate (“INSERT INTO adressetabel (Fornavn, Efternavn, Adresse, Postnr, Bynavn, Telefon, Mobiltelefon) VALUES  (‘Jens’, ‘Jensen’, ‘Borgergade 7’, ‘DK7430’, ‘Ikast’, ‘97251234’, ‘20202020’)”);

System.out.println (“Person oprettet.”);

}

catch (SQLException se)

{

System.err.println (“Personen blev ikke oprettet”);

System.exit(2);

}

 

}

 

}

 

Søgninger med et enkelt resultatsæt

 

Der er imidlertid ikke meget ved at kunne tilføje poster i databasen, hvis man ikke kan hente dem igen. Til dette formål skal en af de mere avancerede execute-metoder anvendes. Klassen SimpleSearch viser, hvordan man kan foretage en SELECT-forespørgsel.

 

import java.sql.*;

 

public class SimpleSearch

{

public static final void main (String args[])

{

ResultSet r = null;

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

 

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

Statement s = c.createStatement();

r = s.executeQuery (“SELECT * FROM Adressetabel WHERE Fornavn = ‘Jens’;”);

 

}

catch (SQLException se)

{

System.err.println (“Forespørgslen kunne ikke udføres.”);

System.exit(2);

}

 

try

{

while (true) {

r.next();

System.out.println (“Fornavn:     ” + r.getString (“Fornavn”));

System.out.println (“Efternavn:   ” + r.getString (“Efternavn”));

System.out.println (“Adresse:     ” + r.getString (“Adresse”));

System.out.println (“Postnummer:  ” + r.getString (“Postnr”));

System.out.println (“By:          ” + r.getString (“Bynavn”));

System.out.println (“Telefon:     ” + r.getString (“Telefon”));

System.out.println (“Mobil:       ” + r.getString (“Mobiltelefon”));

System.out.println (“—-“);

}

}

catch (SQLException se)

{

}

 

Som det ses, returnerer executeQuery et objekt af klassen ResultSet. Denne klasse giver mulighed for at hente de forskellige oplysninger i databasen.

 

Et resultatsæt er bygget op som en tabel, hvor en markør – cursor – peger på en af rækkerne. Denne markør starter med at pege på posten før den første. Det vil sige en ikke-eksisterende post. Ved hjælp af metoder som next, first, last og lignende kan man flytter markøren. Det er denne fremgangsmåde,

 

der bruges i ovenstående eksempel.

 

For at få fat på indholdet af en af kolonnerne, kan man bruge en af ResultSet-klassens mange get-metoder. I dette eksempel er alle posterne af typen tekst, og det er derfor getString, der anvendes. Alle get-funktionerne i ResultSet findes i to varianter. Den ene tager navnet på kolonnen som parameter, mens den anden tager kolonnens position som parameter. Disse kan bruges til en mere dynamisk tilgang til databasen – det vil blive gennemgået senere i dette kapitel.

 

Nedenstående signatur, viser ResultSet-klassens metoder:

 

public abstract interface ResultSet extends java.lang.Object

{

public abstract boolean absolute(int);

public abstract void afterLast();

public abstract void beforeFirst();

public abstract void close();

public abstract boolean first();

public abstract java.sql.Array getArray(java.lang.String);

public abstract java.math.BigDecimal getBigDecimal(int, int);

public abstract java.math.BigDecimal getBigDecimal(java.lang.String);

public abstract java.math.BigDecimal getBigDecimal(java.lang.String, int);

public abstract boolean getBoolean(java.lang.String);

public abstract byte getByte(java.lang.String);

public abstract byte[] getBytes(java.lang.String);

public abstract int getConcurrency();

public abstract java.sql.Date getDate(int, java.util.Calendar);

public abstract java.sql.Date getDate(java.lang.String);

public abstract java.sql.Date getDate(java.lang.String, java.util.Calendar);

public abstract double getDouble(java.lang.String);

public abstract float getFloat(java.lang.String);

public abstract int getInt(java.lang.String);

public abstract long getLong(java.lang.String);

public abstract java.sql.ResultSetMetaData getMetaData();

public abstract java.lang.Object getObject(int, java.util.Map);

public abstract java.lang.Object getObject(java.lang.String);

public abstract java.lang.Object getObject(java.lang.String, java.util.Map);

public abstract int getRow();

public abstract short getShort(java.lang.String);

public abstract java.sql.Statement getStatement();

public abstract java.lang.String getString(int);

public abstract java.lang.String getString(java.lang.String);

public abstract java.sql.Time getTime(int, java.util.Calendar);

 

public abstract java.sql.Time getTime(java.lang.String);

public abstract java.sql.Time getTime(java.lang.String, java.util.Calendar);

public abstract java.sql.SQLWarning getWarnings();

public abstract void insertRow();

public abstract boolean isAfterLast();

public abstract boolean isBeforeFirst();

public abstract boolean isFirst();

public abstract boolean isLast();

public abstract boolean last();

public abstract boolean next();

public abstract boolean previous();

public abstract void refreshRow();

public abstract boolean relative(int);

public abstract boolean rowDeleted();

public abstract boolean rowInserted();

public abstract boolean rowUpdated();

public abstract void setFetchDirection(int);

public abstract void setFetchSize(int);

public abstract void updateBigDecimal(java.lang.String, java.math.BigDecimal);

public abstract void updateBoolean(java.lang.String, boolean);

public abstract void updateByte(java.lang.String, byte);

public abstract void updateBytes(java.lang.String, byte[]);

public abstract void updateDate(java.lang.String, java.sql.Date);

public abstract void updateDouble(java.lang.String, double);

public abstract void updateFloat(java.lang.String, float);

public abstract void updateInt(java.lang.String, int);

public abstract void updateLong(java.lang.String, long);

public abstract void updateNull(java.lang.String);

public abstract void updateObject(int, java.lang.Object, int);

public abstract void updateObject(java.lang.String, java.lang.Object);

public abstract void updateObject(java.lang.String, java.lang.Object, int);

public abstract void updateRow();

public abstract void updateShort(java.lang.String, short);

public abstract void updateString(java.lang.String, java.lang.String);

public abstract void updateTime(java.lang.String, java.sql.Time);

public abstract void updateTimestamp(java.lang.String, java.sql.Timestamp);

public abstract boolean wasNull();

}

 

 

Det skal bemærkes, at det ikke er alle drivere, der understøtter alle funktioner. I forbindelse med JDBC-ODBC-broer skal man være opmærksom på, at JDBC-driveren godt kan understøtte en funktion, som ODBC-driveren ikke understøtter.

 

Søgninger med flere resultatsæt

 

Hvis en forespørgsel kan medføre flere resultatsæt, skal man som nævnt benytte metoden execute. Denne returnerer en boolean, der angiver, om der blev oprettet resultatsæt eller ej. Hvis der blev oprettet resultatsæt, kan man få en reference til det første ved at kalde Statement-objektets getResultSet-metode. Når man vil have en reference til det næste, kalder man først metoden hasMoreResults. Hvis denne returnerer true har forespørgslen genereret flere resultatsæt, og man kan få en reference til den næste ved endnu en gang at kalde getResultSet.

 

Nedenstående eksempel viser, hvordan man kan iterere gennem en række resultatsæt. Eksemplet er mangelfuldt i den forstand, at det forventes, at alle resultatsæt er ens. Hvordan man håndterer forskellige resultatsæt på den samme måde, bliver beskrevet i afsnittet om dynamisk adgang til databasen. Derudover skal det nævnes, at eksemplet kun genererer et enkelt resultatsæt.

 

import java.sql.*;

 

public class MultiSearch

{

public static final void main (String args[])

{

int resultCount = 0;

Statement s = null;

 

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

s = c.createStatement();

if (!s.execute (“SELECT * FROM Adressetabel WHERE Fornavn = ‘Jens’;”)) {

// Der blev ikke oprettet resultatsæt

System.err.println (“Søgningen gav intet resultat.”);

 

System.exit(3);

}

 

}

catch (SQLException se)

{

System.err.println (“Forespørgslen kunne ikke udføres.”);

System.exit(2);

}

 

 

try

{

do

{

ResultSet r = null;

try

{

r = s.getResultSet();

}

catch (SQLException se)

{

}

 

try

{

while (true) {

r.next();

System.out.println (“Fornavn:     ” + r.getString (“Fornavn”));

System.out.println (“Efternavn:   ” + r.getString (“Efternavn”));

System.out.println (“Adresse:     ” + r.getString (“Adresse”));

System.out.println (“Postnummer:  ” + r.getString (“Postnr”));

System.out.println (“By:          ” + r.getString (“Bynavn”));

System.out.println (“Telefon:     ” + r.getString (“Telefon”));

System.out.println (“Mobil:       ” + r.getString (“Mobiltelefon”));

System.out.println (“—-“);

}

}

catch (SQLException se)

{

}

try

{

s.getMoreResults();

}

 

catch (SQLException se)

{

}

}

while (s.getMoreResults());

}

catch (SQLException se)

{

}

}

 

}

 

Flere samtidige forespørgsler

 

Hvis man skal foretage flere forespørgsler hurtigt efter hinanden, kan man med fordel samle dem. Det gør man ved at benytte addBatch-metoden til at tilføje en forespørgsel og executeBatch til at udføre alle forespørgslerne. Returværdien fra executeBatch er et array af int, der for hver forespørgsel angiver, hvor mange elementer som er blevet opdateret.

 

Nedenstående eksempler samler tre indsættelser og udfører dem. Bemærk, at Access ikke understøtter addBatch .

 

import java.sql.*;

 

public class BatchInsert

{

public static final void main (String args[])

{

 

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

Statement s = c.createStatement();

 

s.addBatch (“INSERT INTO (Fornavn, Efternavn) VALUES (‘Søren’, ‘Hansen’);”);

s.addBatch (“INSERT INTO (Fornavn, Efternavn) VALUES (‘Niels’, ‘Petersen’);”);

s.addBatch (“INSERT INTO (Fornavn, Efternavn) VALUES (‘Anders’, ‘Sørensen’);”);

 

 

int update[] = s.executeBatch();

 

for (int i = 0; i < update.length; i++)

{

System.out.println (“Forespørgsel ” + (i+1) + ” medførte ” + update[i] + ” ændring(er)”);

}

}

catch (SQLException se)

{

System.err.println (“Forespørgslerne kunne ikke udføres.”);

System.exit(1);

}

}

}

 

Dynamisk adgang til databasen

 

De eksempler, der er blevet gennemgået tidligere i dette kapitel, har alle taget udgangspunkt i, at opbygningen af databasen var kendt. Dette er imidlertid ikke altid tilfældet, og derfor vil dette afsnit kigge nærmere på en mere dynamisk tilgang til databasen.

 

SQL-sproget forudsætter, at man kender navnene på de tabeller, man vil arbejde på. Da JDBC i så høj grad er knyttet sammen med SQL, er denne forudsætning svær at ændre. Derfor må man vælge en anden fremgangsmåde. Det, man har valgt at gøre, er at lade informationer om opbygningen af databasen være tilgængelig. Disse informationer kaldes metadata. Der findes to forskellige sæt metadata. Det drejer sig om metadata for selve databasen og metadata for de enkelte resultatsæt. Begge dele vil blive beskrevet i dette afsnit.

 

Metadata om databasen

 

Metadata om selve databasen kan findes ved hjælp af Connection-klassens metode getMetaData. Denne returnerer et DatabaseMetaData-objekt. DatabaseMetaData-klassen er beskrevet tidligere i dette kapitel.

 

Selvom JDBC definerer en ensartet adgang til databaser, skal man være opmærksom på, at det ikke er alle databaser og drivere, der understøtter al funktionalitet. Selvom DatabaseMetaData-klassen indeholder en lang række metoder til at hente information om databasen, er det således ikke sikkert, at disse kan kaldes. Forsøger man at kalde en metode, som driveren ikke implementerer, opstår en SQLException.

 

Hvis man ved, hvilke drivere der understøtter hvilken funktionalitet, kan man bruge metoderne getDriverName og getDriverVersion. Er man interesseret i, hvilken database der forbindes til, kan man bruge getDatabaseProductName og getDatabaseProductVersion. Nedenstående eksempel viser, hvordan man kan udskrive information om den forbindelse, der bruges

 

i dette kapitels eksempler:

 

import java.sql.*;

 

public class DatabaseInformation

{

public static final void main (String args[])

{

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

DatabaseMetaData dbmd = c.getMetaData();

 

// Udskriv informationer om databasen

 

System.out.println (“Database: ” + dbmd.getDatabaseProductName() + ” version ” + dbmd.getDatabaseProductVersion());

System.out.println (“Driver:   ” + dbmd.getDriverName() + ” version ” + dbmd.getDriverVersion());

}

catch (SQLException se)

{

se.printStackTrace();

}

}

}

 

Hvis man skal bruge versionsnumrene til sammenligning i stedet for blot udskrift, kan man med fordel benytte metoderne getDriverMajorVersion og getDriverMinorVersion. Disse returnerer int-værdier i stedet for tekststrenge.

 

Hvis driveren understøtter det, kan man få en oversigt over de kataloger, databasen indeholder. Det sker ved hjælp af metoden getCatalogs. Denne metode returnerer et ResultSet-objekt, der behandles ligesom de øvrige resultatsæt. Resultatsættet indeholder navnene de forskellige kataloger i databasen sorteret i stigende alfabetisk orden. Den kolonne, der indeholder katalognavnene, hedder TABLE_CAT. Metoden getTables, der også returnerer et resultatsæt, kan bruges til at få navnene på tabellerne i et bestemt katalog.

 

 

Det er imidlertid ikke altid nok at have navnene på tabellerne. Det kunne også være rart at vide, hvilken type de forskellige tabeller har. Disse oplysninger giver metoden getTableTypes. Denne metode returnerer også et resultatsæt. Den eneste kolonne i resultatsættet har navnet TABLE_TYPE. Hver indgang har typisk en af følgende værdier:

 

  • TABLE
  • VIEW
  • SYSTEM TABLE
  • GLOBAL TEMPORARY
  • LOCAL TEMPORARY
  • ALIAS
  • SYNONYM.

 

Når man har navnet på en tabel, behøver man blot navnene på tabellens kolonner for at kunne udforme en SQL-forespørgsel. Disse navne returneres i et resultatsæt af metoden getColumns.

 

Metadata om resultatsæt

 

Når man kan få alle de informationer, man har behov for, fra DatabaseMetaData-objektet, kan det være svært at se, hvad man skal bruge resultatsættets metadata til. Man kan imidlertid forestille sig en metode, der udskriver et resultatsæt. Ved at anvende resultatsættets metadata behøver metoden ikke kende noget til resultatsættet – alle oplysninger ligger i selve resultatsættet.

 

Nedenstående klasse viser, hvordan en sådan metode kan implementeres.

 

import java.sql.*;

 

public class Udskrift

{

public static void udskrivResultat (ResultSet r) throws SQLException

{

ResultSetMetaData metaData = r.getMetaData();

int antalKolonner = metaData.getColumnCount();

 

String kolonner[] = new String [antalKolonner+1];

 

for (int i = 1; i <= antalKolonner; i++)

{

kolonner[i] = metaData.getColumnLabel (i);

}

 

try

{

r.next();

for (int i = 1; i <= antalKolonner; i++)

{

System.out.println (kolonner[i] + ” : ” + r.getString (i));

 

}

}

catch (SQLException se)

{

}

}

}

 

Som det ses af ovenstående eksempel, er det metoden getMetaData i ResultSet-klassen, der returnerer resultatsættets metadata. Dette sker i et ResultSetMetaData-objekt. ResultMetaData er et interface, og dettes signatur ses herunder.

 

public abstract interface ResultSetMetaData extends java.lang.Object

{

public abstract java.lang.String getCatalogName(int);

public abstract java.lang.String getColumnClassName(int);

public abstract int getColumnCount();

public abstract int getColumnDisplaySize(int);

public abstract java.lang.String getColumnLabel(int);

public abstract java.lang.String getColumnName(int);

public abstract int getColumnType(int);

public abstract java.lang.String getColumnTypeName(int);

public abstract int getPrecision(int);

public abstract int getScale(int);

public abstract java.lang.String getSchemaName(int);

public abstract java.lang.String getTableName(int);

public abstract boolean isAutoIncrement(int);

public abstract boolean isCaseSensitive(int);

public abstract boolean isCurrency(int);

public abstract boolean isDefinitelyWritable(int);

public abstract int isNullable(int);

public abstract boolean isReadOnly(int);

public abstract boolean isSearchable(int);

public abstract boolean isSigned(int);

public abstract boolean isWritable(int);

}

 

Metoden getColumnCount bruges til at finde ud af, hvor mange kolonner der er i resultatsættet. Dette antal behøver ikke nødvendigvis at svare til antallet af kolonner i en tabel, da flere tabeller kan kombineres i en forespørgsel, ligesom det er muligt at udvælge enkelte kolonner.

 

Antallet af kolonner bruges i eksemplet til at oprette et array, der indeholder beskrivende tekst til kolonnerne. Denne tekst hentes ved hjælp af metoden getColumnLabel. Eneste parameter til denne metode er kolonnens indeks. Bemærk, at i modsætning til indeks i eksempelvis arrays, starter dette indeks ved 1.

 

Den beskrivende tekst, der er tilknyttet de forskellige kolonner, behøver ikke være den samme, som navnet på kolonnerne. Hvis man vil have navnet på en kolonne, skal man bruge metoden getColumnName.

 

 

Resten af metoden i eksemplet gennemløber blot resultatsættet og udskriver kolonnernes beskrivelse. I forhold til søgeeksemplet tidligere kan det bemærkes, at teksterne udskrives ved hjælp af den getString-metode, der arbejder på en int-værdi. Man kunne naturligvis have slået hver enkelt kolonnenavn op ved hjælp af getColumnName og derefter have brugt den vanlige getString-metode.

 

Faktisk er det ikke helt korrekt at benytte getString. Man bør naturligvis benytte den get-metode, der svarer til kolonnens type. Heldigvis indeholder metadataene også information om, hvilken type en given kolonne har. Disse informationer hentes gennem metoderne getColumnType og getColumnTypeName, der henholdsvis returnerer en int og en String, der angiver kolonnens type.

 

Talværdien vil svare til en af de konstanter, der er sat i klassen java.sql.Types. Nedenstående liste viser de forskellige værdier, getColumnType kan returnere.

 

  • ARRAY
  • BIGINT
  • BINARY
  • BIT
  • BLOB
  • CHAR
  • CLOB
  • DATE
  • DECIMAL
  • DISTINCT
  • DOUBLE
  • FLOAT
  • INTEGER
  • JAVA_OBJECT
  • LONGVARBINARY
  • LONGVARCHAR
  • NULL
  • NUMERIC
  • OTHER
  • REAL
  • REF
  • SMALLINT
  • STRUCT
  • TIME
  • TIMESTAMP
  • TINYINT
  • VARBINARY
  • VARCHAR

 

Nedenstående klasse viser, hvordan man kan benytte Udskriv-klassen til at gøre udskrivningen af dataene fra et tidligere eksempel simplere.

 

 

import java.sql.*;

 

public class UdskrivSearch

{

public static final void main (String args[])

{

ResultSet r = null;

try

{

Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“JDBC-ODBC driver kunne ikke findes.”);

System.exit(1);

}

try

{

Connection c = DriverManager.getConnection (“jdbc:odbc:adresser”);

Statement s = c.createStatement();

r = s.executeQuery (“SELECT * FROM Adressetabel WHERE Fornavn = ‘Jens’;”);

Udskrift.udskrivResultat (r);

}

catch (SQLException se)

{

System.err.println (“Forespørgslen kunne ikke udføres.”);

se.printStackTrace();

System.exit(2);

}

}

}

 

Sammenfatning

I dette kapitel er JDBC blevet gennemgået. Med udgangspunkt i en Access-database er det blevet vist, hvordan man kan foretage indsættelser, søgninger med mere gennem JDBC. Det er også blevet forklaret, hvordan man kan gøre forholdet mellem ens program og databasen mindre konkret ved hjælp af en mere dynamisk tilgang til databasen.

FAQ

Hvorfor opstår en java.lang.UnsupportedOperationException? Jeg er sikker på, at jeg bruger metoderne, som man skal.

 

En java.lang.UnsupportedOperationException opstår, når man forsøger at benytte en funktion eller funktionalitet, som enten ikke understøttes af driveren eller af databasen.

 

Kan man bruge JDBC-ODBC-broen fra Java-appletter?

Hvis man bruger JDBC-ODBC-broen fra en applet, som brugeren ikke har markeret som trusted fra en webbrowser er ikke muligt. Dette skyldes sikkerhedsforanstaltninger, der gør, at farlige appletter ikke skal få adgang til den native kode, der ligger i ODBC, og som Java ikke kan kontrollere.

 

Hvis man ønsker at bruge JDBC fra en applet, skal man bruge en ren Java JDBC-driver.

 

Man kan også vælge at afvikle appletten i appletfremviseren. Denne forudsætter nemlig altid, at appletter er trusted.

 

Endeligt kan man vælge at bruge HotJava-browseren, da denne giver mulighed for at slå sikkerhedssystemet omkring appletter fra.

 

Kan man bruge JDBC fra JDK version 1.0.2 ?

 

Ja. Man kan downloade en version 1.22 af JDBC fra Javasofts hjemmeside.

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Comments are closed.