En kort introduktion til JDBC

Relaterede dokumenter
Databaseadgang fra Java

En Kort Introduktion til Oracle

Introduktion til Oracle, Datalogi, RUC Af: Jens Lauterbach 2002

//Udskriver System.out.println("Hej " + ditfornavn + " " + ditefternavn + "."); System.out.println("Du er " + dinalder + " aar gammel!

MySQL i Java. Tutorial lavet af Jákup W. Hansen TSU semester 05.januar 2007

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 2

Eksempel på en database: studenter, kurser, eksamener

Test af It-komponent

Web- og serverprogrammering

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 3

Prepared Statements. Denne artikel beskriver hvorfor prepared statements er gode. Den forudsætter lidt kendskab til Java og JDBC.

University of Southern Denmark Syddansk Universitet. DM503 Forelæsning 11

Eksempel: Skat i år 2000

DM507 Algoritmer og datastrukturer

Bemærk! Et PHP script har kun brug for at forbinde én gang til databaseserveren. Det kan så sagtens udføre flere kommandoer vha. denne forbindelse.

Serialization i Java

Indledning. Hvorfor det forholder sig sådan har jeg en masse idéer om, men det bliver for meget at komme ind på her. God fornøjelse med læsningen.

Programmering I Java/C#

DM507 Algoritmer og datastrukturer

9.8 Kildekode. side 88. Pakke Klasse Sidenummer. fortsætter..

Listen over reserverede ord er meget lang, men de væsentligste vil jeg beskrive her i denne artikel:

DM01 DM Obl. Afl. Jacob Christiansen, , D12, Elias 13/ Side 1 af 7

JSP, Tomcat. Tutorial lavet af Jákup W. Hansen TSU semester 10.october 2007

Løsning af møntproblemet

Opdatering af ISOWARE til version 6.1.0

Geografisk lokalisering i JSP

Videregående Programmering Obligatorisk opgave - 3. semester, efterår 2004

AAU, Programmering i Java Intern skriftlig prøve 18. maj 2007

Opret ODBC datakilde Vejledning

DM507 Algoritmer og datastrukturer

Klasser og objekter. (Afsnit i manualen)

Python programmering. Per Tøfting. MacFest

DM01 DM Obl. Afl. Jacob Christiansen, , D12, Elias 18/ Side 1 af 11

KIH Database. Systemdokumentation for KIH Databasen. 1. maj Side 1 af 13

PHP Snippets. De små korte. Skrevet af Daniel Pedersen

Opsætning af MobilePBX med Kalenderdatabase

Ruko SmartAir. Updater installation

Installationsguide til Oracle Database XE 10.2 og APEX 3.1.1

Studiepraktik. Thomas Bøgholm Mikkel Hansen Jacob Elefsen

Installation af Elektronisk APV på flere PC er

Rigtig SQL Programmering

Vejledning. Opsætning af Trio Web Vers 2.0 feb. 2010

DANMARKS TEKNISKE UNIVERSITET

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 4

Kursus i OOP og Java. Kursus i Objektorienteret programmering i Java

DM507 Algoritmer og datastrukturer

Virkefeltsregler i Java

BRP Kursusintroduktion og Java-oversigt

DM507 Eksamen Obligatorisk Opgave Rejseplanlægning

Exceptions i Delphi. Try except

Opdatering af ISOWARE til version 8.0.0

Opsætning af Oracle Designer 10g repositorie

Abstrakte datatyper C#-version

Eksamens spørgsmål i Java HTML - DataBase 3. Semester (i)

SWC eksamens-spørgsmål. Oversigt

Den forudsætter kendskab til C++ og lidt kendskab til SQL og MySQL C API.

Import af rekursivt (parent-child) hierarki i Palo

Parameters. Denne artikel beskriver hvorfor parameters er gode. Den forudsætter lidt kendskab til C# og ADO.NET.

Programmering i C. Lektion september 2009

b) Udvid din implementation af forme til at understøtte.equals. To objekter af samme form er ens hvis de har samme værdier i felterne.

Indholdsfortegnelse Databaser og PHP... 3 Opgave... 4 Opgave... 5 Opgave... 6 Sidste opgave er en lille gæstebog... 7 Kilder og nyttige links:...

Skriftlig eksamen i Datalogi

PHP 3 UGERS FORLØB PHP, MYSQL & SQL

02101 Indledende Programmering Introduktion til Eclipse

Hvad er Objekter - Programmering

Software Construction 1 semester (SWC) Spørgsmål 1

Grundlæggende Programmering ITU, Efterår Skriftlig eksamen i Grundlæggende Programmering

Skriftlig opgave. Designtanker i database-nære systemer

Tree klassen fra sidste forelæsning

Java Programmering. En bog for begyndere. Skrevet af Henrik Kressner

Programmering C RTG

FleeDa (DBK Fleetmap Database) Installationsvejledning til installation af VPN og FleeDa klient på egen PC (Juli 2017)

RMI introduktion. Denne artikel beskriver Java RMI (Remtote Method Invocation).

Administrator - installation og brug i Windows

Introduktion til ActionScript

Martin Olsen. DM507 Projekt Del I. 19. marts 2012 FOTO: Colourbox

Forelæsning Uge 1 Torsdag

Noter til C# Programmering Iteration

Vejledning til Teknisk opsætning

Lær Python dag 1 - modul 1

DM502. Peter Schneider-Kamp

Opsætning af udviklerversion af Microsofts open source XDS.b fra Codeplex Projekt: Net4Care Version: V0.1,

3. Menuen Start -> Programs -> OpenVPN åbnes, og "My Certificate Wizard" vælges:

TimePlan version Installationsvejledning

Administrator - installation og brug i Windows

Forelæsning Uge 2 Torsdag

Kom godt igang med Inventar registrering

Skrevet den 18. Feb 2010 af arne_v I kategorien Programmering / Visual Basic.NET

Programmering i C. Lektion december 2008

Forelæsning Uge 4 Torsdag

Programmering for begyndere Lektion 2. Opsamling mm

Dag 10 Flertrådet programmering

OpenTele datamonitoreringsplatform

Hent filoplysninger fra billeder og filer

HELLO INSTALLATIONS GUIDE - DANSK RACKPEOPLE

Brugermanual PoP3 og Outlook Office 2003 Webmail Udarbejdet af IT-afdelingen 2005

Tilslutning med Cisco AnyConnect VPN-klient (Windows) til AARHUS TECH P-net

Installation af en virtuel maskine

FairSSL Fair priser fair support

Kapitel 4 Løkker i C#

Transkript:

En kort introduktion til JDBC af Henrik Bulskov Datalogi Roskilde Universitetscenter 22. marts 2001 JDBC er et generelt klassebibliotek til kommunikation med databaser. Det er ikke målrettet en bestemt database eller en bestemt måde at kommunikere med databasen på. Det er opbygget af en række forskellige klasser og interfaces der beskriver de mulige handlinger, men ikke hvordan disse skal implementeres. Det er database producenten der fylder denne del ud. Oracles JDBC-biblioteker Det første man skal være i besiddelse af for benytte JDBC er et bibliotek der understøtter den database man gerne vil kommunikere med. For Oracle er det bibliotekerne classes111.zip eller classes12.zip man skal bruge. Den første hvis man benytter JDK 1.1.X og den anden for JDK 1.2 1 eller højere. For at kunne benytte dette bibliotek i forbindelse med kompilering og afvikling af Java programmer skal Java vide hvor de fysisk befinder sig på den maskine der skal kompilere eller afvikle Java. Der er to forskellige muligheder for at gøre dette, enten ved at give kompileren og den virtuelle maskine informationen med i kommandolinien eller ved at tilføje informationen til maskinens miljø(environment). Lad os antage at Oracle er installeret i biblioteket C:\Oracle på Windows eller /oracle på UNIX/Linux. Så vil JDBC bibliotekerne befinde sig hhv. i C:\Oracle\jdbc\lib eller /oracle/jdbc/lib. Der er dog ikke noget krav til at bibliotekerne skal befinde sig et specielt sted. Kompilering af en Java-fil med navnet Test.java vil kunne udføres med følgende kommando: javac -classpath "c:\oracle\jdbc\lib\classes12.zip" Test.java og afviklingen af programmet med java cp ".;C:\Oracle\jdbc\lib\classes12.zip" Test Den skarpe iagttager vil her bemærke at de to henvisninger til biblioteket ikke er identiske. Ved kompilering er den "c:\oracle\jdbc\lib\classes12.zip" mens den ved afviklingen er ".;C:\Oracle\jdbc\lib\classes12.zip" 1 Jeg har bemærket at min version af Oracle for Linux (version 8.1.7) kun har biblioteket classes111.zip. Dette kan godt benyttes sammen med JDK 1.2 el. 1.3.

Der er tilføjet et punktum efterfulgt af et semikolon. Semikolonet er et skilletegn der benyttes til at adskille flere samtidige henvisninger og punktummet referere til det katalog man står i. Tilføjes dette ikke vil man få følgende fejl java -cp "c:\oracle\jdbc\lib\classes12.zip" Test Exception in thread "main" java.lang.noclassdeffounderror: Test der betyder at Java ikke finde klassen Test.class, selvom den ligger i det bibliotek Java afvikles fra. Dette er et lidt ufrostårlig resultat af at tilføje biblioteker til kommandolinien, men det opstår fordi en tildeling i kommandolinien har højere prioritet end alle andre henvisninger, bortset fra Javas egne biblioteker. Det gælder altså også det aktive bibliotek. Den anden mulighed er at tilføje biblioteket via miljøvariabler enten permanent ved opstarten at computeren eller sessionen eller lokalt i den enkelte session. På Windows gøres dette med kommandoen set set CLASSPATH=c:\oracle\jdbc\lib\classes12.zip eller i UNIX/Linux med kommandoen export export CLASSPATH=/oracle/jdbc/lib/classes12.zip Herefter kan kompilering og afvikling foretages med kommandoerne javac Test.java og afviklingen af programmet med java Test Exception in thread "main" java.lang.noclassdeffounderror: test nej, samme fejl igen, så det skal altså være set CLASSPATH=.;c:\oracle\jdbc\lib\classes12.zip under Windows eller export CLASSPATH=.;/oracle/jdbc/lib/classes12.zip på UNIX/Linux. Hvis man ønsker at opsætte sin maskine så dette gøres automatisk ved opstarten eller ved starten af en session skal dette gøres på følgende måder(i eksemplerne antages det stadig at Oracle er installeret i c:\oracle på Windows eller /oracle på UNIX/Linux): Windows 95/98: indsæt linien set CLASSPATH=.;c:\oracle\jdbc\lib\classes12.zip i autoexec.bat og genstart maskinen.

Windows NT/2000: Højreklik på My computer og vælg properties. I den dialog der fremkommer vælges Advanced og Environment Variables. Hvis man er administrator kan man indsætte en CLASSPATH som system variable (som gælder for alle) ellers vælges User variables for.. Klik på New og skrive CLASSPATH som Variable Name og.;c:\oracle\jdbc\lib\classes12.zip som Variable Value 2. Klik Ok for at lukke indtastningsdialogen og Ok for at lukke Environment Variables -dialogen og Ok for at lukke System Properties. For at det virker er det nødvendigt at genstarte de programmer der skal benytte informatinen, f.eks. Ultraedit eller en kommandoprompt. UNIX/Linux: Åben filen.bashrc (hvis du bruger bash, find selv ud af hvilke opstartsfiler andre shells benytter) i dit hjemmekatalog og indsæt følgende i enden af denne export CLASSPATH=.:/Oracle/jdbc/lib/classes12.zip:$CLASSPATH (læg mærke til at der refereres til CLASSPATH til sidst for at sikre at hvis der allerede er tilføjet noget til CLASSPATH så overskrives dette ikke. Det samme kan gøres under Windows). Herefter skal forbindelse til maskinen genetableres for at få indlæst informationerne i.bashrc. Hvad skal man vide om databaseserveren? For at kunne etablere forbindelsen til databaseserveren er det nødvendig at vide forskellige ting. Først er det naturligvis nødvendigt at vide hvilken maskine database afvikles på. For studerende på datalogi på RUC er det en maskine der hedder isl.ruc.dk (130.225.220.116). Dernæst skal man vide på hvilken port databasen lytter efter forbindelser. Default er det post 1521 der benyttes, og det er det også på isl.ruc.dk. Det sidste man skal vide om databaseserveren er navnet på databasen, det såkaldte database SID. På isl.ruc.dk er datalogi. Dette var information om selve databasen og den maskine den afvikles på. Herudover skal man forbinde til en bruger, og skal derfor kende brugernavn og adgangskode. Brugernavne og adgangskoder i databasen har ikke noget at gøre med de brugernavne og adgangskoder man bruger for at logge på Windows, Unix og Novell. Afslutningsvis en lille opsummering over hvad man skal vide for at forbinde til en database via JDBC. maskinnavnet på den maskine databasen afvikles på den port databasen bruger til at lytte efter forbindelser navnet på databasen, det såkaldte database SID et databasebrugernavn en adgangskode for dette brugernavn På datalogi er det følgende informationer for studerende (med eksempelbrugeren SPJ fra databasekurset) 2 Hvis der allerede findes en CLASSPATH tilføjes stien til Oracles biblioteker blot med semikolon som skilletegn. F.eks. /lib/et_andet_program.jar;c:\oracle\jdbc\lib\classes12.zip

maskinnavn: isl.ruc.dk (130.225.220.116) port: 1521 database SID: datalogi databasebruger: spj adgangskode: spj JDBC Connection Nu til det der er det interessante, nemlig forbindelsen til Oracle via JDBC. For at benytte JDBC bibliotekerne er det nødvendigt at importere biblioteket i programmet. Dette gøres med erklæringen import java.sql.*; Det næste der skal ske er at fortælle Java hvor den specifikke implementering for Oracle findes, dette gøres med følgende erklæring Class.forName("oracle.jdbc.driver.OracleDriver"); Dette skal, i vanlig Java stil, foregå i en try-catch blok //Vi skal bruge Connection objected senere så vi erklærer det // udenfor try-catch blokken Connection conn = null; // registrer Oracle driveren Class.forName("oracle.jdbc.driver.OracleDriver"); // etabler forbindelsen conn = DriverManager.getConnection( "jdbc:oracle:thin:@isl.ruc.dk:1521:datalogi", "spj", "spj"); catch(classnotfoundexception e) { System.out.println("Ups, kunne ikke finde Oracles biblioteker!!! "); System.exit(-1); catch(sqlexception e) { // Oracle fejl // Udskriv en fejlbesked ala Oracle System.out.println( "ORA-" + e.geterrorcode() + ": " + e.getmessage()); // Afbryd programafviklingen System.exit(-1); Det var det hele. Nu er der etableret en forbindelse til databasen datalogi på isl.ruc.dk til brugeren SPJ.

JDBC Statement og ResultSet Nu har vi SPJ i vores hule JDBC-hånd og kan begynde at gøre noget. Lad os, for ikke at blive alt for kåde, lave en simpel SQL forespørgsel SELECT sno from SPJ; I JDBC oprettes der først et statement objekt, som hentes fra Connection objektet med funktionskaldet Statement stmt = conn.createstatement(); dernæst eksekveres forespørgslen og et ResultSet-objekt returneres med funktionskaldet ResultSet rset = stmt.executequery("select sno FROM SPJ"); 3 nu har vi resultatet i et ResultSet object og kan manipulere med det. En oplagt begyndelse er at skrive det ud while (rset.next ()) System.out.println(rset.getString("SNO")); Det skal naturligvis også afvikles i en try-catch blok Statement stmt = conn.createstatement(); ResultSet rset = stmt.executequery("select sno FROM SPJ"); while (rset.next ()) System.out.println(rset.getString("SNO"); catch(sqlexception e) { // fejlhåndtering Det første JDBC program Det første JDBC program, lige til at klippe ud og prøve. import java.util.*; import java.io.*; import java.sql.*; class Test { 3 BEMÆRK!!! der SKAL ikke noget semikolon efter SQL-forespørgslen.

public static void main(string[] args) { Connection conn = null; Class.forName("oracle.jdbc.driver.OracleDriver"); conn = DriverManager.getConnection( "jdbc:oracle:thin:@isl.ruc.dk:1521:datalogi", "spj", "spj"); catch(classnotfoundexception e) { System.out.println( "Ups, kunne ikke finde Oracles biblioteker!!! "); System.exit(-1); catch(sqlexception e) { System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); System.exit(-1); System.out.println( Connected!!! ); Statement stmt = conn.createstatement(); ResultSet rset = stmt.executequery("select sno FROM SPJ"); while (rset.next ()) System.out.println(rset.getString("SNO")); catch(sqlexception e) { System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage());

SPJ databaseapplikation Nu ved vi hvordan det skal gøres, så lad os stille lidt større krav, end blot at kunne forbinde og hente nogle værdier i en tabel. Nu skal vi designe en databaseapplikation der kan finde og vise information fra SPJ tabellerne. Input til applikationen skal være et navn og output en række informationer om denne person. Lad os konstruere applikationen således at den starter op og beder om et navn, viser informationerne og beder om et nyt navn, indtil vi taster exit. Da vi måske ikke kan huske hvilke navne der er i SPJ tilføjer vi kommandoen list der viser alle navne i SPJ. Forbindelsen til databasen er det samme som i vores første program, vi udvider den blot lidt ved at lave en funktion der klare det hele. public boolean connecttodatabase(string user, String password, String server, String port, String SID) Funktionen returnere sandt eller falsk alt efter om det lykkedes at oprette forbindelsen. public boolean connecttodatabase(string user, String password, String server, String port, String SID) { // registrer Oracle driveren Class.forName("oracle.jdbc.driver.OracleDriver"); // etabler forbindelsen conn = DriverManager.getConnection( "jdbc:oracle:thin:@"+server+":"+port+":"+sid, user, password); catch(classnotfoundexception e) { System.out.println( "Ups, kunne ikke finde Oracles biblioteker!!! "); System.exit(-1); catch(sqlexception e) { // Hvis der opfanges en exception har vi ikke fået forbindelse System.out.println("Error: no connection to database"); // Udskriv en fejlbesked ala Oracle System.out.println( "ORA-" + e.geterrorcode() + ": " + e.getmessage()); // returner falsk return false; // hvis vi kommer hertil gik alting godt woooow return true; En anden nyttig hjælpefunktion læser en linie input fra stdin String getinput() {

// Opret en bufferedreader der læser en linie fra System.in BufferedReader d = new BufferedReader(new InputStreamReader(System.in)); // Returner linien return d.readline(); catch(ioexception e) { // Udskriv exception informationer System.out.println(e.toString()); // Uuuppps, noget gik galt!!! return new String(""); Nu skal vi bare lave en funktion der looper indtil vi giver den kommandoen exit public void commandloop() Det første der sker i denne funktion er at vi laver løkken while(true) { hvori alt skal foregå. Vi starter med at skrive hvilke muligheder der er System.out.print("Indtast navn, \"list\" for en liste af mulige navne eller \"exit\" for at afslutte.\n> "); Der er altså tre forskellig muligheder. Enten er det kommandoen list eller exit eller også er det et navn. Vi begynder med at vente på input fra brugeren String name = getinput(); herefter er det naturligt først at tjekke om applikationen skal afsluttes if(name.tolowercase().equals("exit")) break; // afbryd den uendelige løkke hvis ikke det var et exit så kan det være et list else if(name.tolowercase().equals("list")) { String sql = "select sname from s order by 1"; // opret et statement Statement stmt = conn.createstatement(); // få et resultset fra statement udfra den givne kommando ResultSet rset = stmt.executequery(sql); while (rset.next ()) { String data = rset.getstring(1); // udskriv data System.out.println(data); catch(sqlexception e) {

// hvis kommandoen ikke kunne udføres, så udskriv en fejlbesked System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); Her er der ikke rigtigt noget nyt, vi udskriver blot attributten fra en tabel. Sidste mulighed er lidt mere krævende. Vi kunne naturligvis blot hælde informationerne ud i hovedet på brugeren i et eller andet tilfældigt format, men nej, vi vil gerne lave en brugervenlig databaseapplikation. Målet er at lave et output som i SQLPlus Kolonne1 Kolonne2 Kolonne3 Kolonne4 -------- -------- -------- -------- data1 data2 data3 data4 hvor hver kolonne har en fast bredde og rækkerne vises formateret i forhold til kolonnerne. For at kunne dette er det nødvendigt at kende antallet af kolonner og deres bredde. Hvis vi vælger at forespørgslen for et givet navn er følgende String sql = "select SNAME, JNAME, J.CITY, PNAME, COLOR, WEIGHT, P.CITY " + "from SPJ, S, P, J " + "WHERE SPJ.SNO = S.SNO " + "AND SPJ.PNO = P.PNO " + "AND SPJ.JNO = J.JNO " + "AND SPJ.SNO = S.SNO " + "AND lower(sname) = lower('" + name + "')"; så ved vi at der er 7 kolonner i svaret og vi kan finde de enkelte kolonners bredde ved at undersøge de tabeller de kommer fra, f.eks. vil en DESCRIBE af tabellen S i SQLPlus vise følgende SQL> describe s Navn NULL? Type ----------------------------------------- -------- ---------- SNO NOT NULL CHAR(3) SNAME CHAR(7) STATUS NUMBER(38) CITY CHAR(8) hvor vi kan se at SNAME maksimalt kan være 7 tegn, etc. Denne metode er meget omstædigt og specifikt for den enkelte forespørgsel. Bestemmer man sig for at ændre i forespørgslen vil alt det man har lavet muligvis skulle laves om. Derfor vil vi gerne opfinde en mere generel løsning. Heldigvis er der i JDBC indført funktionalitet der kan udtrække metainformation om den enkelte forespørgsel. Dette gøres ved at oprette et ResultSetMetaData-objekt fra det ResultSet-objekt vi får tilbage når vi eksekver forespørgslen. Forløbet vil se således ud Statement stmt = conn.createstatement(); ResultSet rset = stmt.executequery(sql); ResultSetMetaData meta = rset.getmetadata(); Vi kan så begynde med at finde ud af hvor mange kolonner der er i svaret

int cols = meta.getcolumncount(); hvilket jo skal være 7 for denne forespørgsel. Vores udskrivning er liniebaseret, hvilket betyder at vi skal producere vores output linie for linie. Den første linie i output skal være en linie med kolonnenavnene placeret i forhold til den bredde kolonnerne har SNAME JNAME CITY PNAME COLOR WEIGHT CITY Vi har derfor brug for viden om kolonnebredden og opretter et array der kan holde denne viden fordi den skal bruges senere int size[] = new int[cols]; herefter er det blot en for-løkke der udskriver kolonnenavnene i forhold til deres bredde. Det første vi gør er at indsætte kolonnebredden i vore array size[i] = meta.getcolumndisplaysize(i+1); herefter udskrives kolonnenavnet String label = meta.getcolumnlabel(i+1); // skriv navnet ud (NB: uden linieskift) System.out.print(label); hvis kolonnenavnet har færre tegn end kolonnebredden så må vi fylde ud med nogle mellemrum for(int j = 0; j < size[i]-label.length(); j++) og derefter huske at lave et mellemrum mellem kolonnerne. For-løkken ser således ud i sin helhed // løb gennem alle kolonner for(int i = 0; i < cols; i++) { // først gem den displaysize der findes om kolonnen size[i] = meta.getcolumndisplaysize(i+1); // Hent kolonnens navn String label = meta.getcolumnlabel(i+1); // skriv navnet ud (NB: uden linieskift) System.out.print(label); // Hvis displaysize er større end kolonnenavnet // så udskriv nogle mellemrum // så det næste navn kommer til at stå det rigtige sted for(int j = 0; j < size[i]-label.length(); j++) // Lav et mellemrum mellem kolonnerne

Dernæst skal der udskrives en stiplet linie uden alle kolonnenavnene, hvilket gøres ved først at udskrive et linieskift og så løbe arrayet med kolonnebredder igennem og udskrive et passende antal streger for hver kolonne for(int i = 0; i < cols; i++) { for(int j = 0; j < size[i]; j++) System.out.print("-"); SNAME JNAME CITY PNAME COLOR WEIGHT CITY ------- ---------- -------- ------- ------- --------------------- -------- Så mangler vi bare at udskrive rækkerne i svaret. Vi vil gerne kunne udskrive hvor mange rækker der udskrives i alt så derfor opretter vi en variabel til at tælle rækkerne int rows = 0; herefter hentes rækkerne en af gangen som vi kender det while(rset.next ()) { // udskriv række for hver række udskrives data i de enkelte kolonner og der fyldes ud med mellemrum hvis indholdet i kolonnen ikke fylder hele kolonnebredden. Når alle data i kolonnerne er udskrevet udskrives et linieskift og rækketælleren tælles op med 1. for(int i = 0; i < cols; i++) { // Hent data som en streng String data = rset.getstring(i+1); // udskriv data System.out.print(data); // fyld ud med mellemrum hvis data ikke fylder hele kolonnebreden for(int j = 0; j < size[i]-data.length(); j++) // mellemrum mellem kolonnerne // linieskift mellem rækkerne rows++; Til allersidst udskrives antallet af valgte rækker System.out.println("\n" + rows + " rækker er valgt.\n"); Nu ser vores output ud som vi gene vil have det SNAME JNAME CITY PNAME COLOR WEIGHT CITY ------- ---------- -------- ------- ------- --------------------- -------- Blake Sorter Paris Screw Blue 17 Rome

Blake Punch Rome Screw Red 14 London 2 rækker er valgt. SQLPlus databaseapplikation Det sidste eksempel er en lille letvægtsudgave af SQLPlus der viser at man også kan håndtere dynamisk SQL via JDBC. En dynamisk SQL-forespørgsel er en forespørgsel hvor vi ikke på forhånd ved noget om hvordan svaret ser ud. Denne applikation er også et eksempel på at man ved at tænke sig om under udviklingen af applikationer kan spare tid næste gang man stå med et lignende problem. Flere at de ændringer der blev indført i SPJ-applikationen kan genbruges i denne applikation. Det første problem der skal løses er at man ved opstarten af denne SQLPlus-applikation skal have mulighed for at vælge hvilken database man ønsker at arbejde med. For at skabe forbindelse via JDBC er det nødvendig at give følgende informationer databaseserveren porten der lyttes på database SID brugernavn adgangskode Tidligere indførte vi et funktionskald der håndterede login med netop disse informationer som parametre og vi kan derfor genbruge denne. Vi skal blot bede om informationerne hos brugeren i stedet for at hardcode dem ind i programmet. // database server navn System.out.print("Database server: "); String server = getinput(); // TNS port System.out.print("Port: "); String port = getinput(); // database SID System.out.print("Database SID: "); String SID = getinput(); // user System.out.print("User: "); String user = getinput(); // password System.out.print("Password: "); String password = getinput(); // etabler forbindelsen til databasen if(connecttodatabase(user, password, server, port, SID) == false) {

System.out.println( "Kunne ikke etablere forbindelse til databasen!!!"); System.exit(-1); // afslut hvis det ikke lykkedes Vi beder om informationerne én af gangen og forsøger så at etablere forbindelsen 4. Lykkedes dette ikke afsluttes programmet. Det første vi gør er lige at udskrive lidt information om den databaseserver vi er forbundet til. Dette gøres ved at oprette et DatabaseMetaData-objekt og udskrive databasens versionsnummer // Her hentes metainformationer fra forbindelsen DatabaseMetaData meta = conn.getmetadata(); System.out.println("Forbindelse er oprettet til :\n"); // Lad os udskrive noget a la den ægte "SQLPlus" System.out.println(meta.getDatabaseProductVersion()); Herefter skal brugerne præsenteres for en SQL-prompt, hvor det er muligt at indtaste SQL. Denne version er begrænset til kun at acceptere en linie SQL, hvilket betyder at applikationen forsøger at afvikle forespørgslen når der tastes enter. Vi skal som i forgående applikation ind i en uendelig løkke der fortolker input fra brugeren. Der er to muligheder enten er det et exit eller også er det en forespørgsel. Dvs. alt der ikke er strengen exit fortolkes som SQL. public void commandloop() { while(true) { // loop i al evighed // Lav en pæn prompt System.out.print("SQL> "); // Hent en linie fra brugeren String cmd = getinput(); if(cmd.charat(cmd.length()-1) == ';') cmd = cmd.substring(0, cmd.length()-1); // har vi et farvel???? så afslut loopet if(cmd.tolowercase().equals("exit")) break; else // behandl kommandoen HandleCommand(cmd); Det væsentlige i denne funktion er at udskrive SQL-prompten og vente på input fra brugeren. Herefter at fjerne et eventuelt semikolon i enden, fordi det giver en fejl, tjekke om det er strengen exit eller kalde en funktion der håndtere SQL fortolkningen. Vi har tidligere lavet en generel udskrivning af resultatet af en forespørgsel, så funktionskaldet HandleCommand er blot en gentagelse af dette. Der er naturligvis en lang række funktioner og problemer der ikke håndteres af denne letvægtsudgave af SPLPlus, men formålet var også blot at vise at man med JDBC er i stand til at konstruere temmelig komplekse databaseapplikationer med en forholdsvist simpel grænseflade til databasen. 4 Bemærk at vi også genbruger funktionen getinput() til at hente input fra brugeren.

Afrunding JDBC er generelt og derfor ikke afhængig af hvilken database der forbindes til. Erstattes de to første linier Class.forName("oracle.jdbc.driver.OracleDriver"); // etabler forbindelsen conn = DriverManager.getConnection( "jdbc:oracle:thin:@"+server+":"+port+":"+sid, user, password); vil resten af koden kunne benyttes på en anden database 5. JDBC er veldokumenteret og der er mange eksempler at hente ude omkring i verden, hvis der er noget man er i tvivl om. Den generelle dokumentation findes i dokumentationen til JDK 6. Herudover findes der en JDBC-tutorial på http://java.sun.com/docs/books/tutorial/jdbc/toc.html som er et oplagt sted at begynde. Et vigtigt hint i forbindelse med udviklingen af databaseapplikationer er hvordan man fejlfinder når koden pludselig indeholder 2 sprog. Fejl kan opstå både i Java og i SQL eller i begge, og det er ofte svært at afgøre om det er den ene eller anden del af programmet der fejler. Hvis der opstår problemer omkring Java-SQL integrationen er det en rigtig god ide at udskrive de SQL-statements der skal afvikles og prøve at køre dem i SQLPlus (den ægte ). Dels er man sikker på at det så ikke er her fejlen opstår, og dels er der ofte mere information om fejlen (der refereres til hvor i udtrykket fejlen er opstået). På denne måde kan man fokusere på de enkelt dele af applikationen for sig og derfor ikke komme til at lede efter noget i den ene del som i virkeligheden skyldes den anden. (Her tales med bitter erfaring) 5 Hvis man vil lave programmer der er fuldstændig kompatible mellem alle database bør man frekventere beskrivelsen af JDBC på http://java.sun.com/products/jdbc/, hvor det defineres hvilke funktionskald der skal implementeres og hvilke man kan undlade som driver-udvikler. 6 http://developer.java.sun.com/developer/infodocs/

Kildekode SPJ-applikationen import java.util.*; // string, etc. import java.io.*; // input, output import java.sql.*; // sql tingene class SPJ { Connection conn; public SPJ() { // etabler forbindelsen til databasen if(connecttodatabase("spj", "spj", "isl.ruc.dk", "1521", "datalogi") == false) { System.out.println("Kunne ikke etablere forbindelse til databasen!!!"); System.exit(-1); // afslut hvis det ikke lykkedes // start commandloop(); // Funktion der kan modtage input fra brugeren public void commandloop() { while(true) { // loop i al evighed // Lav en pæn prompt System.out.print("Indtast navn, \"list\" for en liste af mulige navne " + " eller \"exit\" for at afslutte.\n> "); // Hent en linie fra brugeren String name = getinput(); // afslut programmet hvis dre tastes "exit" if(name.tolowercase().equals("exit")) break; // udskriv en liste af navne i s else if(name.tolowercase().equals("list")) { String sql = "select sname from s order by 1"; // opret et statement Statement stmt = conn.createstatement(); // få et resultset fra statement udfra den givne kommando ResultSet rset = stmt.executequery(sql); while (rset.next ()) { String data = rset.getstring(1); // udskriv data System.out.println(data); catch(sqlexception e) { // hvis kommandoen ikke kunne udføres, så udskriv en fejlbesked System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); // find informationer om personen else { // Først finde vi lige String sql = "select SNAME, JNAME, J.CITY, PNAME, COLOR, WEIGHT, P.CITY " + "from SPJ, S, P, J " + "WHERE SPJ.SNO = S.SNO " + "AND SPJ.PNO = P.PNO " + "AND SPJ.JNO = J.JNO " + "AND SPJ.SNO = S.SNO " + "AND lower(sname) = lower('" + name + "')"; // opret et statement Statement stmt = conn.createstatement(); // få et resultset fra statement udfra den givne kommando

ResultSet rset = stmt.executequery(sql); // information om forespørgslen som vi senere bruger til at formatere output ResultSetMetaData meta = rset.getmetadata(); // hvis der er noget at skrive ud, så gør det pænt if(rset.next ()) { // natallet af kolonner i svaret. Vi kender naturligvis godt dette antal, men // hvis vi ændre i forespørgslen så behøver vi ikke lave om på andet fordi resten // håndteres automatisk int cols = meta.getcolumncount(); // vi opsamler lige den brede kolonnerne har så vi kan lave et rigtigt pænt output int size[] = new int[cols]; // løb gennem alle kolonner for(int i = 0; i < cols; i++) { // først gem den displaysize der findes om kolonnen size[i] = meta.getcolumndisplaysize(i+1); // Hent kolonnens navn String label = meta.getcolumnlabel(i+1); // skriv navnet ud (NB: uden linieskift) System.out.print(label); // Hvis displaysize er større end kolonnenavnet så udskriv nogle mellemrum // så det næste navn kommer til at stå det rigtige sted for(int j = 0; j < size[i]-label.length(); j++) // Lav et mellemrum mellem kolonnerne // det var så kolonnenavnene // her laver vi lige noget lir. // vi udskriver en streg i hele kolonnens brede // KOL1 KOL2 KOL3 // ---- ------- ---- // 1 2 4 // // flot ik'?? for(int i = 0; i < cols; i++) { for(int j = 0; j < size[i]; j++) System.out.print("-"); // og så lige en ny linie int rows = 0; do { for(int i = 0; i < cols; i++) { // Hent data som en streng String data = rset.getstring(i+1); // udskriv data System.out.print(data); // fyld ud med mellemrum hvis data ikke fylder hele kolonnebreden for(int j = 0; j < size[i]-data.length(); j++) // mellemrum mellem kolonnerne // linieskift mellem rækkerne rows++; while(rset.next ()); // skriv hvor mange rækker der blev valgt System.out.println("\n" + rows + " rækker er valgt.\n"); else // den person vi søge findes ikke System.out.println("Navnet \"" + name + "\" findes ikke!!!"); catch(sqlexception e) { // hvis kommandoen ikke kunne udføres, så udskriv en fejlbesked System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); // En lille hjælpefunktion der læser en linie fra inputstream

String getinput() { // Opret en bufferedreader der læser en linie fra System.in BufferedReader d = new BufferedReader(new InputStreamReader(System.in)); // Returner linien return d.readline(); catch(ioexception e) { // Udskriv exception informationer System.out.println(e.toString()); // Uuuppps, noget gik galt!!! return new String(""); // Funktion der skaber forbindelsen til databasen public boolean connecttodatabase(string user, String password, String server, String port, String SID) { // registrer Oracle driveren Class.forName("oracle.jdbc.driver.OracleDriver"); // etabler forbindelsen conn = DriverManager.getConnection( "jdbc:oracle:thin:@"+server+":"+port+":"+sid, user, password); catch(classnotfoundexception e) { System.out.println("Ups, kunne ikke finde Oracles biblioteker!!! "); System.exit(-1); catch(sqlexception e) { // Hvis der opfanges en exception har vi ikke fået forbindelse System.out.println("Error: no connection to database"); // Udskriv en fejlbesked ala Oracle System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); // returner falsk return false; // hvis vi kommer hertil gik alting godt woooow return true; public static void main(string[] args) { new SPJ(); // Opret et SPJ objekt SQLPLus-applikationen import java.util.*; // string, etc. import java.io.*; // input, output import java.sql.*; // sql tingene // Læg mærke til at der ikke skal refereres til Oracles // JDBC klasser. Det håndteres af DriverManager, derfor stien til // Oracles driver!!! // En lille klasse der er en meget letvægts udgave // af Oracles SQLPlus class SQLPlus { Connection conn; public SQLPlus() { // database server navn System.out.print("Database server: "); String server = getinput();

// TNS port System.out.print("Port: "); String port = getinput(); // database SID System.out.print("Database SID: "); String SID = getinput(); // user System.out.print("User: "); String user = getinput(); // password System.out.print("Password: "); String password = getinput(); // etabler forbindelsen til databasen if(connecttodatabase(user, password, server, port, SID) == false) { System.out.println("Kunne ikke etablere forbindelse til databasen!!!"); System.exit(-1); // afslut hvis det ikke lykkedes // Eksempel på hvordan der kan hentes information fra databasen // Her hentes metainformationer fra forbindelsen DatabaseMetaData meta = conn.getmetadata(); System.out.println("Forbindelse er oprettet til :\n"); // Lad os udskrive noget a la den ægte "SQLPlus" System.out.println(meta.getDatabaseProductVersion()); catch(sqlexception e) { System.out.println("Kunne ikke hente database metainformationer!!!!"); // gør klar til at modtage kommandoer fra brugeren commandloop(); // Funktion der skaber forbindelsen til databasen public boolean connecttodatabase(string user, String password, String server, String port, String SID) { // registrer Oracle driveren Class.forName("oracle.jdbc.driver.OracleDriver"); // etabler forbindelsen conn = DriverManager.getConnection( "jdbc:oracle:thin:@"+server+":"+port+":"+sid, user, password); catch(classnotfoundexception e) { System.out.println("Ups, kunne ikke finde Oracles biblioteker!!! "); System.exit(-1); catch(sqlexception e) { // Hvis der opfanges en exception har vi ikke fået forbindelse System.out.println("Error: no connection to database"); // Udskriv en fejlbesked ala Oracle System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); // returner falsk return false; // hvis vi kommer hertil gik alting godt woooow return true; // En lille hjælpefunktion der læser en linie fra inputstream String getinput() { // Opret en bufferedreader der læser en linie fra System.in BufferedReader d = new BufferedReader(new InputStreamReader(System.in)); // Returner linien

return d.readline(); catch(ioexception e) { // Udskriv exception informationer System.out.println(e.toString()); // Uuuppps, noget gik galt!!! return new String(""); // Her håndteres alle kommandoer undtagen exit public void HandleCommand(String cmd) { // opret et statement Statement stmt = conn.createstatement(); // få et resultset fra statement udfra den givne kommando ResultSet rset = stmt.executequery(cmd); // Nu er al database aktiviteten afsluttet, dvs. SQL kommandoen er // udført. Tilbage er blot at hente resultatet ud af databasen // da SQL er dynamisk ved vi ikke hvad vi skal skrive ud. Så det må vi // først lige finde ud af??? // Dette gøres ved at hente metadata fra ResultSet der ved noget om kolonnerne. ResultSetMetaData meta = rset.getmetadata(); // lad os lige lave et pænt linieskift så der kommer mellemrum fra // prompten til resultatet // det første vi skal finde ud af er hvor mange kolonner der er i resultatet int cols = meta.getcolumncount(); // vi opsamler lige den brede kolonnerne har så vi kan lave et rigtigt pænt output int size[] = new int[cols]; // løb gennem alle kolonner for(int i = 0; i < cols; i++) { // først gem den displaysize der findes om kolonnen size[i] = meta.getcolumndisplaysize(i+1); // Hent kolonnens navn String label = meta.getcolumnlabel(i+1); // skriv navnet ud (NB: uden linieskift) System.out.print(label); // Hvis displaysize er større end kolonnenavnet så udskriv nogle mellemrum // så det næste navn kommer til at stå det rigtige sted for(int j = 0; j < size[i]-label.length(); j++) // Lav et mellemrum mellem kolonnerne // det var så kolonnenavnene // her laver vi lige noget lir. // vi udskriver en streg i hele kolonnens brede // KOL1 KOL2 KOL3 // ---- ------- ---- // 1 2 4 // // flot ik'?? for(int i = 0; i < cols; i++) { for(int j = 0; j < size[i]; j++) System.out.print("-"); // og så lige en ny linie // vi vil gerne kunne fortælle brugeren hvor mange rækker vi har udskrevet int rows = 0; // Hent rækkerne i databasen while (rset.next ()) { for(int i = 0; i < cols; i++) { // Hent data som en streng String data = rset.getstring(i+1); // udskriv data System.out.print(data); // fyld ud med mellemrum hvis data ikke fylder hele kolonnebreden for(int j = 0; j < size[i]-data.length(); j++) // mellemrum mellem kolonnerne

// linieskift mellem rækkerne // tæl antallet af rækker 1 op rows++; // skriv hvor mange rækker der blev valgt System.out.println("\n" + rows + " rækker er valgt.\n"); catch(sqlexception e) { // hvis kommandoen ikke kunne udføres, så udskriv en fejlbesked System.out.println("ORA-" + e.geterrorcode() + ": " + e.getmessage()); // Funktion der kan modtage input fra brugeren public void commandloop() { while(true) { // loop i al evighed // Lav en pæn prompt System.out.print("SQL> "); // Hent en linie fra brugeren String cmd = getinput(); // Hvis brugeren har skrevet et ";" til sidst så fjern det!!!! // det giver nemlig problemer, idet der ikke må være ";" i enden af de // sql-statements der sendes til databasen via JDBC. Dette er en klassisk // fejlkilde som adskillige store programmører er faldet i gennem tiderne. // (inkl. undertegnede) if(cmd.charat(cmd.length()-1) == ';') cmd = cmd.substring(0, cmd.length()-1); // har vi et farvel???? så afslut loopet if(cmd.tolowercase().equals("exit")) break; else // behandl kommandoen HandleCommand(cmd); // Det er her det hele begynder... public static void main(string[] args) { // Vi opretter en instans af vores SQLPlus class og // så klare den resten new SQLPlus();