8. JDBC-programmering med tilrettelegging for webapplikasjoner



Like dokumenter
Videregående programmering 6

Klasser skal lages slik at de i minst mulig grad er avhengig av at klienten gjør bestemte ting STOL ALDRI PÅ KLIENTEN!

HØGSKOLEN I SØR-TRØNDELAG

HØGSKOLEN I SØR-TRØNDELAG

Å bruke Java API-et til å sortere tabeller/arraylister der elementene er (referanser til) objekter

HØGSKOLEN I SØR-TRØNDELAG

1. SQL datadefinisjon og manipulering

Transaksjoner og flerbrukerproblematikk. Transaksjoner

Hva er Derby og Java DB? Denne forelesningen. Java Database Connectivity (JDBC) Hva er Derby og Java DB?

Java Database Connectivity (JDBC) Norvald H. Ryeng

Transaksjoner og flerbrukerproblematikk. Transaksjoner

JDBC. Java DataBase Connectivity SQL i Java Læreboken: 8.5, s Forelesning i TDT4145, 9. mars 2004 Av Gisle Grimen

Jentetreff INF1000 Debugging i Java

Tilkobling og Triggere

Å programmere databasetjeneren JavaDB. Programkoden ligger i databasen

Oppgaver Oppgave a: Sett opp mulige relasjoner

Løsningsskisse, eksamen J2EE og distribuerte systemer 19.mai 2004

Eksamen i Internetteknologi Fagkode: ITE1526

Leksjon 7. Filer og unntak

Plan for dagen. Vprg 4. Dagens tema - filbehandling! Strømmer. Klassen FilLeser.java. Tekstfiler

Hittil har programmene kommunisert med omverden via tastatur og skjerm Ønskelig at data kan leve fra en kjøring til neste

Løsningsforslag ukeoppg. 6: 28. sep - 4. okt (INF Høst 2011)

LC191D Videregående programmering Høgskolen i Sør-Trøndelag, Avdeling for informatikk og e-læring. Else Lervik, januar 2012.

J2EE og distribuerte systemer Leksjon 10: Entity Beans (BMP)

INF 1000 høsten 2011 Uke september

9. ASP med databasekopling, del II

INF1000 undervisningen INF 1000 høsten 2011 Uke september

Metaspråket for å beskrive grammatikk

J2EE. CMP Entity Beans, Transaksjoner, JSP

Datamodellering og databaser SQL, del 2

INF1010 våren 2017 Onsdag 25. januar. Litt om unntak i Java

Å lese tall fra en fil, klassen Scanner

HØGSKOLEN I SØR-TRØNDELAG

Oppgave 1 (Opprett en database og en tabell)

HØGSKOLEN I SØR-TRØNDELAG

INF1010 våren 2019 Onsdag 30. januar. Mer om unntak i Java (med litt repetisjon av I/O først)

Datamodellering og databaser SQL, del 2

JSP - 2. Fra sist. Hvordan fungerer web? Tjenerside script HTML. Installasjon av Web-tjener Et enkelt JSP-script. Ønsker dynamiske nettsider:

Datamodellering og databaser SQL, del 2

Oversikt. INF1000 Uke 1 time 2. Repetisjon - Introduksjon. Repetisjon - Program

1. NetBeans IDE: Lage en enkel mobilapplikasjon

Kort om meg. INF1000 Uke 2. Oversikt. Repetisjon - Introduksjon

Kapittel 8: Programutvikling

Klassen javax.swing.joptionpane

TOD063 Datastrukturer og algoritmer

Introduksjon til fagfeltet

SQL Structured Query Language. Definere tabeller Skranker Fylle tabeller med data

Hva er verdien til variabelen j etter at følgende kode er utført? int i, j; i = 5; j = 10; while ( i < j ) { i = i + 2; j = j - 1; }

2 Om statiske variable/konstanter og statiske metoder.

Programmeringsspråket C

Oblig 4Hybelhus litt mer tips enn i oppgaven

INF1000 : Forelesning 1 (del 2)

Løse reelle problemer

INF Uke 10. Ukesoppgaver oktober 2012

programeksempel Et større En større problemstilling Plan for forelesingen Problemstillingen (en tekstfil) inneholdt ordet "TGA"

TDT4100 Objektorientert programmering

Dagens forelesning. Java 13. Rollefordeling (variant 1) Rollefordeling (variant 2) Design av større programmer : fordeling av roller.

UNIVERSITETET I OSLO

Del 3: Evaluere uttrykk

HØGSKOLEN I SØR-TRØNDELAG

HØGSKOLEN I SØR-TRØNDELAG

MySQL-database, php. Innhold. 8 MySQL-database, php. 8.1 Databasen MySQL

INF1000: Forelesning 7. Konstruktører Static

Enkle generiske klasser i Java

Løsningsforslag Test 2

1. Innføring i bruk av MySQL Query Browser

Eksamen. Objektorientert Programmering IGR 1372

INF1000: Forelesning 7

Oblig 4 (av 4) INF1000, høsten 2012 Værdata, leveres innen 9. nov. kl

IN1010. Fra Python til Java. En introduksjon til programmeringsspråkenes verden Dag Langmyhr

Programmering i C++ Løsningsforslag Eksamen høsten 2005

INF1000 EKSTRATILBUD. Stoff fra uke 1-5 (6) 3. oktober 2012 Siri Moe Jensen

Løsningsforslag til eksamen i INF1000 våren 2006

Fra Python til Java, del 2

Bruk av NetBeans i JSP-delen av Web-applikasjoner med JSP og JSF

Kapittel 1. Datamaskiner og programmeringsspråk. 1.1 Programmering

Oversikt. INF1000 Uke 2. Repetisjon - Program. Repetisjon - Introduksjon

Databaser: Relasjonsmodellen, del I

1. Finn klassene (hvilke objekter er det i problemet) 1. Dataene som beskriver problemet (hvilke objekter har vi og hvor mange klasser er det?

Stein Gjessing, Institutt for informatikk, Universitetet i Oslo

Argumenter fra kommandolinjen

Forkurs INF1010. Dag 3. Andreas Færøvig Olsen Gard Inge Rosvold Institutt for Informatikk, 15.

INF1010 våren 2018 tirsdag 23. januar

Rekursjon. (Big Java kapittel 13) Fra Urban dictionary: recursion see recursion. IN1010 uke 8 våren Dag Langmyhr

Applikasjonsutvikling med databaser

INF Notater. Veronika Heimsbakk 10. juni 2012

UNIVERSITETET I OSLO

Dagens tema Kapittel 8: Objekter og klasser

Rekursjon. (Big Java kapittel 13) Fra Urban dictionary: recursion see recursion. IN1010 uke 8 våren Dag Langmyhr

INF1000 Metoder. Marit Nybakken 16. februar 2004

I dag skal vi se på. INF 1000 (uke 2) Variabler, tilordninger og uttrykk. Gruppene starter denne uken! Klart for første oblig

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

IN1010. Fra Python til Java. En introduksjon til programmeringsspråkenes verden Dag Langmyhr

Introduksjon til objektorientert. programmering. Hva skjedde ~1967? Lokale (og globale) helter. Grunnkurs i objektorientert.

EKSAMEN. TILLATTE HJELPEMIDLER: Alle trykte og skrevne. INNFØRING MED PENN, evt. trykkblyant som gir gjennomslag

Dagens tema. Hva er kompilering? Anta at vi lager dette lille programmet doble.rusc (kalt kildekoden): Hva er kompilering?

Kapittel 1 En oversikt over C-språket

Oversikt. Introduksjon Kildekode Kompilering Hello world Hello world med argumenter. 1 C programmering. 2 Funksjoner. 3 Datatyper. 4 Pekere og arrays

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

EKSAMEN 6108/6108N PROGRAMMERING I JAVA Alt trykt og skriftlig materiale.

Transkript:

Avdeling for informatikk og e-læring, Høgskolen i Sør-Trøndelag 8. JDBC-programmering med tilrettelegging for web-applikasjoner Else Lervik 12.10.2010 Lærestoffet er utviklet for faget LN349D Web-applikasjoner med JSP og JSF 8. JDBC-programmering med tilrettelegging for webapplikasjoner Resymé: Leksjonen begynner med en innføring i JDBC-programmering. Deretter begrunnes og gjennomgås unntakshåndtering i forhold til JDBC-programmering. Vi ser også på hvordan vi programmerer transaksjonshåndtering ved bruk av commit og rollback. Deretter gjennomgås ferdigkompilerte SQL-setninger (PreparedStatement), da dette er viktig for å unngå SQL-injeksjon. Leksjonen avslutter med et eksempel og en oppsummering av hva man må ta hensyn til når man skal programmere web-applikasjoner med databasekopling. Innhold 8. JDBC-PROGRAMMERING MED TILRETTELEGGING FOR WEB-APPLIKASJONER... 1 8.1. INNLEDNING... 2 8.2. ENKEL JAVA-PROGRAMMERING MOT DATABASE... 2 8.2.1 Litt om databasedrivere... 2 8.2.2 Eksempeldatabase... 3 8.2.3 Java-program som henter ut data fra en database executequery()... 3 8.2.4 Java-program som endrer på innholdet i en database executeupdate()... 6 8.2.5 Refleksjoner over sammenhengen mellom relasjonsdatabaser og objekter... 7 8.3. UNNTAKSHÅNDTERING OG DATABASER... 7 8.3.1 Unntakshåndtering i JDBC-kode... 8 8.4. TRANSAKSJONSHÅNDTERING... 11 8.5. FORHÅNDSKOMPILERTE SQL-SETNINGER... 14 8.6. HVORDAN PROGRAMMERE DATABASEKONTAKT FOR EN WEB-APPLIKASJON... 16 Referanse til læreboka: David Geary, Cay Horstmann: Core JavaServer Faces, Third Edition: Kapittel 12, side 487-495. Leksjonen repeterer og går dypere inn i materien enn det læreboka gjør.

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 2 av 19 8.1. Innledning Hovedtemaet i denne leksjonen er egentlig uavhengig av web-applikasjoner. Du skal lære å skrive Java-kode mot en database. For noen er dette kanskje kjent stoff, for andre er det totalt ukjent. 1 Dersom det er lenge siden du har jobbet med databaser og SQL anbefaler jeg at du leser vedlegget til denne leksjonen. Det er lenke på leksjonsforsiden. Du kan i prinsippet benytte hvilket databasesystem som helst her, men leksjonen og eksemplene er tilrettelagt for Java DB, som er et fullverdig databasesystem som følger med Java 6. Du finner lenke til brukerveiledning på leksjonsforsiden. I det øvingsbaserte prosjektet må du bruke JavaDB, ellers blir det urimelig tungvindt for oss å prøve ut det du har laget. 8.2. Enkel Java-programmering mot database 8.2.1 Litt om databasedrivere Du er vant med å kjøre SQL-setninger mot en database. Vi skal nå pakke disse setningene inn i Java-program. Alle Java-programmer som skal snakke med en database, gjør dette via en JDBC-driver. JDBC-til-ODBCdriver Javaapplikasjon Javaapplikasjon Javaapplikasjon JDBC Driver Manager ODBC-driver Oracle-driver Java DB-driver Microsoft Access databasesystem Microsoft SQL Server databasesystem Oracle databasesystem JavaDB databasesystem database database database database Figur 1: Java-applikasjoner kommuniserer med databasen via drivere 1 Dere som kan dette godt, får ha meg unnskyldt. Det er pensum i Videregående programmering. Jeg velger likevel å bruke en leksjon på dette temaet, fordi det kun var en (liten) del av Videregående programmering.

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 3 av 19 JDBC (Java Database Connectivity) er en del av det vanlige Java-API-et. Det er spesifisert i en mengde interface som er dokumentert i API-dokumentasjonen (se pakken java.sql). Interfacene er implementert i tilsvarende mengder av klasser som vi finner som såkalte JDBCdrivere. Disse klassene lar oss kommunisere med et databasesystem. Vi kan for eksempel sende en SQL-setning til systemet ved å kalle en bestemt Java-metode. Svaret får vi tilbake i et Java-objekt. Klassene (driverne) er knyttet til det bestemte databasesystemet vi bruker. Den JDBC-driveren (vanligvis en jar-fil) vi skal bruke, må ligge i CLASSPATH. Hvis du planlegger å bruke Java DB fra kommandolinjen, og har satt opp miljøvariablene som forklart i brukerveiledningen, så er dette i orden allerede. Hvis du planlegger å bruke NetBeans (New Project /Java/Java Application), må du legge inn jar-filen i listen over biblioteker. Under det aktuelle prosjektet velger du Libraries, høyreklikker og velger så Add JAR/Folder. Deretter henter du fram derbyclient.jar. Hva hvis jeg vil bruke en annen database? I programkoden nedenfor må du skifte ut navnet på driveren og navnet på databasen. Du må også legge inn en driver som passer med det databasesystemet du bruker. Og dermed også utvide CLASSPATH. 8.2.2 Eksempeldatabase I vedlagte leksjonssamling finner du scriptet person.sql som lager følgende lille tabell: persnr fornavn etternavn 100 Ole Hansen 101 Anne Grethe Ås 102 Jonny Hansen Opprett en database som heter persondata med brukernavn vprg og passord vprg. Kjør scriptet person.sql, slik at tabellen havner i denne databasen. (Hvis du ikke gjør det slik, må du endre eksemplene nedenfor.) 8.2.3 Java-program som henter ut data fra en database executequery() I eksempelsamlingen finner du Java-programmet DatabaseKontakt.java. Kompiler og kjør det. Får du kjørefeilen Exception in thread "main" java.lang.classnotfoundexception:org.apache.derby.jdbc.clientdriver...... betyr det at programmet ikke finner databasedriveren. Det vil si at CLASSPATH er feil. Sjekk denne en gang til. Husk at hele navnet på jar-filen skal være med, ikke bare stien fram til filen. Her er kildekoden:

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 4 av 19 /* * DatabaseKontakt.java * * Programmet kopler seg til databasetjener, * og henter ut innholdet i tabellen Person. * import java.sql.*; class DatabaseKontakt { public static void main(string[] args) throws Exception { String databasedriver = "org.apache.derby.jdbc.clientdriver"; Class.forName(databasedriver); // laster inn driverklassen String databasenavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; Connection forbindelse = DriverManager.getConnection(databasenavn); Statement setning = forbindelse.createstatement(); ResultSet res = setning.executequery("select * from person"); while (res.next()) { int persnr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(persNr + ": " + fornavn + " " + etternavn); res.close(); setning.close(); forbindelse.close(); /* Utskrift fra programmet: 100: Ole Hansen 101: Anne Grethe Ås 102: Jonny Hansen La oss gå gjennom koden linje for linje. De klassene vi bruker i forbindelse med databasehåndteringen, ligger i pakken java.sql: import java.sql.*; Vi ser at main() kaster eventuelle unntak. Dette er nødvendig fordi alle databasekall kan kaste java.sql.sqlexception, og kompilatoren krever at de håndteres. Vi må først registrere driveren vi skal bruke. Det gjør vi enklest på følgende måte: String databasedriver = "org.apache.derby.jdbc.clientdriver"; Class.forName(databasedriver); Metoden forname() er en klassemetode i klassen Class. Den sier at vi skal laste inn en klasse med navnet org.apache.derby.jdbc.clientdriver. (Dersom vi ser på innholdet i filsamlingen derbyclient.jar, vil vi finne filen ClientDriver.class i undermappen org/apache/derby/jdbc)

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 5 av 19 Neste steg er å sette opp en forbindelse til databasen: String databasenavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; Connection forbindelse = DriverManager.getConnection(databasenavn); Vi åpner forbindelsen til JavaDB-databasen som kjører på port 1527 på egen PC. Dersom du bruker en annen databasedriver, må du endre navnet på driverklassen (strengen databasedriver) og navnet på databasen (strengen databasenavn). Se dokumentasjonen som følger med driveren. Husk også å endre CLASSPATH. Vi er nå ferdig med det databasesystemspesifikke. Programmet skal hente ut alle dataene i tabellen person og skrive dem ut på skjermen. Følgende SQL-setning henter alle dataene fra tabellen: select * from person; Denne setningen skal vi sende til databasen. Vi må begynne med å lage et setningsobjekt knyttet til databaseforbindelsen: Statement setning = forbindelse.createstatement(); SQL-setninger for å manipulere data deles vanligvis i to grupper: select-setningen brukes til å søke i databasen, og de andre setningene (insert, update og delete) brukes til å endre innholdet i databasen. Den siste gruppen setninger kan utføres ved å sende meldingen executeupdate() til setningsobjektet. Det skal vi se eksempler på senere. Vi utfører nå en select-setning ved å sende meldingen executequery() til setningsobjektet: ResultSet res = setning.executequery("select * from person"); res er et objekt av klassen ResultSet. Resultatet fra en select-setning er ofte flere linjer med data. Vi bruker metoden next() for å løpe gjennom alle linjene: while (res.next()) { int persnr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(persNr + ": " + fornavn + " " + etternavn); Merk at vi må bruke next() også for å få tak i den første linjen. For hver linje må vi hente ut dataverdien i hver enkelt kolonne. Vi bruker getstring() for strenger, getint() for heltall, getdouble() for desimaltall osv. Se API-dokumentasjonen til klassen java.sql.resultset. Til slutt frigjør vi databaseressursene: res.close(); setning.close(); forbindelse.close();

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 6 av 19 I programkoden har vi brukt getint() for å hente et heltall og getstring() for å hente en tekst. Her følger en oversikt over datatyper i SQL og i Java, samt navnet på de aktuelle getmetodene: SQL datatype Java datatype getxxx() CHAR, VARCHAR, LONGVARCHAR String getstring() NUMERIC, DECIMAL java.math.bigdecimal getbigdecimal() BIT boolean getboolean() TINYINT byte getbyte() SMALLINT short getshort() INTEGER int getint() BIGINT long getlong() REAL float getfloat() FLOAT, DOUBLE double getdouble() BINARY, VARBINARY, LONGVARBINARY byte[] getbytes() DATE java.sql.date getdate() TIME java.sql.time gettime() TIMESTAMP java.sql.timestamp gettimestamp() 8.2.4 Java-program som endrer på innholdet i en database executeupdate() Studer også filen LagDatabase.java i vedlagte eksempelsamling. Denne er et alternativ til å lage databasen fra kommandolinjen. Her ser du hvordan vi utfører insert-setninger fra et Javaprogram: setning.executeupdate("insert into person values (100, 'Ole', 'Hansen')"); Setningen returnerer antall rader lagt inn, men i dette eksemplet er ikke det av interesse. Hvis person nr 100 er lagt inn fra før, vil programmet feile pga at databasesystemet ikke godtar to rader med samme verdi på primærnøkkelen. Feilhåndtering er ganske snart tema. Vi bruker altså executequery() med ResultSet-objekt dersom vi utfører select-setninger, og vi bruker executeupdate() hvis vi skal utføre insert/delete/update-setninger. Foran var dataene i insert-setningen hardkodet. Det er også mulig å lese dem inn fra brukeren, eller på annen måte legge dem inn i variabler. Anta at vi har dataene i heltallsvariabelen nr og i String-variablene fnavn og enavn. Disse variablene bruker vi i insert-setningen slik: String sqlsetn = "insert into person values(" + nr + ", '" + fnavn + "', '" + enavn + "')"; Merk at vi må ha med enkeltapostrofene rundt fornavn og etternavn.

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 7 av 19 8.2.5 Refleksjoner over sammenhengen mellom relasjonsdatabaser og objekter Vi aner allerede nå problemene som oppstår på grunn av at vi ikke arbeider med en objektdatabase. For å lagre et personobjekt må vi plukke det fra hverandre i nummer, fornavn og etternavn og lage tabell-linjer av det. For å hente ut et objekt fra databasen må vi hente ut hver enkelt dataverdi og lage et objekt av disse verdiene. Her skriver vi bare ut verdiene, men vi kunne også laget et objekt, slik: Person enperson = new Person(persNr, fornavn, etternavn); Det eksisterer rene objektdatabaser, og det eksisterer også objektlignende utvidelser av mange av de tradisjonelle databasesystemene. Det gjør det mulig å forholde seg til objektmodellen også når vi lagrer data (jamfør serialisering). Men det er lite som tyder på at bruken av relasjonsdatabaser er i ferd med å avta, derfor eksisterer det nå såkalte Object Relational Mapping (ORM) rammeverk. Disse skjuler jobben med å omforme tabelldata til og fra objekter. Java Persistence er en ORM-standard. Hibernate og Oracle Top Link er kjente implementasjoner av standarden. Å ta i bruk Java Persistence er ikke trivielt, men gevinsten er betydelig når læringsterskelen er overskredet. Java Persistence er dermed for omfattende til å ta inn i denne leksjonen. Her holder vi oss til JDBC. (Derimot er Java Persistence sentralt i faget Java EE og distribuerte systemer.) 8.3. Unntakshåndtering og databaser I eksemplene foran har vi valgt minste motstands vei og kastet unntaksobjektene ut av main(). Det medfører at ethvert unntaksobjekt gjør at programmet stopper. En viktig grunn til å håndtere unntak på en ordentlig måte er selvfølgelig å unngå dette. Men det fins også andre grunner som jeg skal forsøke å gjøre rede for. Et Java-program som kommuniserer med en database, arbeider mot et databasesystem som er komplisert programvare. Mellom Java-programmet og databasesystemet har vi en JDBCdriver, som er et sett med klasser, som sørger for at Java-programmet kan bruke de samme metodene uavhengig av databasesystem (se figuren på side 2). SQL-setningene vi sender inn som argumenter til disse metodene, overføres uendret til databasesystemet, som kompilerer og utfører setningene. Et Java-program kan derfor meget vel gå gjennom kompileringen selv om SQL-setningene har ugyldig syntaks og/eller refererer til tabeller eller kolonner som ikke fins. Dersom slike feil oppdages, kaster den aktuelle Java-metoden (f.eks. executequery()) en SQLException. De fleste syntaksfeil fra databasesystemet er feil av en type som programmereren bør kunne rette opp før systemet settes i produksjon. Det viser seg imidlertid vanskelig å gardere seg mot alle slike feil, og behandling av SQLException er derfor nødvendig.

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 8 av 19 class DatabaseKontakt { public static void main(string[] args) throws Exception { String databasedriver =... Class.forName(databasedriver); // laster inn driverklassen String databasenavn =... Connection forbindelse = DriverManager.getConnection(databasenavn); oftest forekommende feil er ClassNotFound- Exception Statement setning = forbindelse.createstatement(); ResultSet res = setning.executequery("select * from person"); while (res.next()) { int persnr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(persNr + ": " + fornavn + " " + etternavn); res.close(); setning.close(); forbindelse.close(); Connections kan forbli åpne Statements og Connections kan forbli åpne kaster SQLException hvis feil inntreffer ResultSets, Statements og Connections kan forbli åpne Figur 2: Unntak som kan kastes i et enkelt databaseprogram Figur 2 viser hvilke typer unntaksobjekt som de vanligste setningene i et databaseprogram kaster. Merk at dette er unntak som kompilatoren krever at må behandles. (På samme måte som IOExceptions i forbindelse med filbehandling.) Som du ser av figuren, kan vi risikere at ressurser knyttet til ResultSet-, Statement- og Connection-objekt ikke er frigjort. Vi snakker her om databasesystemet sine ressurser. Jeg er etter hvert blitt ganske hysterisk mht. viktigheten av å frigjøre databaseressurser: For en del år siden klarte studentene å knekke Oracle på grunn av at de ikke lukket ResultSet-objekter eksplisitt. Vi har fortsatt en del problemer med databaseforbindelser i den poolen som Tomcat vedlikeholder i tilknytning til vår nettbutikk itfag.hist.no. Her bruker vi databasesystemet PostgreSQL. Selv om dokumentasjonen sier noe annet (eksempel, for java.sql.statement: "When a Statement object is closed, its current ResultSet object, if one exists, is also closed."), anbefaler jeg at dere alltid lukker alle ressursene på en ryddig måte - først ResultSet-objekter, så Statement-objekter og endelig Connection-objektet. 8.3.1 Unntakshåndtering i JDBC-kode På filen DatabaseKontaktMedExc.java finner du det enkle klientprogrammet fra side 4 med fullstendig unntakshåndtering: /* * DatabaseKontaktMedExc.java * Utvidet med unntakshåndtering

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 9 av 19 * import static javax.swing.joptionpane.*; import java.sql.*; class DatabaseKontaktMedExc { public static void main(string[] args) { String databasedriver = "org.apache.derby.jdbc.clientdriver"; Connection forbindelse = null; // feilutgang hvis databasetilkopling mislykkes Class.forName(databasedriver); String databasenavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; forbindelse = DriverManager.getConnection(databasenavn); catch (Exception e) { System.out.println("Feil 1: " + e); System.exit(0); // avslutter Statement setning = null; ResultSet res = null; setning = forbindelse.createstatement(); res = setning.executequery("select * from person"); while (res.next()) { int persnr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(persNr + ": " + fornavn + " " + etternavn); catch (SQLException e) { System.out.println("Feil 2: " + e); finally { if (res!= null) res.close(); catch (SQLException e) { System.out.println("Feil 3: " + e); finally { if (setning!= null) setning.close(); catch (SQLException e) { System.out.println("Feil 4: " + e); finally { if (forbindelse!= null) forbindelse.close(); catch (SQLException e) { System.out.println("Feil 5:" + e); // finally, lukking av Statement-objekt // finally, lukking av ResultSet-objekt // finally Merk hvordan res.close(), setning.close() og forbindelse.close() håndteres hver for seg. Dersom res.close() feiler, hopper vi ikke ut, men forsøker setning.close(). Tilsvarende hvis setning.close() feiler, da prøver vi likevel forbindelse.close(). Prøv å framprovosere feil ved å skrive drivernavnet feil, og deretter å legge inn feil i selectsetningen. Ingen vil vel påstå at denne koden er spesielt oversiktlig. Det er derfor ikke uvanlig å legge unntaksbehandlingen av close() til egne metoder. Filen DatabaseKontaktMedExc2.java viser

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 10 av 19 samme eksempel som foran, men der en spesiell opprydder -klasse er tatt i bruk i stedet for den kompliserte try-catch-finally-strukturen: /** * DatabaseKontaktMedExc2.java * Bruker klassen Opprydder til frigjøring av databaseressurser. * import static javax.swing.joptionpane.*; import java.sql.*; class DatabaseKontaktMedExc2 { public static void main(string[] args) { String databasedriver = "org.apache.derby.jdbc.clientdriver"; Connection forbindelse = null; // feilutgang hvis databasetilkopling mislykkes Class.forName(databasedriver); String databasenavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; forbindelse = DriverManager.getConnection(databasenavn); catch (Exception e) { System.out.println("Feil 1: " + e); System.exit(0); // avslutter Statement setning = null; ResultSet res = null; setning = forbindelse.createstatement(); res = setning.executequery("select * from person"); while (res.next()) { int persnr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(persNr + ": " + fornavn + " " + etternavn); catch (SQLException e) { System.out.println("Feil 2: " + e); finally { Opprydder.lukkResSet(res); Opprydder.lukkSetning(setning); Opprydder.lukkForbindelse(forbindelse); // finally Her følger klassen Opprydder: import java.sql.*; public class Opprydder { public static void lukkresset(resultset res) { if (res!= null) { res.close(); catch (SQLException e) { skrivmelding(e, "lukkresset()");

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 11 av 19 public static void lukksetning(statement stm) { if (stm!= null) { stm.close(); catch (SQLException e) { skrivmelding(e, "lukksetning()"); public static void lukkforbindelse(connection forbindelse) { if (forbindelse!= null) { forbindelse.close(); catch (SQLException e) { skrivmelding(e, "lukkforbindelse()"); public static void rulltilbake(connection forbindelse) { if (forbindelse!= null &&!forbindelse.getautocommit()) { forbindelse.rollback(); catch (SQLException e) { skrivmelding(e, "rollback()"); public static void settautocommit(connection forbindelse) { if (forbindelse!= null &&!forbindelse.getautocommit()) { forbindelse.setautocommit(true); catch (SQLException e) { skrivmelding(e, "settautocommit()"); public static void skrivmelding(exception e, String melding) { System.err.println("*** Feil oppstått: " + melding + ". ***"); e.printstacktrace(system.err); Unntakshåndtering i web-applikasjoner Unntakshåndtering er like viktig i kode som skal brukes i web-applikasjoner som ellers, men unngå å bruke System.exit(). Det kan føre til at hele web-tjeneren stopper. 8.4. Transaksjonshåndtering (Kapitlet er hentet fra kapittel 24.7 i Lervik og Havdal: Programmering i Java, 4.utgave.) En transaksjon er en logisk enhet med arbeid og kan bestå av flere oppdateringssetninger mot databasen. Eksempel: Dersom penger skal overføres fra en konto til en annen, er det viktig at

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 12 av 19 begge kontoene endrer saldo. Dersom en feil inntreffer, kan vi ikke risikere at bare en av kontoene har endret saldo. Vi definerer derfor transaksjonen til å bestå av de to oppdateringskommandoene. Dersom en feil inntreffer før begge kommandoene er ferdig utført, skal hele transaksjonen rulles tilbake (engelsk: rollback) slik at databasen er i den tilstanden den var i før transaksjonen begynte. Dersom alt gikk bra, skal transaksjonen bekreftes (engelsk: commit) ved at endringene lagres permanent i databasen. Vanligvis er hver enkelt SQL-setning en transaksjonsenhet, vi sier at autocommit er på. I situasjoner som nevnt her, må vi ha muligheten til å utvide størrelsen på en transaksjon. Metoder i interfacet Connection gir oss muligheten til å styre dette. Følgende skript kan brukes til å lage en tabell med kontoopplysninger: create table konto( kontonr varchar(15) not null, navn varchar(30) not null, saldo decimal not null, primary key (kontonr)); insert into konto values ('123456', 'Ole Ås', 1000); insert into konto values ('345678', 'Anne Grethe Ås', 20000); insert into konto values ('678909', 'Jonny Hansen', 10000); En transaksjon kan for eksempel bestå av følgende to SQL-setninger: update konto set saldo = saldo 1000 where kontonr = '123456'; update konto set saldo = saldo + 1000 where kontonr = '678909'; Her følger et Java-program som utfører disse to setningene som én transaksjon: /** * Transaksjonstest.java - "Programmering i Java", 4.utgave * * Programmet utfører en databasetransaksjon som består av * to SQL-setninger. import java.sql.*; import mittbibliotek.opprydder; class Transaksjonstest { public static void main(string[] args) throws Exception { String databasedriver = "org.apache.derby.jdbc.clientdriver"; Class.forName(databasedriver); String databasenavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; Connection forbindelse = null; Statement setning = null; ResultSet res = null; forbindelse = DriverManager.getConnection(databasenavn); setning = forbindelse.createstatement(); forbindelse.setautocommit(false); // transaksjon starter res = setning.executequery("select * from konto"); System.out.println("Innhold i databasen før endring: "); while (res.next()) { System.out.println(res.getString("kontonr") + ", " + res.getstring("navn") + ", " + res.getbigdecimal("saldo"));

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 13 av 19 res.close(); /* Overfører 1000 kroner fra konto 123456 til konto 678909 setning.executeupdate( "update konto set saldo = saldo - 1000 where kontonr = '123456'"); setning.executeupdate( "update konto set saldo = saldo + 1000 where kontonr = '678909'"); /* Skriver ut alle dataene res = setning.executequery("select * from konto"); System.out.println("\nInnhold i databasen etter endring: "); while (res.next()) { System.out.println(res.getString("kontonr") + ", " + res.getstring("navn") + ", " + res.getbigdecimal("saldo")); res.close(); forbindelse.commit(); // alt ok, endringer lagres i databasen catch (SQLException e) { // feil oppstått, alle endringer angres Opprydder.rullTilbake(forbindelse); Opprydder.skrivMelding(e, "Ruller tilbake"); finally { // opprydding uansett utfall av transaksjonen Opprydder.settAutoCommit(forbindelse); Opprydder.lukkResSet(res); Opprydder.lukkSetning(setning); Opprydder.lukkForbindelse(forbindelse); /* Utskrift fra programmet: Innhold i databasen før endring: 123456, Ole Ås, 1000 345678, Anne Grethe Ås, 20000 678909, Jonny Hansen, 10000 Innhold i databasen etter endring: 123456, Ole Ås, 0 345678, Anne Grethe Ås, 20000 678909, Jonny Hansen, 11000 Legg merke til hvordan vi slår av autocommit før transaksjonen begynner: forbindelse.setautocommit(false); Etter at transaksjonen er fullført, sørger vi for å sende en bekreftelse til databasen: forbindelse.commit(); Hvis en feil oppstår, vil programkontrollen flytte over til unntakshåndteringen. Alle endringer rulles tilbake: Opprydder.rullTilbake(forbindelse); // se klassen Opprydder foran

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 14 av 19 Uansett om transaksjonen går bra eller ikke, er det en del operasjoner som må gjøres. Disse er samlet i finally-blokken. Opprydder.settAutoCommit() kaller forbindelse.setautocommit(true) og slår dermed på autocommit igjen. Dette er ikke så viktig her som programmet avsluttes, men normalt vil transaksjonen inngå i et større programsystem der vi skal bruke databaseforbindelsen videre og da er det viktig at autocommit er slått på. En transaksjon fører til at en del av databasen er låst inntil transaksjonen er avsluttet. På grunn av dette er det viktig å huske å avslutte transaksjoner som er påbegynt. Akkurat hvordan og hvor stor del av databasen som holdes reservert i en transaksjon, varierer fra databasesystem til databasesystem, og det kan også til en viss grad styres av programmereren. Pass på at du ikke legger innlesing av data fra brukeren inne i en transaksjon! Transaksjonshåndtering og synkronisering av metoder (blokker) Merk at det ene ikke utelukker det andre. Synkronisering av metoder (blokker) er nødvendig for å hindre at en tråd får tilgang til et Java-objekt før en annen tråd er ferdig med det. Transaksjonshåndtering (commit og rollback) er nødvendig for å hindre at en databaseapplikasjon (bruker) får tilgang til dataene i en database før en annen applikasjon (bruker) er ferdig med dem. Transaksjonshåndtering er et omfattende tema som bare så vidt er berørt her. Det stilles små krav til transaksjonshåndtering i dette kurset. 8.5. Forhåndskompilerte SQL-setninger (Kapitlet er hentet fra kapittel 24.9 i Lervik og Havdal: Programmering i Java, 4.utgave.) En SQL-setning må kompileres av databasesystemet før den kjøres. Databasesystemet setter også opp en plan slik at søket kan gjøres på en mest mulig effektiv måte. Dersom en sender den samme setningen mange ganger, vil en spare tid ved å gjøre disse forberedelsene bare én gang. Vi kan bytte ut konstantverdier i setningen uten å kompilere den på nytt. Vi kan lage et ferdig kompilert setningsobjekt ved å bruke klassen PreparedStatement. Se programliste nedenfor. /** * PreparedStmtTest.java - "Programmering i Java", 4.utgave * * Viser eksempel på bruk av ferdigkompilerte SQL-setninger import static javax.swing.joptionpane.*; import java.sql.*; import mittbibliotek.opprydder; class PreparedStmtTest { public static void main(string[] args) throws Exception { String databasedriver = "org.apache.derby.jdbc.clientdriver"; Class.forName(databasedriver); String dbnavn = "jdbc:derby://localhost:1527/persondata;user=vprg;password=vprg"; Connection forbindelse = null; PreparedStatement setning = null; ResultSet res = null;

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 15 av 19 forbindelse = DriverManager.getConnection(dbnavn); String sqlsetning = "select * from person where fornavn like? and etternavn like?"; setning = forbindelse.preparestatement(sqlsetning); String kritforn = showinputdialog( "Søkekriterium fornavn (avslutt med trykk Escape):"); while (kritforn!= null) { String kritettern = showinputdialog("søkekriterium etternavn: "); setning.setstring(1, kritforn.trim()); setning.setstring(2, kritettern.trim()); System.out.println("\nSøkekriteriene er " + kritforn + " " + kritettern + "."); res = setning.executequery(); while (res.next()) { int nr = res.getint("persnr"); String fornavn = res.getstring("fornavn"); String etternavn = res.getstring("etternavn"); System.out.println(fornavn + " " + etternavn); res.close(); kritforn = showinputdialog( "Søkekriterium fornavn (avslutt med trykk Escape):"); catch (SQLException e) { Opprydder.skrivMelding(e, "Ruller tilbake"); finally { Opprydder.lukkResSet(res); Opprydder.lukkSetning(setning); Opprydder.lukkForbindelse(forbindelse); /* Utskrift fra programmet: Søkekriteriene er % H%. Ole Hansen Jonny Hansen Søkekriteriene er % %. Ole Hansen Anne Grethe Ås Jonny Hansen Søkekriteriene er H%. Ole Hansen SQL-setningen inneholder to spørsmålstegn: select * from person where fornavn like? and etternavn like? Vi skal her ha fram personen som tilfredsstiller bestemte søkekriterier. Programmet går i løkke og spør brukeren etter stadig nye søkekriterier for fornavn og etternavn. Søkekriteriene setter vi inn på plassen til spørsmålstegnene: setning.setstring(1, kritforn.trim()); setning.setstring(2, kritettern.trim());

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 16 av 19 Det første spørsmålstegnet har nummer 1. Vi kan bruke jokertegn i søkekriteriene. % betyr vilkårlig antall tegn, mens _ betyr eksakt ett tegn. (Disse jokersymbolene er standard SQL, men ikke alle databasesystemer følger standarden akkurat på dette punktet.) Søkekriteriene (tre understrekingstegn) for fornavnet og h% for etternavnet gir oss alle navn der fornavnet består av eksakt tre bokstaver, mens etternavnet begynner på H. Metodenavnene for å sette inn data der spørsmålstegnene står, følger SQL-datatypen på tilsvarende måte som getxxx()-metodene i tabellen på side 6. PreparedStatement bør brukes i web-applikasjoner I tillegg til at PreparedStatement ofte gir mer effektiv kode, så gir det beskyttelse mot innbrudd av typen "SQL-injeksjon". Denne typen sikkerhetshull gjennomgås i en senere leksjon. 8.6. Hvordan programmere databasekontakt for en webapplikasjon Som eksempel skal vi programmere den frivillige databaseøvingen fra kurset Videregående programmering for bruk i en web-applikasjon. (Noen av dere husker kanskje den øvingen?) Klassen Bok er gitt: /** * Bok.java * public class Bok { private final String isbn; private final String tittel; private final String forfatter; /** * Konstruktør: * Isbn, tittel og forfatter må oppgis, * de kan ikke være verken null eller tomme strenger. public Bok(String isbn, String tittel, String forfatter) { this.isbn = isbn; this.tittel = tittel; this.forfatter = forfatter; public String getisbn() { return isbn; public String gettittel() { return tittel; public String getforfatter() { return forfatter;

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 17 av 19 public String tostring() { return isbn + ": " + forfatter + ", " + tittel; Databasescriptet ser slik ut: CREATE TABLE boktittel( isbn VARCHAR(50) NOT NULL, forfatter VARCHAR(50) NOT NULL, tittel VARCHAR(50) NOT NULL, CONSTRAINT boktittel_pk PRIMARY KEY(isbn)); CREATE TABLE eksemplar( isbn VARCHAR(50) NOT NULL, eks_nr INTEGER NOT NULL, laant_av VARCHAR(50), CONSTRAINT eksemplar_pk PRIMARY KEY(isbn, eks_nr)); ALTER TABLE eksemplar ADD CONSTRAINT eksemplar_fk FOREIGN KEY(isbn) REFERENCES boktittel; INSERT INTO boktittel(isbn, forfatter, tittel) values ('0-201-50998-X', 'J. Rumbaugh, I. Jacobson, G. Booch', 'The Unified Modeling Language Reference Manual'); INSERT INTO boktittel(isbn, forfatter, tittel) values ('0-07-241163-5', 'J. P. Cohoon, J. W. Davidson', 'C++ Program Design'); INSERT INTO boktittel(isbn, forfatter, tittel) values ('0-596-00123-1', 'Brett Mclaughlin', 'Bulding Java Enterprise Applications'); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-201-50998-X', 1, NULL); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-07-241163-5', 1, NULL); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-596-00123-1', 1, NULL); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-07-241163-5', 2, NULL); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-596-00123-1', 2, NULL); INSERT INTO eksemplar(isbn, eks_nr, laant_av) values ('0-596-00123-1', 3, NULL); Oppgaven går ut på å lage klassen Database med følgende innhold: public boolean regnybok(bok nybok) Denne metoden skal registrere en ny tittel og samtidig eksemplar nummer 1 av denne tittelen. Metoden skal returnere false dersom bok med dette ISBN er registrert fra før. Da skal ingen oppdatering skje. public int regnytteksemplar(string isbn) Denne metoden skal registrere et nytt eksemplar av en tittel som allerede skal være registrert i databasen. Eksemplarnummeret skal være 1 høyere enn hittil største eksemplarnummer. For å være sikret dette, kan ikke sekvenser/autonummerering brukes. (Du har ikke noen garanti for at slike sekvenser er ubrutt). Metoden skal returnere 0 hvis ingen tittel med dette ISBN eksisterer, ellers skal metoden returnere eksemplarnummeret.

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 18 av 19 public boolean lånuteksemplar(string isbn, String navn, int eksnr) Denne metoden skal registrere at ei bok er utlånt. Kolonnen utlånt for det aktuelle eksemplaret skal settes lik navnet til låneren. Du trenger ikke ta hensyn til om dette feltet allerede er utfylt (vi har ingen metode for å returnere ei bok). Metoden skal returnere false dersom ISBN og/eller eksemplarnummer er ugyldig. (Tips: Det er nok å sjekke at update-setningen returnerer 0, det vil si at ingen rader er oppdatert). Hvordan lage klassen Database slik at den passer å bruke i en web-applikasjon? To prinsipper er viktige: Vi må bruke PreparedStatement for å beskytte oss mot SQL-injeksjon. Hver operasjon (metode) må stå på egne bein, det vil si at den må starte med å åpne en databaseforbindelse og avslutte med å stenge den. Grunnen til dette er at vi må unngå at databaseforbindelser blir hengende i løse luften ved sesjonsavbrudd. Hver klient får sitt eget Database-objekt (vanligvis session-skop). Vi lager metoder for å åpne og lukke databasen: private void åpneforbindelse() { forbindelse = DriverManager.getConnection(dbNavn); System.out.println("Databaseforbindelse opprettet"); catch (SQLException e) { Opprydder.skrivMelding(e, "Konstruktøren"); Opprydder.lukkForbindelse(forbindelse); private void lukkforbindelse() { System.out.println("Lukker databaseforbindelsen"); Opprydder.lukkForbindelse(forbindelse); Disse metodene lar vi være private slik at de kun kan kalles innenfra klassen Database. Vi tar med en av de offentlige metodene for å vise bruken: /* * Registrerer utlån. * Returnerer false dersom ugyldig isbn og/eller eksemplarnummer. * Metoden sjekker ikke om boka allerede er utlånt. * (Kan ev. gjøres med test "laant_av is not null") public boolean lånuteksemplar(string isbn, String navn, int eksnr) { boolean ok = false; PreparedStatement sqlupdlåneks = null; åpneforbindelse(); sqlupdlåneks = forbindelse.preparestatement( "update eksemplar set laant_av =? where isbn =? and eks_nr =?"); sqlupdlåneks.setstring(1, navn); sqlupdlåneks.setstring(2, isbn); sqlupdlåneks.setint(3, eksnr); int ant = sqlupdlåneks.executeupdate(); if (ant > 0) ok = true;

8. JDBC-programmering med tilrettelegging for web-applikasjoner side 19 av 19 catch (SQLException e) { Opprydder.skrivMelding(e, "lånuteksemplar()"); finally { Opprydder.lukkSetning(sqlUpdLånEks); lukkforbindelse(); return ok; Fullstendig kode finner du i eksempelsamlingen. Litt om effektivitet Koden vist her vil ikke bli spesielt effektiv med stadige åpninger og lukkinger av databaseforbindelser. I tillegg mister vi effekt-gevinsten av PreparedStatement på grunn av at vi i hver operasjon oppretter nye PreparedStatement-objekt. I en senere leksjon vil vi se hvordan vi i stedet for å åpne og lukke forbindelser når vi trenger dem, låner forbindelser fra en pool med åpne forbindelser. I den forbindelse vil vi også kunne gjenbruke PreparedStatement-objekt.