Tallinna Pedagoogikaülikool
Haapsalu Kolledž
Rakenduste
programmeerimine
Jaagup Kippar
2004
Sisukord
Eessõna 7
Andmebaasid 8
Andmebaasiühenduse loomine 8
Otsene draiver 11
Servlet 12
Sisestus 14
Pilt servleti väljundina 19
Servlet ja andmebaas. 20
JSP 20
Tsükkel 22
Teate kaasamine 24
Kommentaarid 24
Uba 25
Uba ja andmebaas 27
JDBC käskude ülevaade 31
Kõikide ridade väljastus. 31
Andmed andmete kohta 31
Päringu tulemuste hulgas liikumine. 33
Päringu mahu piiramine 34
Lisamine. 34
PreparedStatement 35
Päringus lisamine 35
Transaktsioonid 36
SQL-laused 36
Kaks tabelit 39
Lauluandmetega rakendus 41
Standardile vastav lehekülg. 43
Sortimine 45
Andmete lisamine 47
Mitme tabeliga rakendus 49
Kustutamine 54
Laulude haldus 57
Ülesandeid 61
Söökla menüü 61
Bussiplaan 61
Kirjasõprade otsing sünniaasta järgi 61
Kirjasõprade otsing huviala järgi. 61
Autovaruosade otsing 61
Telefoninumbrite märkmik 61
Veahaldusvahend 62
Korrapidajate tabel 62
Linnuvaatlusmärkmik 62
Taimevaatlusmärkmik 62
Putukate kogu 62
Loodusfotograafi märkmik 62
Ilmavaatlusandmed 63
Videokahuri kasutusgraafik 63
Raamatukogulaenutus 63
Tööaja arvestusgraafik 63
Komandeeringuaruanded 63
Uksed 64
Laulude andmebaas 64
Vilistlaste kontaktandmed 64
Tunniplaan 64
Jõe vooluhulgad 64
Ülesannete kogu 65
Koodinäidete kogu 65
Failipuu 65
Baasipõhine failipuu 65
Restorani ladu 65
Tähed muusikas 66
Kuldvillak 66
Eurovisiooni hääletus 66
Miljonimäng 66
Kahevõitlus 66
7 vaprat 67
Kuldlaul 67
Autoregister 67
Õppetooli raamatukogu 67
J2ME 68
Demonstratsiooniprogrammid 68
Omaloodud projekt 69
Tervitav programm 69
Kalkulaator 71
Tehtevalikuga kalkulaator 72
Joonistused 74
Üksik joon 74
Mitmekülgsem joonis 75
Kaks joonist 76
Liigutamine 77
Liikumine 79
Andmed veebist 80
Salvestus. 81
Loendur 81
Neljabaidine salvestus 82
Salvestus vormist 83
Mitu ekraanivormi 84
Ülesandeid 88
Mobiiliprogrammidega tutvumine. 88
Hinnaotsing 88
Joonistus 88
Aardepüüdmismäng 88
Munapüüdja 89
Salvestusrakendus 89
Kaart 89
Veebirakenduse mobiililiides 89
XML 90
XSL 90
Käivitamine 91
Ühenimelised elemendid 92
Andmed tabelina 93
Mallid 94
Tekstikontroll 95
XSL-i funktsioone 95
Sõnefunktsioonid 96
Parameetrid 96
Ülesandeid 98
XML 98
Andmepuu 98
XSL 98
Sugupuu 98
XML ja kassid 99
XML ja koerad 99
DOM 99
Joonistusvahend 101
SAX 104
Nimede loendur 104
Elementide sisu 105
Turvalisus 107
Signeerimine 107
Digitaalallkiri 110
Sõnumilühend 112
Programmi õigused 112
Omaloodud turvahaldur 115
Turvahalduri õiguste määramine 116
Hoiatusribaga aken 117
Valikuline õiguste loetelu 118
Atribuudid (Properties) 118
Krüptimine 121
Üks plokk 121
Šifreereeritud voog 121
Parooliga krüptimine 123
Ülesandeid 124
Signeerimine 124
Krüptograafia 124
Digitaalallkiri 124
Hajusrakendused 125
RMI 125
Lihtsaim näide 125
Käivitamise juhend 126
Seiskumisvõimeline server 126
Nime hoidmine serveris 127
Tagasisidega ühendus 128
Sünkroniseeritud lisamine ja eemaldamine 130
Ülesandeid 131
RMI tutvus 131
Oksjon 131
EJB 132
Liides 132
Koduliides 132
Realiseeriv objekt 132
Kompileerimine 133
Keskkonna käivitus 133
Ülespanek 134
Klient 138
Servleti installeerimine J2EE serverisse 139
Andmehaldus 142
Bitid 142
Bitinihutuskrüptograafia 143
Baidi bitid failist. 143
Bitikaupa failikirjutus 144
Bitiväljundvoog 145
Bittide sisendvoog failist. 146
Kokkuvõtteks 147
Ülesandeid 147
Bitid 147
Bitimuster 147
DNA ahela pakkimine 147
Bitinihutus 148
Andmestruktuurid 148
Nimistu 148
Üksik rakk 149
Seotud rakud 149
Pikem ahel 149
Vähem muutujaid 149
Ahela läbimine tsükliga 150
Väljatrükk 150
Vahelepanek 151
Järjestamine 151
Ülesandeid 154
Pinu 154
Järjekord 154
Osutiring 154
Kahendpuu 155
Üksik sõlm 155
Kahe haruga puu 155
Rekursioon 156
Järjestamine 157
Otsimine 158
Kokkuvõtteks 159
Ülesandeid 160
Andmepuu 160
Trepitud kahendpuu 160
Morse 160
Keele võimalused 161
Jar-arhiivid 161
Paketid 163
Erindid 165
Omaloodud erind 165
Lõpuplokk finally 166
Kloonimine 168
Süviti kloonimine. 169
Ülesandeid 170
Klasside uuring koodiga 171
Käivitamine nime järgi 174
Ülesandeid 174
JUnit, automaattestimine 174
Testide kogum 176
Ülesandeid 176
Tarkvara hindamine. 177
Mõõdetavad suurused 177
Vead 180
Testid 180
Laused ja harud 180
Juhuslikud andmed 181
Koodi analüüs, tüüpvead 181
Moodulite ühendamine 181
Vigade hulga hindamine 182
Testimise maksumus 182
Vigade põhjalikum püüdmine 182
Autori analüüs 183
Läbivaatus 183
Audit 183
Testimise korraldus 183
Ülesandeid 186
Järelsõna 187
Eessõna
Siinsesse konspekti koguti programmeerimise ja Javaga seotud teemad, mis oma pikkuse tõttu ei sobinud
sama autori koostatud Java põhikursuse konspekti ning teemavaliku poolest ei kuulunud omaette
konspektiks kirjutatud graafika või muusika programmeerimise alla. Mis aga on samas piisavalt tähtsad, et
neist maakeelne tutvustav ülevaade anda. Ehkki koostamise aluseks oli kriteerium "kõik, mis muust üle
jääb", leiab ka siit sisukorrast mõned suuremad.
Tähtsamaks ja tõenäoliselt enam kasutatavamaks osaks võiks olla relatsiooniliste
andmebaasidega seonduv. Aastakümnel, kus üha enam teateid ja dokumente liigub arvuti kaudu,
vajatakse ka taoliste süsteemide loojaid ja ülalpidajaid. Ning ehkki vähemasti kümmekond aastat juba
räägitakse, et relatsioonilised baasid ja objektorienteeritud programmeerimismudel on omavahel
vastuolus, tundub selline tava vähemasti mõnda aega veel püsima ning vähemalt osa praegu loodud
süsteeme töötab ka veel aastate pärast nagu seniste kogemuste varal arvata võib.
Teiseks suuremaks teemaks on Java 2 Micro Edition. Siinsed näited koostati mobiiltelefonide
emulaatoreid kasutades, kuid samade vahenditega saab programmeerida ka pihuarvuteid ning muidki
Javat toetavaid miniseadmeid. Ehkki tehnoloogia juba mitme aasta vanune, sai teema siia konspekti
lisamisel otsustavaks J2ME suhteliselt kiire areng 2003. aastal ning oodatav miniseadmete leviku kasv.
XMLile ja Unicodele on kuulutatud suurt võidukäiku vähemasti 1997ndast aastast alates. Järske
imesid pole sündinud, kuid standardid on levinud ning nende põhjal loodud rakendused
usaldusväärsemateks muutunud. Ning kui on vaja struktuurseid andmeid nii inimesele kui masinale
mõistetavasse vormingusse paigutada (näiteks konfiguratsioonifaili puhul), siis peab päris tugev seletus
olema, kui tahetakse põhjendada, miks nende andmete hoidmiseks just XML-vormingut ei kasutatud.
Vaadatakse läbi levinumad XMLi programmse töötlemise moodused: SAX ning DOM. Esimene
mahukatest dokumentidest üksikute väärtuste eraldamiseks, teine andmepuu loomiseks, muutmiseks ja
põhjalikumaks analüüsiks.
Hajutatud rakendused on omaette suurem maailm. Nendest peetakse vähemasti nii Tartu
Ülikoolis kui Tehnikaülikoolis omaette kursusi. Siin on vaadeldud tehnilisi lahendusi, mille kaudu
õnnestub hajusalt paiknevalt osad omavahel suhtlema panna. J2EE serveriga tutvutakse vaid Sun-i
näidiskeskkonna abil, kuid siin saadud kogemusi meenutades peaks mujalt loetava materjali külge olema
kergem haakuda.
Läbi vaadatakse ka programmeerimiskursustes traditsioonilised teemad: nimistu ja andmepuu,
samuti bititöötlus. Nende teemade juures ei püüta pakkuda midagi uut, küll aga peaks olema tegemist
kõlbuliku lugemismaterjaliga inimesele, kes Java vahenditega ümber käies on jõudnud kaugusele, kus
vastavad toimingud arusaadavaks ja tarvilikuks muutuvad. Samuti õnnestub nende peatükkide
omandamise järel ehk paremini mõista muidki puukujuliste andmetega tegelevaid algoritme. Olgu siis
tegemist failipuuga kettal või XMLi puuga mälus.
Paarileheküljeliste lõikude kaudu tutvutakse mitmete Java tehniliste võimalustega. Koodi
jagamine pakettidesse aitab suuremate rakenduste puhul seda kergemini hallata ning eri loojate koodi
ühendada. Arhiveerimine aitab lihtsalt ruumi kokku hoida ning mõnikord ka pildi selgemaks muuta.
Klasside käskluste uurimine programmi abil või eksemplaride loomine klassi nime järgi võib tunduda
imelik, kuid sealtkaudu õnnestub näiteks automaatselt mõningaid võimalusi dokumenteerida või saada üle
kitsaskohtadest, mille poolest kompileeritavad keeled kipuvad interpreteeritavatele alla jääma.
Viimase peatükina paigutatud tarkvara hindamine ei sisalda kuigivõrd koodinäiteid ega seletusi,
kuid seal kirjutatust võib kasu olla nii oma rakenduse kavandamisel, töökindluse hindamisel ja
parandamisel. Kõiki häid mõtteid, võimalusi ja tavasid ei jõua alati korraga oma programmis rakendada,
aga vahel proovida ikka tasub.
Jõudu!
Jaagup Kippar
Andmebaasid
SQL, ODBC, Servlet, veebirakendus
Enamik programme talletab andmeid kusagil. Üheks levinumaks väljundiks programmi poolt
vaadates on failid kettal, teiseks andmebaasid. Failide eeliseks on kohene kasutamisvalmidus. Baasiga
suhtlemise puhul peab lisaks oma rakendusele ka andmebaasiga suhtlemist võimaldav vahend masinas
leiduma. Peaaegu hädavajalikuks aga muutub andmebaas mitmelõimelise programmi korral, sest ise
korralikke lukustusmehhanisme kirjutada võib olla päris aeganõudev ettevõtmine. Samuti aitavad
andmebaaside päringuvahendid andmete keerukama ülesehituse korral sobivaid väärtusi üles leida.
Andmemudeleid ja päringukeeli leidub mitte. 2004. aastal ja sellele eelnenenud paaril aastakümnel on aga
valitsevaks relatsioonilised, tabelitest koosnevad andmebaasid ning baasidega suhtlemiseks SQL-keel.
Andmebaasiühenduse loomine
Et andmeid ja tabeleid saaks kuhugi baasi paigutada, selleks peab kõigepealt andmebaas loodud
olema. Mõnes paigas saab seda teha vaid administraatoriõigustes inimene, Windowsi masinates võib aga
sageli igaüks omale kasutamiseks-katsetamiseks andmebaasiserveri püsti panna ning sinna nii palju baase
luua kui kettamaht võimaldab. Enesele MySQLi vahendid kopeerida saab http://www.mysql.com/-i alt.
Vaja läheb nii andmebaasikeskkonda ennast kui draiverit sellega ühendumiseks.
Toimingute tegemiseks peab kõigepealt
baasiserveri käima lükkama. Käsuks
mysqld-shareware ning selle tulemusena
hakkab server kuulama väratit 3306, kui
pole määratud teisiti. Käsk mysqladmin
lubab luua ja kaotada baase ning teha
muudki korraldusega seonduvat. Siin
luuakse baas nimega pood.
Kui aga soovida lihtsamalt läbi ajada, siis ei
pea selleks mitte oma serverit püsti panema. Võib
rahulikult toime tulla olemasolevate Accessi, Exceli
või suisa tekstifaili draiveritega. Kui aga MySQL
installeeritud, siis saab seda kasutada. Küllaltki
universaalne koht andmebaasidele ligi pääsemiseks
on ControlPanel'i alt avanev ODBC. Et MySQLile
sealtkaudu ligi pääseks, on vaja installeerida vastav
draiver, näiteks Connector/ODBC, mis vabalt
kättesaadava MySQLi kodulehelt. Sealt
ControlPanel'i alt on näha, millised ressursid juba
kasutada on, samuti annab siit oma baasile ODBC
ühendus luua, mille abil siis kergesti võib
olemasolevate draiverite abil programmide kaudu
sinna andmeid saatma ja sealt pärima hakata.
Nimetatud vahelüli (Open DataBase
Connectivity) on lihtsalt ühine protokoll, mille
kaudu saavad suhelda osapooled, kes
üksteise keelt ei tunne (pole otseühenduseks
vastavaid draivereid).
Uue andmeallika loomiseks tuleb
vajutada Add... ning pakutakse toetatavatest
tüüpidest välja, millist kasutaja soovib luua.
Kui siin näites soovime ühenduda MySQL-i
baasiga, tuleb ka vastavat tüüpi draiver valida.
Draiveri juures tuleb määrata parameetrid.
Vähemasti nimi, mille alt Windows'is vastavat
andmeallikat tuntakse ning milline on tegelik
baasi nimi serveri
Kui kogu loomine läks õnnelikult, siis jõuame
tagasi algse lehe juurde, kuhu on tekkinud ka
vastloodud ühendus, siin näites nime all
poebaas.
Edasi pole muud, kui asuda loodud ühendust kasutama. Baasi sisse võib tabeleid ja andmeid
lisada mitut moodi. Accessi või Exceli puhul saab avada vastava programmi ning rahumeeli tähed ja
numbrid tabelisse kirjutada. MySQLil oma kliendi kaudu saab ka baasi külge ühenduda ning seal SQL
lausete abil soovitud muutusi tekitada. Kui tahta edaspidi panna oma programm baasi andmeid kasutama,
siis on paslik alustada lühemast käsureast, mis parajasti baasi sisse ühe üheveerulise tabeli loob ning
sinna sisse väärtuse paigutab. Võib ette kujutada, et tabelis on kirjas, mitu palli parajasti poe laos hoiul
on.
Andmebaasiga suhtlemiseks tuleb kõigepealt mällu laadida draiver. ODBC tarvis on Java
starndardkomplektis kaasas sun.jdbc.odbc.JdbcOdbcDriver. Luuakse ühendus, jättes
kasutajanime ja parooli koht tühjaks, kuna meie katsebaasi puhul neid ei nõuta. Saadetakse käsklaused
teele ning suletakse ühendus.
import java.sql.*;
public class Baasilooja1{
public static void main(String argumendid[]) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=
DriverManager.getConnection(
"jdbc:odbc:poebaas", "", "");
Statement st=cn.createStatement();
String lause="CREATE TABLE pallid (kogus int);";
st.executeUpdate(lause);
lause="INSERT INTO pallid (kogus) values ('0');";
st.executeUpdate(lause);
cn.close();
}
}
Kui programmi tekst sisse kirjutatud,
siis enne käivitamist tuleb see
kompileerida ning seejärel käima lasta.
Näidet vaadates paistab tulemus
tühjavõitu olema. Kompileerimisel ei
tulnud veateadet seetõttu, et ühtki
viga ei leitud. Käivitamisel pole midagi
näha, kuna kogu tegevus käis
programmi ja baasi vahel ning polnud
küsitud, et midagi ekraanile näidataks.
Kui väljatrükilauseid vahele pikkida,
eks siis oleks ka käivitajal rohkem
midagi vaadata olnud.
Et töö siiski päris tühi ei olnud ja
midagi ka toimus, sellest annab teada
järgmine pilt. Kui ühenduda MySQLi
kliendiga baasi taha ning uurida, mis
seal sees paikneb, siis on näha, et
tekkinud on tabel nimega pallid ning
sinna sisse on koguseks pandud 0.
Otsene draiver
ODBC võimaldab omavahel suhelda paljudel programmidel ning protokollidel, kuid selle
puuduste juurde kuulub, et tegemist on veel ühe järjekordse vahelüliga, mis enesele ressursse nõuab ning
nagu pudelikael ikka ei pruugi see mitte kõiki häid omadusi läbi lasta, mis kummalgi osapoolel olemas
võivad olla. Sellepärast, kui on tegemist tohutute andmehulkade või suurte kiirustega, on mõistlik otsida
programmi ja andmebaasi vahele otsest draiverit. Javat ning MySQLi ühendava vahendi leiab näiteks
lehelt
http://www.mysql.com/downloads/api-jdbc.html
Kui sealne arhiiv maha laadida ning lahti pakkida, tekkis kataloog, milles nii draiver ise kui hulga õpetusi,
kuidas temaga ümber käia.
Kirjadest selgus, et muuta polegi vaja muud kui draiveri nime ning ühenduse URLi. Nüüd saab
kirjutada otse jdbc:mysql: .
import java.sql.*;
public class Pallibaas2{
public static void main(String argumendid[]) throws Exception{
Class.forName("org.gjt.mm.mysql.Driver");
Connection cn=DriverManager.getConnection(
"jdbc:mysql:/localhost/pood", "", "");
Statement st=cn.createStatement();
String lause="SELECT kogus FROM pallid;";
ResultSet rs=st.executeQuery(lause);
rs.next();
System.out.println("Baasis on "+
rs.getInt("kogus")+" palli");
cn.close();
}
}
Kui programm panna tööle draiveri kodukataloogis, siis leitakse ise kõik sobivad klassid üles,
sest nad on seal lihtsalt käe-jala juures.
Soovides aga kohaleveetud draiverit kusagil mujal kasutada, selleks tuleb draiveri klassid
arhiividena kaasa võtta ning käivitamisel –classpath abil öelda, millistest arhiividest draiveri osad kokku
korjata tuleb.
Servlet
Baasis paiknevaid andmeid võib vaja olla mitmele poole välja anda. Veebi kaudu on hea andmeid
lugeda ning sinna saatmiseks sobivad servletid ehk pisiprogrammikesed veebiserveris. Nagu varsti näha,
võime soovi korral oma arvutisse HTTP-serveri püsti panna ning servletid andmebaasi andmeid lugema
saata. Alustada võiks aga lihtsa servleti loomisest ja käivitamisest. Ja seletusest, et millega tegu.
Java programme käivitatakse päris mitmesugustes paikades. Algseks ja "õigeks"
käivitamiskohaks võidakse pidada ju main-meetodit, kuid võimalikke Java-programmide käivituskohti on
tunduvalt enam. Rakendid veebilehtedel saavad käiturilt teateid sündmuste kohta ning toimivad vastavalt
nendele. Rakendusserveris paiknevad EJB-nimelised komponendid ootavad aga hoopis teistsuguste
käskude käivitamist. Ning miniseadmetes toimivad J2ME programmid ärkavad jälle omamoodi.
Servlettide praegusaja levinumaks kasutusalaks on veebilehtede väljastamine – kuid mitte ainult.
Mitmesugused masinatevahelised teated ning teenused töötavad samuti servlettide kaudu. Kui kord
loodud mugav ning suhteliselt turvaline ja kontrollitav võimalus teisest masinast andmete küsimiseks, siis
võib seda ju kasutada. Sestap võibki näha servlettide päises kahe paketi importimist: javax.servlet ning
javax.servlet.http. Viimane siis HTTP-vahenditega lähemalt seotud klasside tarbeks.
Sarnaselt rakendile võetakse ka servlettide puhul aluseks ülemklass ning asutakse selle
meetodeid üle katma. Vaid toimingud on rakendi või mobiiliprogrammiga võrreldes teistsugused,
ülekatmine ikka samasugune. Erisuseks veel, et servleti puhul iga lehe avamine piirdub funktsiooni
ühekordse väljakutsega. Rakendi puhul võivad start, stop ning paint korduvalt käivituda.
HTTP-päringute puhul on võimalike toiminguid vähemasti kuus, kuid servlettide puhul
levinumateks GET ning POST, mõlemal juhul väljastatakse üldjuhul veebileht. GET-päringu puhul antakse
parameetrid kaasa URLi real, nende pikkus on piiratum ning loodud leht võidakse kergemini puhverdada.
Tüüpiline kasutusvaldkond on näiteks otsingumootorite juures, kus sama päringu tulemus minutite ja
tundide jooksul oluliselt ei muutu.
Kui tegemist andmete sisestamisega – näiteks enese võrgu kaudu registreerimisega, siis tuleb
paratamatult programm igal korral uuesti käima panna ning selleks kasutatakse POST-nimelist meetodit.
Tegemise ajal katsetada on aga GET-i puhul mugavam, sest siis paistavad saadetavad andmed välja. GET-
meetodi käivitamiseks tuleb üle katta servleti meetod doGet. Meetodile antud esimese parameetri kaudu
saab andmeid päringu kohta: milliselt aadressilt ja masinast tuldi, millised andmed kasutaja kaasa saatis.
Teine parameeter tüübist HttpServletResponse võimaldab määrata loodava lehe sisu ning päised.
Järgnevalt näha võimalikult lihtne tervitav servlet.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class Servlet1 extends HttpServlet{
public void doGet(HttpServletRequest kysimus, HttpServletResponse vastus)
throws IOException, ServletException{
PrintWriter valja=vastus.getWriter();
valja.println("Tervist!");
}
}
Enne tulemuse nägemist tuleb veel pingutada servletile sobiva keskkonna loomise nimel. Kel
juba sobiv käivitusserver eelnevalt püsti, sel võib piisata faili kompileerimisest ning sobivasse kataloogi
paigutamisest. Kel aga mitte, siis tuleb veidi installeerimisega pead vaevata. 2004. aastal tundub levinud
servletikäituriks olevat näiteks Apache Tomcati nimeline veebiserver http://jakarta.apache.org/tomcat/.
Sealt allalaetud faili lahtipakkimisel või käivitamisel saab õnnelike juhuste kokkulangemisel tööle oma
masinas veebiserveri. Täpsemaid seadistamise juhiseid leiab näiteks aadressilt
http://www.coreservlets.com/Apache-Tomcat-Tutorial/.
Loodud koodi kompileerimiseks peavad kättesaadavad olema servlettide alusklassid. Need leiab
Tomcati installeerimiskataloogi alamkataloogist common\lib\ . Tomcati 4. versiooni puhul on failiks
servlet.jar, viienda versiooni puhul servlet-api.jar. Neid võib kättesaadavaks teha CLASSPATH-nimelise
muutuja kaudu. Teiseks võimaluseks on aga kopeerida nimetatud fail java interpretaatori laienduste
kataloogi, milleks siinses masinas on näiteks C:\j2sdk1.4.2_01\jre\lib\ext, mujal siis vastavalt Java
installeerimise asukohale. Edasi võib koodi kompileerida nagu tavalist Java faili. Üheks mugavaks
käivitamise kohaks on asukoht Tomcati enese näidete juures nt. C:\Program Files\Apache Group\Tomcat
4.1\webapps\examples\WEB-INF\classes , kuid konfiguratsioonifailide abil saab siin paljutki sättida.
Kaasatulnud näited saab käivitada aadressireal examples/servlet-kataloogi kaudu.
Mõningase nikerdamise tulemusena võib aga servletid ka juurkataloogis oleva servlet-kataloogi
all tööle lükata.
Sisestus
Kui soovida programmilt vastuseid omapoolsetele andmetele, siis tuleb need kuidagi ka
programmile ette anda. Servlettide puhul sisestab kasutaja enamasti andmed veebilehel paiknevasse
tekstivälja või muusse sisestuskomponenti. Andmete sisestusnupule vajutamisel jõuavad need
paremeetritena järgmisena avatava veebilehte loova programmi kasutusse ning edasi tuleb juba seal
otsustada, mida saadud andmetega peale hakatakse. Igal andmeid edastaval sisestuskomponendil
sõltumata tüübist on nimi, järnevas näites näiteks "eesnimi". Andmeid vastuvõttev programm saab selle
nime järgi küsida just konkreetse elemendi väärtust.
Siin pole eraldi määratud, kuhu faili andmeid saata. Sel juhul käivitatakse uuesti sama fail ning
uue ringi peal jõuavad eelmisel korral sisestatud andmed kohale.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Sisestus1 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println(""+
"
Sisestus\n"+
"");
valja.println("Tere, "+ kysimus.getParameter("eesnimi"));
valja.println("");
}
}
Esimesel korral aga pole veel andmeid kusagilt võtta ning kysimus.getParameter annab
vastuseks tühiväärtuse null.
Kui nüüd tekstivälja sisse nimi kirjutada ning sisestusklahvile vajutada, siis võib järgmisel ringil
näha, et nimi jõudis avanevale lehele kohale. Lehe keskel ilutseb rõõmsasti "Tere, Juku". Kui
tähelepanelikumalt piiluda, siis võib märgata, et programmi nime taga aadressireal paikneb küsimärk ning
selle järel sisestatud parameetri nimi ja võrdusmärgi taga väärtus. Sealtkaudu on liikuvad andmed
programmeerijale ilusti näha ning tal võimalus kontrollida, mis ja millise nime all serverisse saadeti.
Esmakordselt näidatavast tühiväärtusest on täiesti võimalik hoiduda. Selleks tuleb enne nime
välja trükkimist kontrollida, kas ikka midagi teele saadeti. Andmed loeti eesnime-nimelisse muutujasse
kahel põhjusel. Lühema nimega muutujaga on lihtsalt kergem ümber käia kui pidevalt parameetrit käskluse
kaudu küsides. Samuti võib juhtuda, et kui kord mõni parameeter Request-i käest küsitud, siis võidakse
arvata, et selle väärtus juba programmeerijal teada on ning seda rohkem enam algses kohas ei säilitata.
Tugevamalt tuleb sellise kadumisvõimalusega arvestada andmebaaside juures mõne draiveri ja seadistuse
puhul, kuid ka siin on tegemist sama nähtusega.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Sisestus2 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println(""+
"Sisestus\n"+
"");
String eesnimi=kysimus.getParameter("eesnimi");
if(eesnimi!=null){
valja.println("Tere, "+eesnimi);
}
valja.println("");
}
}
Kui nüüd kontrollitakse enne väljatrükki tühiväärtust, siis võib näha, et lehe vastuseosa on ilusti
tühi ning mõttetut teksti välja ei kirjutata.
Viisakalt sisse kirjutatud nime puhul aga tervitatakse sama rõõmsasti vastu.
Räägitakse, et veebilehtede koostamisel tuleb arvestada igasuguste turvaprobleemidega. Ning et
üheks märgatavaks ohuks on kasutajate nii kogemata kui meelega sisestatud erisümbolid. Lihtsamatel
juhtudel võidakse kirjutada HTML-i kujunduskäsklusi oma teksti ilmestamiseks.
Tulemus võib sellisel juhul päris meediv olla.
Samas ei takista miski ka Javaskripti koodilõike teksti sisse kirjutamast ning nende tööga ei
pruugi kasutaja enam rahul olla.
Näiteks praegusel juhul avatakse teateaken. Kui selliseid akent avavaid teateid aga mõnda
külalisraamatusse hulgem saab, siis võib lehekülje avamine päris vaevaliseks osutuda.
Suuremaks probleemiks siinjuures on, et lehel toimetav Javaskript võib ka näiteks kasutaja
sisestatud andmed oma kontrolli alla saada ning hoopis võõrasse serverisse teele saata, mille üle kasutaja
sugugi rõõmus ei pruugi olla. Samuti võib juhtuda, et üksik valesse kohta sisestatud < või "-märk tekitab
seilris sedavõrra segadust, et järgnev tekst jääb sootuks näitamata või muutub keerulisema paigutusega
lehel pilt ees segaseks.
Kui kasutaja sisestatud erisümbolid HTML-i reeglitele vastavalt kodeerida, siis pääseb eelpool
kirjeldatud muredest. Et kodeerimist läheb vaja siinsest näitest tunduvalt rohkemates paikades, siis sai
abifunktsioon paigutatud omaette klassi. Staatilise funktsiooni saab kättesaadava klassi kaudu kohe
käima tõmmata, ilma et peaks objekti loomisele jõudu kulutama. Tähtede asendamiseks on kasutatud
StringBuffer-tüüpi objekti, kuna puhvrile liitmine on tunduvalt odavam tegevus kui sõnede liitmine. Eriti
juhul, kui tekstid kipuvad pikemaks minema. Sest kord valmis loodud Stringi enam muuta ei saa. Tähe
lisamiseks tuleb uus mälupiirkond leida ning algsed andmed sinna üle kopeerida. StringBuffer on aga
siinkirjeldatud toiminguteks just loodud.
public class Abi{
/**
* Etteantud tekstis asendatakse HTML-i erisümbolid
* lehele sobivate kombinatsioonidega.
*/
public static String filtreeriHTML(String tekst){
if(tekst==null){return null;}
StringBuffer puhver=new StringBuffer();
for(int i=0; i': puhver.append(">"); break;
case '&': puhver.append("&"); break;
case '"': puhver.append("""); break;
default: puhver.append(c);
}
}
return puhver.toString();
}
}
Andmete filtreerimiseks piisab järgnevast käsust.
String eesnimi=Abi.filtreeriHTML(kysimus.getParameter("eesnimi"));
Kui klass Abi asub käivituva servletiga samas kataloogis, siis leitakse klass üles.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Sisestus3 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println(""+
"Sisestus\n"+
"");
String eesnimi=Abi.filtreeriHTML(kysimus.getParameter("eesnimi"));
if(eesnimi!=null){
valja.println("Tere, "+eesnimi);
}
valja.println("");
}
}
Nüüd võib kasutaja ka proovida skripti sisestada.
Tulemusena aga asendatase tekst ära ning lehele jõuab sarnane pilt kui kasutaja kirjutatugi.
Kui tahta programmi muundamistööd täpsemalt piiluda, siis tulemuse leiab lehe lähtekoodi
vaadates.
Sisestus
Tere, <script>alert("kuku")</script>
Enamasti sisestatakse lehele rohkem kui üks väärtus. Kui ühe teksti puhul piisas tekstiväljast
ning sisestusklahvile vajutades läksid andmed teele, siis rohkemate saadetavate andmete puhul on lisaks
vaja ka sisestusnuppu. Selleks siis sisestusväli tüübiga "submit".
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Sisestus4 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println(""+
"Sisestus\n"+
"");
String eesnimi=Abi.filtreeriHTML(kysimus.getParameter("eesnimi"));
String perenimi=Abi.filtreeriHTML(kysimus.getParameter("perenimi"));
if(eesnimi!=null){
valja.println("Tere, "+eesnimi+" "+perenimi);
}
valja.println("");
}
}
Nii võib väärtusi sisestada loodud tekstiväljadesse
ning pärast rakenduse töö tulemust imetleda.
Pilt servleti väljundina
Arvutis liikuvaid andmeid võib enamikul juhul käsitleda baidijadana ning ka servlet pole selle
poolest erand. Nõnda võib servleti panna väljastama ka pilti või heli. HTTP-ühenduse päiseridadega
antakse teada, millist tüüpi andmeid saadetakse ning edasine on juba vastuvõtja ülesanne. Et Javas
leiduvad vahendid pildi kirjutamiseks voogu, siis saab andmeid võrdselt õnnelikult saata nii faili kui
võrku. Ning siinses näites jõuavadki andmed üle võrgu kasutajani. Pilt luuakse mälus valmis ning lõpuks
saadetakse andmed voogu pidi teele. Mugavaks pildi loomise vahendiks on BufferedImage ning sealt
küsitud graafiline kontekst. Joonistamine toimub sarnaste käskude puhul nagu mujalgi.
import javax.servlet.*;
import javax.servlet.http.*;
import com.sun.image.codec.jpeg.*; //kuulub SUNi JDK-sse
import java.awt.image.*;
import java.awt.*;
import java.io.*;
public class piltservlet2 extends HttpServlet{
public void doGet(HttpServletRequest kysimus, HttpServletResponse vastus)
throws IOException, ServletException{
int suurus=(int)(20+Math.random()*60);
BufferedImage pilt=new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D piltg=pilt.createGraphics();
piltg.setColor(Color.red);
piltg.fillOval(50-suurus/2, 50-suurus/2, suurus, suurus);
vastus.setContentType("image/jpeg");
JPEGCodec.createJPEGEncoder(vastus.getOutputStream()).encode(pilt);
}
}
Ning kui tulemus valmis, võib seda imetleda
Servlet ja andmebaas.
Veebist saabuvaid ja küsitavaid andmeid on küllalt mõistlik talletada andmebaasis. Sellisel juhul
ei pea programmeerija liialt palju pead vaevama andmete poole üheaegsest pöördumisest tekkivate
murede üle, sest selle eest hoolitsemine on juba andmebaasimootoritesse sisse ehitatud. Üldjuhul käib
andmebaasiühenduse loomine servleti puhul nii nagu mujalgi programmis. Vaja laadida draiver, luua
ühendus. Statement-objekt lause edastamiseks ning ResultSet andmete lugemiseks. Kuna ResultSet
arvestab, et päringu tulemusel väljastatakse tabel, siis tuleb andmeid ka nõnda välja lugeda.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;
public class pallibaas1 extends HttpServlet{
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException{
vastus.setContentType("text/plain");
PrintWriter valja=new PrintWriter(
vastus.getWriter());
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:poebaas", "", "");
Statement st=cn.createStatement();
String lause="SELECT kogus FROM pallid;";
ResultSet rs=st.executeQuery(lause);
rs.next();
valja.println(
"Laos on "+rs.getInt("kogus")+" palli.");
}catch(Exception viga){
valja.println("Probleem andmebaasiga: " +viga);
}
}
}
JSP
Servletid on mugavad olukordades, kus lehtedel on staatilist teksti vähe ning enamik sisust tuleb
kokku arvutada. Suuremate püsivate tekstide puhul on võimalik neid teistest failidest või
andmebaasikirjetest sisse lugeda. Siin lisatakse rakenduse juurkataloogis (nt. webapps/examples) paiknev
fail SISU.JSP.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class kaasamiskatse extends HttpServlet{
public void doGet(HttpServletRequest kysimus, HttpServletResponse vastus)
throws IOException, ServletException{
PrintWriter valja=vastus.getWriter();
ServletContext kontekst=getServletConfig().getServletContext();
RequestDispatcher rd=kontekst.getRequestDispatcher("/SISU.JSP");
rd.include(kysimus, vastus);
}
}
Kui aga väljaarvutamist nõudvaid paiku lehel suhteliselt vähem ning märkimisväärse osa lehe
loomisest moodustab kujundus, siis sobib kasutada JSP-nimelist võimalust. Siin moodustab lehe põhiosa
otse väljastatav tekst ning vaid erisümbolite vahel paiknevates lõikudes käivitatakse programm. Lihtsamal
juhul kirjutatakse JSP-kood <% ja %> vahele, kuid uuema standardi järgi võimaldatakse ja soovitatakse
JSP-lehed kirjutada XML-standardile vastaval kujul, kus jsp:ga algavad elemendid siis juhivad koodi
käivitamist.
Järgnevalt on tegemist võimalikult lihtsa näitega, kus lihtsalt arvutatase kokku kahe arvu summa.
Tervitus
Parim hinne koolis on <%=3+2 %>
Käivitamiseks tuleb failid panna samasse kataloogi kuhu harilikud html-failidik, nagu järgnevalt
jooniselt näha.
Ning veebist vaatamiseks piisab sobiva aadressi sissetoksimisest.
JSP-lehti eraldi kompileerida pole vaja, selle eest hoolitseb juba käitur. Sestap ka lehe aeglane
avamine esimesel algsel või muutmisjärgsel käivitamisel, sest seal tuleb kogu kompileerimise töö ära teha.
Selline lähenemine võib eriti mugav olla näiteks inimestele, kel varem pole kompileerimisega kogemusi
olnud ning PHP või muid skripte kirjutades juba harjunud, et piisabki vaid koodi kirjutamisest, kui juba
võibki tulemusi imetlema asuda.
Et sisimas aga muudetakse JSP lehed enne servlettideks ja alles siis kompileeritakse/käivitatakse,
kipuvad saabuvad veateated küllalt arusaamatud ja vähemasti algul häirivad olema. Näiteks võib juhtuda,
et liitmisel sattus kogemata üks x-täht arvu taha.
Tervitus
Parim hinne koolis on <%=3+2x %>
Tegemist on ju küllalt lihtsa ja sageli esineva veaga, mis võiks õnnestuda ilma suuremate
muredeta ära parandada. Kui aga nüüd lehte avama asuda, ilmneb päris põhjalik veateade:
org.apache.jasper.JasperException: Unable to compile class for JSP
An error occurred at line: -1 in the jsp file: null
Generated servlet error:
[javac] Since fork is true, ignoring compiler setting.
[javac] Compiling 1 source file
[javac] Since fork is true, ignoring compiler setting.
[javac] C:\Program Files\Apache Group\Tomcat
4.1\work\Standalone\localhost\examples\arvutus_jsp.java:47: ')' expected
[javac] out.print(3+2x );
[javac] ^
[javac] 1 error
at
org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.ja
va:130)
at
org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:293)
at org.apache.jasper.compiler.Compiler.generateClass(Compiler.java:353)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:370)
at
org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:473)
at
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:190)
at
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:295)
... lisaks veel paarkümmend rida näitamaks millised funktsioonid kust välja kutsuti. Ning seda kõike vaid
ühe puuduva tähe pärast. Mõningase piilumise peale leiab koha, mis servletis vigaseks osutus.
[javac] C:\Program Files\Apache Group\Tomcat
4.1\work\Standalone\localhost\examples\arvutus_jsp.java:47: ')' expected
[javac] out.print(3+2x );
Ning sealt pealt õnnestub aimata, et <%= kujul antud avaldis muudetakse print-käsu sisuks ning sinna
taoline uitama asunud x ei sobi. Kui x eemaldada ning leht uuesti laadida, töötab ta jälle. Möödunud
veateatest loeb välja ka näiteks loodava servleti asukoha: Tomcati alamkataloog work. Kui muu ei aita,
tuleb asuda vastava servleti koodi lähemalt uurima. Aga enamasti ikka õnnestub JSP lehel soovitud kohti
muutes ja välja kommenteerides segased kohad kindlaks teha ja parandada.
Tsükkel
JSP lehe sisse saab Java koodi täiesti rahumeeles kirjutada. Et lõppkokkuvõttes muudetakse JSP-
leht ikkagi servletiks, siis käivitamisel polegi nende vahel kuigivõrd vahet. Vaid kasutaja mugavuse
mõttes kannatab JSP-lehel tavalist teksti kergemini edasi anda. Nõnda töötab harilik tsükkel
Arvude ruudud ühest kahekümneni on:
<% for(int i=1; i<=20; i++){ %>
<%= i*i+" " %>
<% } %>
Ja tulemus lehel nagu oodatud.
Nagu mujal, nii ka siin soovitakse kasutaja käest andmeid saada, muidu poleks ju põhjust lasta
programmil lehte kokku panna. Andmed nagu ikka kirjutatakse vormi ning selleks sobib täiesti tavaline
HTML-leht. Vormi action-atribuudiga määratakse koht, kuhu sisestatud andmed saadetakse.
Nime sisestamine
Palun nimi:
Et tekstivälja nimeks oli "nimi", siis võib selle kaudu ka andmed kinni püüda.
Nime lugemine
Tere,
<%= request.getParameter("nimi") %>
Ning nagu pildilt paistab, saabuvad vaikimisi GET-päringu korral andmed URL-rea kaudu.
Teate kaasamine
Kui samu andmeid soovitakse mitmel veebilehel kasutada, siis on mugav andmed ühte kohta
kirja panna ning sobivates paikades faili sisse lugeda. Nagu järgnevast näitest paistab, on selliseks
käsuks include, nii nagu mõnes muuski veebikirjutuskeeles (PHP, ASP).
Faili sisu kaasamine
Failis on teade:
<%@ include file="teade.txt" %>
Ning vastav fail peab lihtsalt samas kataloogis omaette kättesaadav olema.
Tere, kool
Nõnda võibki töö tulemust imetleda.
Enam kasutatakse taolist kaasamist olukordades, kus soovitakse mitmele lehele luua ühesugune
päis.
Kommentaarid
Enamikes keeltes jäetakse programmeerijale võimalus omi märkusi koodi juurde lisada ilma, et
tavakasutaja sellest aimu saaks. Olgu siis tegemist rakenduse tutvustamise, koodi üksikute lõikude
seletamise või ebasoovitavate lõikude ajutise eemaldamisega. JSP puhul on selleks kolm märgatavalt
erinevat võimalust. Esiteks võis HTMLi koodi sisse kirjutada oma "nähtamatu" tekst käskude
vahele. Selline tekst jõuab küll kasutaja masinasse, kuid ei ole lehe tavalisel vaatamisel nähtav.
Kui soovida tervet JSP-lõiku eemaldada, siis võis selle panna <%-- ja --%> vahele. Nõnda saab
lõike kergesti sisse ja välja lülitada. Ning lõppeks kehtivad ka tavalised Java-kommentaarid: // ühe rea
tarvis ning /* ja */ pikema lõigu jaoks.
Kommentaarid
<%-- varjatud kommentaar --%>
<%
//kommentaar koodi sees
%>
Uba
JSP lehtede sisu soovitatakse võimalikult lihtsaks jätta. Siis on neid võimelised kujundama ka
programmeerimiskauged inimesed. Äriloogika ehk arvutused ning andmetega seotud toimingud saab
paigutada eraldi ubadeks nimetatud klassidesse ning sealt siis sobivate käskude abil teenuseid küsida.
Klassid paigutatakse sinna kuhu servletidki, ainult et soovitavalt veel iga teemaga seotud oad omaette
kataloogi ehk paketti. Siinsel paketi nimeks on pandud k1, ning alt pildilt paistab ta ilusti classes-kataloogi
alamkataloog olema.
Siinse oa ülesandeks on vaid nime meeles pidamine. Ning vaikimisi nimeks on Triin.
package k1;
import java.io.Serializable;
public class Uba1 implements Serializable{
String nimi="Triin";
public void paneNimi(String nimi1){
nimi=nimi1;
}
public String annaNimi(){
return nimi;
}
public String tutvusta(){
return "Mu nimi on "+nimi;
}
}
Kui klass loodud, tuleb see kompileerda nagu enamikele muudelegi Java-programmidele kohane.
Kompileerimisel peaks aktiivne kataloog olema WEB-INF\classes, nii et kompileerimisel tuleb pakkettide
tee mööda katalooge ette anda. Ning kompileeritud class-fail paigutatakse java-failiga samasse kataloogi.
C:\Program Files\Apache Group\Tomcat 4.1\webapps\examples\WEB-INF\classes>javac k1\Uba1.java
Kui uba valmis, võib teda kasutama hakata. Oa kirjeldamiseks lehel käsklus jsp:useBean.
Atribuudiga annan oale nime, mille järgi hiljem selle poole pöörduda. Edasi saan osalt kasutada uba nagu
tavalist muutujat. Kuigi – ubade tarbeks on JSP sisse ka mitmesuguseid muid pöördumisvõimalusi leitud.
Oa kasutamine
<%=nimehoidja.tutvusta() %>
<%nimehoidja.paneNimi("Katrin"); %>
Oa sees on nüüd
<%=nimehoidja.annaNimi() %>
Kui algselt oli oa sees Triin ning eraldi käsuga määrati nimeks Katrin, siis nõnda võib tulemust ka
veebilehelt näha.
Vaikimisi on oa poole võimalik pöörduda vaid sama lehe avamise jooksul. Kui soovida aga
andmeid pikemks talletada, võib määrata skoobiks sessiooni. Nõnda püsivad andmed paigal sama
kasutaja mitme järjestikuse pöördumise ajal ning seal võib meeles pidada näiteks teadet, et kasutaja on
end juba sisse meldinud.
Oa kasutamine
<%=nimehoidja.tutvusta() %>
<%nimehoidja.paneNimi("Katrin"); %>
Oa sees on nüüd
<%=nimehoidja.annaNimi() %>
Siin töötab leht esimese pöördumise puhul nii nagu eelmisel korral: kuna midagi pole veel eraldi
salvestatud, siis alguses teatatakse ikka vaikimisi nimeks olev Triin.
Kui nüüd aga järgmisel korral sama sessiooni jooksul minna oast andmeid küsima, siis on seal
kirjas juba eelmisest korrast meelde jäänud nimi.
Uba ja andmebaas
Et igasugused programmeerimiskäsud
püütakse JSP-lehest eemal hoida, siis on mõistlik ka
andmebaasiga seotud toimingud oa sisse peita.
Kui peitmine korralik, siis ei pruugi JSP-
lehe looja sageli teadagi, kus baasis andmeid
hoitakse. See võimaldab vajadusel andmekandjat
küllalt kergesti vahetada ning vajadusel näiteks
andmebaasi sootuks tavalise tekstifailiga asendada.
Et ühendamine libedalt läheks, loome ka siin ODBC
alla andmeallika.
Edasi koostame oa, millel oskused nii andmetabeli loomiseks kui väärtuse seadmiseks ja
küsimiseks. Ning kui eelmise oaga võrrelda, siis näevad nad küllalt sarnased välja – ikka käsud väärtuste
seadmiseks ja küsimiseks. Ning oa kasutaja ei peagi teadma, et andmeid just baasis hoitakse.
package k1;
import k1.*;
import java.sql.*;
public class Baasiuba1{
Connection cn;
Statement st;
public Baasiuba1(){
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
cn=DriverManager.getConnection("jdbc:odbc:baas1", "", "");
st=cn.createStatement();
}catch(Exception e){
System.out.println(e);
}
}
public void looBaas() throws SQLException{
String lause="CREATE TABLE pallid (kogus int);";
st.executeUpdate(lause);
lause="INSERT INTO pallid (kogus) values ('0');";
st.executeUpdate(lause);
}
public void setPalliarv(int arv) throws SQLException{
String lause="UPDATE pallid SET kogus="+arv+";";
st.executeUpdate(lause);
}
public int getPalliarv() throws SQLException{
String lause="SELECT kogus FROM pallid;";
ResultSet rs=st.executeQuery(lause);
rs.next();
return rs.getInt("kogus");
}
}
Kui paketi sees olev uba loodud, tuleb ta kompileerida nagu Java-fail ikka.
C:\Program Files\Apache Group\Tomcat 4.1\webapps\examples\WEB-INF\classes>javac
k1\Baasiuba1.java
Et pallilao administreerimine mugavamalt läheks, selleks on ka tabeli loomiseks omaette
"administraatorileht" tehtud. Võrgust leitavate rakenduste puhul võib sageli kohata juhendit, kus üles
seadmiseks tuleb vaid andmebaasi nimi määrata või sobiva nimega baas luua ning edasi õnnestub kõik
veebi kaudu paika sättida. Siin vaid öeldakse oale, et looBaas (mille juures praegu küll vaid üks tabel
luuakse) ning võibki asuda juba rakenduse teeneid kasutama. Kontrolliks küsitakse välja baasis leiduvate
palllide arv. Nagu näha, ei tehta seda mitte tavalise funktsiooniväljakutsega, vaid oa väärtuste küsimiseks
sobib element jsp:getProperty. Mis küll toimimiseks eeldab, et oal oleks vastavanimeline get-liitega algav
meetod.
<% pallibaas.looBaas(); %>
Baas loodud
Baas loodud
Baas pallide arvu loomiseks õnnelikult loodud.
Laos on palli.
Nii võib koodilõigu tööd veebist imetleda ning pärast ka andmebaasifailist piiluma minna, et
soovitud tabel ka tegelikult loodud ning väärtus sinna sisse pistetud on.
Andmete lisamisel küsitakse kasutaja käest lisatavate pallide arvu ning saadetakse tulemused
edasi lehele lisamine.jsp.
Pallide lisamine
Pallide lisamine
Mitu palli lisatakse lattu?
Too leht saab väärtused URL-i rea pealt kätte ning määrab baas uue pallide arvu. Pärastine
väärtuse küsimine nagu ennegi – getProperty kaudu.
Pallide lisamine
Pallide lisamine
<%
int olemas=pallibaas.getPalliarv();
int juurde=Integer.parseInt(request.getParameter("arv"));
int kokku=olemas+juurde;
out.println("Laos oli palle "+olemas+" lisatakse "+juurde+".");
pallibaas.setPalliarv(kokku);
%>
Laos on nüüd palli.
Ja võibki koodi tööd veebilehel imetleda.
Lisada kannatab ka olemasolevatele juurde.
Ning andmebaasi tabelisse vaatama minnes
võib veenduda, et sinna ka arv 17 jõudnud on
Ostmise puhul on toiming lihtsalt teistpidine. Algul tasub ikka küsida, kas laost üldse midagi
võtta on ning siis teada anda, mitut palli osta soovitakse.
Pallide ostmine
Pallide ostmine
Laos on palli.
Mitu palli ostetakse?
Müümisel kõigepealt kontrollitakse, et nõnda palju kaupa ikka jagub ning vaid sobivuse korral
võetakse tehing ette.
Pallide eemaldamine
Pallide eemaldamine
<%
int olemas=pallibaas.getPalliarv();
int maha=Integer.parseInt(request.getParameter("arv"));
int tulemus=olemas-maha;
out.println("Laos oli palle "+olemas+" väljastada soovitakse "+maha);
if(tulemus<0){
out.println("Väljastada saab vaid "+olemas+" palli");
tulemus=0;
}
pallibaas.setPalliarv(tulemus);
%>
Lattu jäi palli.
Ning andmetabelist võib taas järele kontrollida,
et veebi väljastatud andmed ikka õiged on.
JDBC käskude ülevaade
Kui kord ühendus loodud, siis edasised toimetused võiksid juba mõnevõrra lihtsamalt sujuda. Järgnevalt
proovitakse läbi mitmed levinumad andmetega ümber käimise võtted.
Kõikide ridade väljastus.
Ehk levinumaiks esimeseks andmebaasiga seotud rakenduse ülesandeks ongi andmete
kasutajale näitamine soovitud kujul. Tüüpilisel juhul tuleb luua ühendus, koostada päring, käivitada see
ning saabunud andmed sobival kujul kuvada. Järgnevas näites küsitakse eelnevalt loodud inimeste
andmete tabelist välja kõikide veergude andmed. Ekraanile aga näidatakse neist eesnime ja sünniaasta
väärtused.
import java.sql.*;
public class Inimloetelu{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement();
ResultSet rs=st.executeQuery("select * from inimesed");
while(rs.next()){
System.out.println(rs.getString("eesnimi")+":"+rs.getInt("synniaasta"));
}
cn.close();
}
}
/*
C:\jaagup\andmed>java Inimloetelu
Juku:1989
Kati:1987
Mati:1983
*/
Andmed andmete kohta
Kui vaja rakenduse võimalusi sageli muuta, või kui soovitakse sama andmeväljastuslõiku
kasutada mitmesuguste andmete korral, siis aitab päringu vastusega koos tulev metadata ehk andmed
andmete kohta. Mõne andmebaasimootori või draiveri puhul võib vastav võimalus puududa või töötada
nuditult. Kui aga andmeid kirjeldavate andmete küsimise võimalus olemas, siis saab neid sarnaselt kätte
sõltumata kasutatavast andmebaasist.
Nagu alljärgnevast näitest näha, tuleb andmete kirjeldus ResultSet'ist eraldi käsuga välja küsida.
Edasi õnnestub juba ResultSetMetaData tüüpi objektist üksikute käskude abil omale soovitavaid andmeid
teada saada.
ResultSetMetaData rmd=rs.getMetaData();
int veergudearv=rmd.getColumnCount();
Kui veergude arv ja andmed teada, siis saab koostada koodilõigu, mis juba vastavalt andmed
välja küsib ja nendega edasi toimetab. Siin näites trükitakse tulemused vaid ekraanile, kuid sarnaselt võib
ette valmistada väljastuse veebilehele või mujalegi. Kes juhtub kasutama andmebaasihaldusvahendeid
nagu näiteks veebipõhine PHP MyAdmin, võib aimata, et taoliste rakenduste koostamisel kuluvad
saabuvad andmed andmete kohta väga marjaks ära.
import java.sql.*;
public class Inimloetelu2{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement();
ResultSet rs=st.executeQuery("select * from inimesed");
ResultSetMetaData rmd=rs.getMetaData();
int veergudearv=rmd.getColumnCount();
System.out.println("Tabelis on "+veergudearv+" veergu:");
for(int i=1; i<=veergudearv; i++){
System.out.println("Nimi: "+rmd.getColumnName(i));
System.out.println("Kirjeldus: "+rmd.getColumnLabel(i));
System.out.println("Tyyp: "+rmd.getColumnTypeName(i));
System.out.println("Java klass: "+rmd.getColumnClassName(i));
System.out.println("Isesuurenev: "+rmd.isAutoIncrement(i));
System.out.println("Suurim laius: "+rmd.getColumnDisplaySize(i));
System.out.println();
}
while(rs.next()){
System.out.print(rs.getRow()+". ");
for(int i=1; i<=veergudearv; i++){
System.out.print(rs.getObject(i)+" ");
}
System.out.println();
}
cn.close();
}
}
Ning programmi töö tulemusena anti ilus selge ülevaade kasutatavast andmetabelist. Nii tulpade
kirjeldused ükshaaval, kui pärast kogu tabeli sisu.
/*
Tabelis on 3 veergu:
Nimi: id
Kirjeldus: id
Tyyp: COUNTER
Java klass: java.lang.Integer
Isesuurenev: true
Suurim laius: 11
Nimi: eesnimi
Kirjeldus: eesnimi
Tyyp: VARCHAR
Java klass: java.lang.String
Isesuurenev: false
Suurim laius: 50
Nimi: synniaasta
Kirjeldus: synniaasta
Tyyp: INTEGER
Java klass: java.lang.Integer
Isesuurenev: false
Suurim laius: 11
1. 1 Juku 1989
2. 2 Kati 1987
3. 3 Mati 1983
*/
Päringu tulemuste hulgas liikumine.
Esimese lähendina võib relatsioonilisest andmebaasist andmete välja küsimine tunduda küllalt
selgena. Kõik andmed ja nende vahelised seosed esitatakse baasis tabelitena ning ka tulemuseks on tabel
- sarnane programmeerijale tuttava kahemõõtmelise massiiviga. Et toimingute sisemine keerukus püütakse
rakenduse loojate eest võimalikult peita, siis ideaaljuhul polegi vaja muule mõelda kui rea numbrile ja
veeru nimele või numbrile, kust andmed enesele välja küsida.
Andmebaasimootorid peavad hakkama saama suurte andmemahtude ning mitmete üheaegsete
kasutajatega. Selle toimimise tarvis tuleb arvestada andmebaasiühenduse kasutatavate
enesekaitsemehhanismidega. Vaikimisi seadete korral õnnestub päringust andmeid välja meelitada vaid
ridu järjest eest tahapoole lugedes ning iga väärtust vaid ühe korra küsides. Sellise lähenemise puhul ei
pea sugugi kõik päringu väljastatavad andmed olema korraga baasist välja küsitud. Mällu loetakse ja üle
kantakse vaid need, mis parasjagu tarvilikud. Ülejäänud võivad veel oma aega oodata ning uuritud ridade
arvelt võib sootuks mälu vabastada. Selline vaikimisi järjest küsimine peab ka kõigi töötavate draiverite
puhul ühtviisi leiduma ja toimima. Kui teada ja arvestada taolist ette kirjutatud andmete küsimise
järjekorda, siis enamike rakenduste puhul olulist probleemi ei teki. Ekraanile või veebilehele saadetaksegi
andmed sageli saabumise järjekorras. Ning kui vaja kokkuvõtteid teha või mõnda väärtust korduvalt
kasutada, siis tuleb lihtsalt vajalikud andmed muutujatesse ja massiividesse kirjutada ning edaspidi
kasutada kui tavalisi programmeerimise juures tarvilikke andmeid.
On aga mingil põhjusel kindel vajadus mööda andmeid sageli edasi-tagasi liikuda, siis tuleb
vajadust juba eelnevalt arvestada. Javakeelsete programmide puhul luuakse kõigepealt ühendus
(Connection). Iga ühenduse kaudu võib korraga töötada mitu käsklust (Statement). Ning iga käskluse
küljes võib vajadusel olla avatud korraga kuni üks vastuste kogum (ResultSet). Vastuste kogumi
omadused määratakse juba käskluse loomisel. Järgnevas näites paistavad konstandid
ResultSet.TYPE_SCROLL_INSENSITIVE
ResultSet.CONCUR_READ_ONLY
Esimene neist määrab, et saabunud vastustehulka võib soovitud suunas kerida. Olgu siis
edaspidi või tagurpidi. Suurema andmehulga korral võib selline nõudmine märgatavalt ressursse nõuda
või riistvarale sootuks üle jõu käia. Kuid kui näiteks Swingi vahenditega tabelit luua, siis on päris mugav,
kui võib otse ResultSetist saabuvaid andmeid usaldada ning ei pea hakkama veel lisaks oma
andmekogumit looma.
Teise parameetri tagamaad on veel mõnevõrra keerulisemad. CONCUR_READ_ONLY tähendab,
et andmeid võib päringust vaid lugeda, mis nagu päringu juures võikski loomulik tunduda. Nagu aga
hilisematest näidetest paistab, ei pruugi see päringu kasutamise ainuke võimalus olla.
Järgnevas näites käiakse läbi ResultSet'is liikumise tähtsamad käsud. Enamik neist võiks olla otse
inglise keelest tõlgitavad. Käsk last palub minna viimasele reale, isLast kontrollib, kas ollakse viimasel
real; relative liigub soovitud arvu ridasid jooksvast reast alates, absolute loendab ridasid alates päringu
algusest.
import java.sql.*;
public class Inimloetelu3{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY
);
ResultSet rs=st.executeQuery("select * from inimesed");
rs.last();
System.out.println("Ridu kokku: "+rs.getRow());
rs.previous();
System.out.println("Eelviimane eesnimi: "+rs.getString("eesnimi"));
rs.absolute(3);
System.out.println("Kolmas eesnimi: "+rs.getString("eesnimi"));
rs.relative(2);
System.out.println("Ylejärgmine eesnimi: "+rs.getString("eesnimi")+
", reanr: "+rs.getRow());
if(rs.isLast()){
System.out.println("Tegemist on viimase reaga");
}
rs.relative(-1);
System.out.println("Eelmine eesnimi: "+rs.getString("eesnimi")+
", reanr: "+rs.getRow());
cn.close();
}
}
/*
Ridu kokku: 6
Eelviimane eesnimi: Juk's
Kolmas eesnimi: Mati
Ylejõrgmine eesnimi: Juk's, reanr: 5
Eelmine eesnimi: Sass, reanr: 4
____
Nimed:
Juku
Kati
Mati
Sass
Juk's
Jass
*/
Päringu mahu piiramine
Rakendusi koostades on vahel raske aimata, kui suurte andmekogustega tuleb tegemist teha.
Näited, mis kahe või kümne rea juures ilusti toimivad, ei pruugi tuhandete vastusridade puhul enam
sugugi kasutatavad olla. Olgu siis tegemist ekraani nähtava ala ummistumisega, arvuti mälumahu või
arvutusvõimsuse lõpuga. Üheks võimaluseks on eelneva päringu abil kontrollida, millises suurusjärgus
vastustehulgaga võiks tegemist olla ning edasi juba mitme võimaluse tarbeks kood kokku panna.
Lihtsamaks ning mõnigikord kasutatavaks lähendiks aga piisab päringu ridade arvu või tööaja piiramisest.
Kui ka ei saa soovitud kohast kõiki andmeid kätte, siis vähemasti jääb rakendus ellu, arvuti ei jookse
kokku ning võimalik näiteks täpsustatud parameetritega uus päring kokku panna.
import java.sql.*;
public class Inimloetelu4{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement();
st.setMaxRows(2); //Suurim väljastatavate ridade arv
// st.setQueryTimeout(2);
//Valikuline käsklus, päringu suurim aeg sekundites
ResultSet rs=st.executeQuery("select eesnimi from inimesed");
while(rs.next()){
System.out.println(rs.getString("eesnimi"));
}
cn.close();
}
}
/*
Juku
Kati
*/
Lisamine.
Harilik lisamine käib pea kõikide keelte ja vahendite puhul sarnaselt. Ikka tuleb kokku panna SQLi
INSERT-lause ning siis käivitada. Java käskluseks nii lisamise kui muutmiste korral on executeUpdate.
Vaid päringu puhul oli executeQuery, kus siis tulemuseks väljastati ResultSet.
import java.sql.*;
public class Inimlisamine1{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement();
st.executeUpdate(
"INSERT INTO inimesed (eesnimi, synniaasta) values ('Sass', 1977)"
);
cn.close();
}
}
PreparedStatement
Lisamist levinud operatsioonina on püütud mitmel moel paindlikumaks muuta. Kui vaja kümneid
kordi sama lauset erinevate andmetega käivitada, siis igakordne masinapoolne SQL-lause analüüs ning
enesele sobivaks seadmine võtavad oma aja. Kui aga kord koostada PreparedStatement ning hiljem vaid
väärtusi sees vahetada, siis võiks tulemus mõnevõrra kiiremini saabuda.
Teiseks mureks sisestuste juures on erisümbolid. Ehkki mõeldakse välja mitmeid viise andmete
sisse jäävate ülakomade ja muude märkide varjestamiseks, kipub ikka turvaauke sisse jääma, kus lihtsalt
sisestuse kaudu suudetakse SQL-laused sassi ajada ning rakenduse tööd muuta. Või teistpidi juhtub
vahel, et sümboleid varjestatakse mitmekordselt ning hiljem kipuvad varjestuse jäljed väljundisse sisse
jääma.
Kui kasutada aga PreparedStatementi, siis varjestusega muresid pole. Sest sisestatavaid
andmeid otse SQL-lausesse ei kirjutatagi. Algsesse lausesse pannakse andmete kohale küsimärgid. Ning
alles hiljem määratakse, millised andmed selle käivitamise korral küsimärkide asemele paigutatakse.
import java.sql.*;
public class Inimlisamine2{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
PreparedStatement st=cn.prepareStatement(
"INSERT INTO inimesed (eesnimi, synniaasta) values (?, ?)"
);
st.setString(1, "Juk's");
st.setInt(2, 1972);
st.executeUpdate();
cn.close();
}
}
Päringus lisamine
Lihtsustamaks rakenduste loomist, kus nähtavaid andmeid ka kohe muuta saab, võimaldavad
osa andmebaasimootoreid lihtsamate päringute korral ka päringust väljastatud tulemusi muuta või sama
tüüpi ridasid algsetesse andmetesse juurde luua. Kui summeeritaks päringus näiteks inimeste arv, siis
seda loomulikult muuta ei lubata - pole ju võimalik nõnda lihtsalt olematuid inimesi juurde tekitada. Kui
aga päringuks on lihtsalt ühe tabeli esitus või ka lihtsam ühend, siis võib muutmine ja lisamine täiesti
õnnestuda. Allpoolses näites küsitakse inimeste andmed ning edaspidiste käsklustega lisatakse üks
inimene loetellu juurde.
rs.moveToInsertRow();
rs.updateString(1, "Jass");
rs.updateInt(2, 1968);
rs.insertRow();
räägib igaüks enese eest. Nii nagu võib mõnikord vormis andmeid viimasele reale juurde
kirjutada, nii lubatakse ka siin programmi abil üks rida päringu poolt väljastatud tabelisse juurde panna,
lahtrid väärtustega täita ning siis tulemused paika saata.
import java.sql.*;
public class Inimlisamine3{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
Statement st=cn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE
);
ResultSet rs=st.executeQuery(
"SELECT eesnimi, synniaasta FROM inimesed"
);
rs.moveToInsertRow();
rs.updateString(1, "Jass");
rs.updateInt(2, 1968);
rs.insertRow();
cn.close();
}
}
Transaktsioonid
Vahel pidada olema pool muna halvem kui tühi koor. Et kui töö jäi tervikuna tegemata, siis
järgmisel korral teada, et võib kõike otsast alustada. Kui aga miskit poole peale rippuma jäi, võib kergemini
juhtuda, et mõni tegevus hiljem kaks korda tehtud saab või sootuks kahe silma vahele jääb. Tüüpiliseks
näiteks tuuakse pangaülekannet, kus ühelt kontolt võtmine ning teisele ülekandmine ikka paarikaupa
peavad käima. Ning enne lõplikku tulemuste kinnitamist peab veenduma, et mõlemad toimingud
õnnestuvad. Selliste seoste loomiseks võib automaatse täitmise peatada käsuga.
cn.setAutoCommit(false);
Edasised toimingud jäävad ootele. Kui selgub, et midagi tuli vahele, siis saab algseisu taastada käsuga
cn.rollback();
import java.sql.*;
public class Inimlisamine4{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
cn.setAutoCommit(false);
Statement st=cn.createStatement();
st.executeUpdate(
"INSERT INTO inimesed(eesnimi, synniaasta) values('Elmar', 1955)"
);
cn.rollback();
cn.close();
}
}
On aga kõik õnneks läinud, siis kannatab öelda
cn.commit();
ning muutused kinnistatakse.
import java.sql.*;
public class Inimlisamine5{
public static void main(String[] argumendid) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection("jdbc:odbc:poebaas");
cn.setAutoCommit(false);
Statement st=cn.createStatement();
st.executeUpdate(
"INSERT INTO inimesed(eesnimi, synniaasta) values('Mann', 1911)"
);
cn.commit();
cn.close();
}
}
SQL-laused
Järgnevalt vaatame läbi enamlevinud SQL-käsklused. Näited on tehtud MySQLi-nimelise
andmebaasi abil, kuid samalaadsed käsklused leiduvad ka teiste andmebaaside juures. Mõnel korral
lihtsalt vaja täpne kuju vastavast manuaalist järele vaadata.
Tabeli loomiseks kasutatakse käsklust CREATE TABLE. Tavaks on kirjutada otse SQL-keele
käsklused suurte tähtedega, muud väikestega, kuid iseenesest on SQL-andmebaasid tõstutundetud.
Tabeli nimeks siin näites teated2. Edasi tulevad sulgudes komadega eraldatult tulpade nimed ja
kirjeldused. Järgnevalt on tulpade nimedeks id, teade ja nimi. Tüüpideks vastavalt int, text ja text. NOT
NULL esimese välja taga tähendab, et tulbas ei tohi olla tühiväärtusi; auto_increment aga, et andmete
lisamisel tabelisse paigutatakse sinna lahtrisse automaatselt leitud unikaalne väärtus. PRIMARY KEY(id)
loetelu lõpus näitab, et tulp nimega id on primaarvõtmeks ehk üldjuhul kui viidatakse selle tabeli reale, siis
kasutatakse selleks primaarvõtme unikaalset väärtust.
CREATE TABLE teated2(
id int NOT NULL auto_increment,
teade TEXT,
nimi TEXT,
PRIMARY KEY(id)
);
Järgmisena andmete lisamise lause, mis peaks sellisel kujul kõikidele SQL-andmebaasidele
arusaadav olema. INSERT INTO, millele järgneb tabeli nimi, sulgudes tulpade loetelu kuhu lisatakse,
seejärel sõna VALUES ning edasi väärtuste loetelu. Tekstilised väärtused ülakomade vahel. Sõltuvalt
andmebaasimootorist on sellel käsklusel mitmeid erikujusid mitme rea andmete korraga sisestamiseks või
tulpade nimede ja väärtuste lähemale kirjutamiseks, kui siintoodu peaks kõige üldisem ja töökindlam
olema.
INSERT INTO teated2(teade, nimi) VALUES
('Kool hakkab kell 10', 'Mati');
MySQLi-spetsiifiline kirjeldus tabeli tutvustuse kuvamiseks. Ka teistel andmebaasidel leiab
selliseid kirjeldavaid vahendeid, olgu siis tekstipõhiseid või graafilisi.
mysql> explain teated2;
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | | PRI | NULL | auto_increment |
| teade | text | YES | | NULL | |
| nimi | text | YES | | NULL | |
+-------+---------+------+-----+---------+----------------+
3 rows in set (0.26 sec)
Kõige universaalsem pärngulause. Tärn tähendab, et näha soovitakse kõiki ridu. Tärni asemel
võiks olla ka soovitavate tulpade loetelu.
mysql> select * from teated2;
+----+------------------------------+------+
| id | teade | nimi |
+----+------------------------------+------+
| 1 | Kool hakkab kell 10 | Mati |
| 2 | Võta vihikud kaasa | Kati |
| 3 | Matemaatika vihik on kadunud | Mati |
| 4 | Otsi riiuli tagant | Kati |
| 5 | Mina toon palli | Siim |
| 6 | Mina ka | Mati |
| 7 | Jätke mu ilus kleit valgeks | Kati |
+----+------------------------------+------+
7 rows in set (0.38 sec)
Kui tahta näha vaid erinevaid väärtusi, siis selle juures aitab käsklus distinct. Ehkki Mati on
saatnud tunduvalt rohkem kui ühe teate, siis siin kuvatakse iga nimi ikkagi ainult ühe korra.
mysql> select distinct nimi from teated2;
+------+
| nimi |
+------+
| Mati |
| Kati |
| Siim |
+------+
3 rows in set (0.34 sec)
Kui soovitakse mõne tulba järgi järjestada, siis selleks võib lisada lauseosa order by ning
soovitud tulba nime.
mysql> select * from teated2 order by nimi;
+----+------------------------------+------+
| id | teade | nimi |
+----+------------------------------+------+
| 2 | Võta vihikud kaasa | Kati |
| 4 | Otsi riiuli tagant | Kati |
| 7 | Jätke mu ilus kleit valgeks | Kati |
| 1 | Kool hakkab kell 10 | Mati |
| 3 | Matemaatika vihik on kadunud | Mati |
| 6 | Mina ka | Mati |
| 5 | Mina toon palli | Siim |
+----+------------------------------+------+
7 rows in set (0.07 sec)
Soovides vastava tulba järgi tagurpidises järjestuses tulemust näha, tuleb lisada võtmesõna
desc. Soovides rõhutada päripidist järjestust, võib kirjutada sõna asc, kuid see kehtib ka vaikimisi.
mysql> select * from teated2 order by nimi desc;
+----+------------------------------+------+
| id | teade | nimi |
+----+------------------------------+------+
| 5 | Mina toon palli | Siim |
| 1 | Kool hakkab kell 10 | Mati |
| 3 | Matemaatika vihik on kadunud | Mati |
| 6 | Mina ka | Mati |
| 2 | Võta vihikud kaasa | Kati |
| 4 | Otsi riiuli tagant | Kati |
| 7 | Jätke mu ilus kleit valgeks | Kati |
+----+------------------------------+------+
7 rows in set (0.03 sec)
MySQL võimaldab juba SQL-lauses vastuste arvu piirata. Praegusel juhul seati suurimaks
väljastatavate vastuste arvuks neli. Mõne andmebaasimootori korral on vastavaks piiravaks käskluseks
top.
mysql> select * from teated2 limit 4;
+----+------------------------------+------+
| id | teade | nimi |
+----+------------------------------+------+
| 1 | Kool hakkab kell 10 | Mati |
| 2 | Võta vihikud kaasa | Kati |
| 3 | Matemaatika vihik on kadunud | Mati |
| 4 | Otsi riiuli tagant | Kati |
+----+------------------------------+------+
4 rows in set (0.14 sec)
Siin antakse teada, et soovitakse näha teateid alates kolmandast kaks tükki. Selline piirang on
näiteks mugav lehtede loomisel, kus kõiki andmeid ei soovita korraga ühele lehele paigutada, vaid
vastavalt kasutaja soovile näidatakse järgmisi lehekülgi.
mysql> select * from teated2 limit 3, 2;
+----+--------------------+------+
| id | teade | nimi |
+----+--------------------+------+
| 4 | Otsi riiuli tagant | Kati |
| 5 | Mina toon palli | Siim |
+----+--------------------+------+
2 rows in set (0.00 sec)
Tahtes konkreetse väärtuse järgi piirata väljastatavaid ridu, tuleb selline piirang kirjutada where-
lausesse. Kui tingimusi on rohkem, siis ühendamiseks sobivad sõnad AND ning OR. Võrdlemiseks märgid
< ja > nagu muudelgi puhkudel.
mysql> select * from teated2 where nimi='Kati';
+----+-----------------------------+------+
| id | teade | nimi |
+----+-----------------------------+------+
| 2 | Võta vihikud kaasa | Kati |
| 4 | Otsi riiuli tagant | Kati |
| 7 | Jätke mu ilus kleit valgeks | Kati |
+----+-----------------------------+------+
3 rows in set (0.00 sec)
Loendamiseks sobib käsklus count. Sõnapaar "as nr" avaldise count(*) taga tähendab, et
loenduse tulemus väljastatakse tulbana, mil nimeks nr.
mysql> select count(*) as nr from teated2 where nimi='Kati';
+----+
| nr |
+----+
| 3 |
+----+
1 row in set (0.32 sec)
Ühe tabeliga seotud lihtsamad rakendused enamasti eeltoodud päringutega piirduvadki.
Loendava statistika puhul aga teeb järgnev vahend elu mõnevõrra mugavamaks. Lisand "group by"
võimaldab väljundisse jätta näidatud tulba väärtustest vaid erinevad. Samas mitme rea andmeid
arvestavad funktsioonid nagu count, sum ja avg töötavad siis eraldi iga sellise grupi kohta ning nõnda
võibki leida ühe käsuga iga inimese teadete arvu või kokku kulutatud summa.
mysql> select nimi, count(*) as kogus from teated2 group by nimi;
+------+-------+
| nimi | kogus |
+------+-------+
| Kati | 3 |
| Mati | 3 |
| Siim | 1 |
+------+-------+
3 rows in set (0.01 sec)
Nii nagu harilikel päringutel saab piiranguid seada WHERE-lausega nii gruppide jaoks on
piiranguid tähistav sõna HAVING.
mysql> select nimi, count(*) as kogus from teated2 group by nimi having kogus>1;
+------+-------+
| nimi | kogus |
+------+-------+
| Kati | 3 |
| Mati | 3 |
+------+-------+
2 rows in set (0.01 sec)
Ja saidki ühe tabeliga katsetused ühele poole.
mysql>
Kaks tabelit
Andmebaaside puhul peetakse tähtsaks iga sisestatud väärtust hoida vaid ühe eksemplarina. Et
iga uue kauba ostmisel ei peaks uuesti kirja panema kliendi aadressi või konto numbrit. Kui on vaja
sisestust kontrollida, siis kasutatakse selleks mõnd kontrollsummat või muud piirajat, kuid ideaaljuhul
samu andmeid andmebaasis mitmes kohas ei hoita. Sama lugu nagu koodilõikude puhul: mis kord tehtud,
seda uuesti kirjutada pole hea.
Tabelid seotakse omavahel üldjuhul täisarvudega. Kui allpool loodi tabelid toitude ja jooksjate
tarvis ning jooksjate tabelis olev tulp lemmiktoidu_id näitab toidutabeli vastava ID-numbriga toidule, siis
toitude tabeli ID-tulpa nimetatakse primaarvõtmeks ning tulba lemmiktoidu_id väärtusi võõrvõtmeks.
CREATE TABLE jooksjad (ID int NOT NULL AUTO_INCREMENT, eesnimi varchar(30),
lemmiktoidu_id int, PRIMARY KEY(ID));
mysql> CREATE TABLE toidud(ID int NOT NULL AUTO_INCREMENT, nimetus varchar(30),
PRIMARY KEY(ID));
Tabelitesse mõned väärtused, et oleks pärast mille peal katsetada. Esimesele toidule pannakse
automaatselt järjekorranumbriks 1
mysql> insert into toidud(nimetus) values ('Hernesupp');
Query OK, 1 row affected (0.10 sec)
Ka jooksja id-number pannakse automaatselt. Juku lemmiktoidu number tuleb aga määrata.
mysql> INSERT INTO jooksjad(eesnimi, lemmiktoidu_id) values ('Juku', 1);
Query OK, 1 row affected (0.00 sec)
Väljatrükk näitamaks, milliste andmetega edaspidi katsetatakse. Matile on jäetud lemmiktoit
määramata ning selle välja väärtuseks on NULL.
mysql> select * from jooksjad;
+----+---------+----------------+
| ID | eesnimi | lemmiktoidu_id |
+----+---------+----------------+
| 1 | Juku | 1 |
| 2 | Kati | 1 |
| 3 | Mati | NULL |
+----+---------+----------------+
3 rows in set (0.01 sec)
mysql> SELECT * FROM toidud;
+----+------------+
| ID | nimetus |
+----+------------+
| 1 | Hernesupp |
| 2 | Kapsasupp |
| 3 | Pannkoogid |
+----+------------+
3 rows in set (0.01 sec)
Kõige tavalisem päring paigutamaks ühte tabelisse nii jooksjad kui nende lemmiktoidud.
WHERE-osa puudumisel antaks välja kõikvõimalikud kahe tabeli ridade omavahelised kombinatsioonid.
Praegusel juhul 3*3 ehk üheksa rida. WHERE seab aga piirangu ning välja näidatakse vaid kaks - need,
kus jooksja lemmiktoidu_id vastab toitude tabelis leiduvale ID-veeru väärtusele. Et Mati juures olevat
NULL-väärtust toitude tabeli ID-väärtuste hulgas pole, siis jääb Mati ka nimekirja kuvamata.
mysql> SELECT * FROM jooksjad, toidud WHERE jooksjad.lemmiktoidu_id=toidud.ID;
+----+---------+----------------+----+-----------+
| ID | eesnimi | lemmiktoidu_id | ID | nimetus |
+----+---------+----------------+----+-----------+
| 1 | Juku | 1 | 1 | Hernesupp |
| 2 | Kati | 1 | 1 | Hernesupp |
+----+---------+----------------+----+-----------+
2 rows in set (0.06 sec)
Kui soovitakse kõiki ühe tabeli väärtusi näha ning paremale poole lisada mittetühjadena vaid
need väärtused, mida võõrvõtme kaudu võimalik leida on, siis aitab tabeleid ühendada LEFT JOIN. ON-
lauseosas tuleb siis määrata, millised tulbad omavahel seotud on. Suuremate rakenduste korral võib
nõnda kokku ühendada tunduvalt rohkem kui kaks tabelit. Juhul, kui näiteks soovitakse uurida, millised
sobivas vanuses inimesed töötavad ettevõttes, mille leidub filiaal ka Pärnus.
mysql> SELECT * FROM jooksjad LEFT JOIN toidud ON
toidud.id=jooksjad.lemmiktoidu_id;
+----+---------+----------------+------+-----------+
| ID | eesnimi | lemmiktoidu_id | ID | nimetus |
+----+---------+----------------+------+-----------+
| 1 | Juku | 1 | 1 | Hernesupp |
| 2 | Kati | 1 | 1 | Hernesupp |
| 3 | Mati | NULL | NULL | NULL |
+----+---------+----------------+------+-----------+
3 rows in set (0.00 sec)
Nii nagu failinimede küsimisel aitasid elu hõlpsamaks teha tärn ning küsimärk, nii saab MySQLi
puhul kasutada LIKE-võrdluses protsenti ning alljoont.
mysql> select * from jooksjad where eesnimi like 'J%';
+----+---------+----------------+
| ID | eesnimi | lemmiktoidu_id |
+----+---------+----------------+
| 1 | Juku | 1 |
+----+---------+----------------+
1 row in set (0.01 sec)
mysql> select * from jooksjad where eesnimi like 'J_ku';
+----+---------+----------------+
| ID | eesnimi | lemmiktoidu_id |
+----+---------+----------------+
| 1 | Juku | 1 |
+----+---------+----------------+
1 row in set (0.00 sec)
mysql> select * from jooksjad where eesnimi like 'J_k';
Empty set (0.01 sec)
SQL-keelest võib leida ka komplekti muudeski keeltes kasutatavaid funktsioone. Olgu siis
arvutamise või tekstitöötluse tarbeks.
mysql> select eesnimi, length(eesnimi) from jooksjad;
+---------+-----------------+
| eesnimi | length(eesnimi) |
+---------+-----------------+
| Juku | 4 |
| Kati | 4 |
| Mati | 4 |
+---------+-----------------+
3 rows in set (0.08 sec)
mysql> select eesnimi, left(eesnimi, 1) from jooksjad;
+---------+------------------+
| eesnimi | left(eesnimi, 1) |
+---------+------------------+
| Juku | J |
| Kati | K |
| Mati | M |
+---------+------------------+
3 rows in set (0.07 sec)
Lauluandmetega rakendus
Järgnevalt kinnistatakse eelpool kirja pandud tarkused väikese programmi abil. Andmed on
esiotsa ühes ja pärast kolmes tabelis. Ning väljund saadetakse nii tekstiekraanile kui veebilehtedele.
Alustame võimalikult lihtsast väljamõeldud olukorrast, kus soovitakse meeles pidada laulude
pealkirju ning laulude esitajaid. Viiekümnest tähest kummagi välja salvestamisel võiks piisata. Nagu
tavaks, lisatakse igale tabelireale ka võtmeväli, et oleks hiljem kindlasti võimalik kontreetsetele ridadele
viidata.
CREATE TABLE laulud (
id int(11) NOT NULL auto_increment,
pealkiri varchar(50) default NULL,
esitaja varchar(50) default NULL,
PRIMARY KEY (id)
)
Mõned väljamõeldud andmed sisse
INSERT INTO laulud VALUES (1,'Valged Roosid','Joala');
INSERT INTO laulud VALUES (2,'Kuldkannike','Joala');
INSERT INTO laulud VALUES (3,'Mererannal','Linna');
INSERT INTO laulud VALUES (4,'Kungla Rahvas','Veskimaja');
INSERT INTO laulud VALUES (5,'Koolisellid','Tammik');
ning võibki tulemust imetleda.
mysql> select * from laulud;
+----+---------------+-----------+
| id | pealkiri | esitaja |
+----+---------------+-----------+
| 1 | Valged Roosid | Joala |
| 2 | Kuldkannike | Joala |
| 3 | Mererannal | Linna |
| 4 | Kungla Rahvas | Veskimaja |
| 5 | Koolisellid | Tammik |
+----+---------------+-----------+
5 rows in set (0.30 sec)
Eeltoodud näidete põhjal saab andmeid väljastava käsureaprogrammi kokku küllalt lihtsalt –
juhul kui tarvilikud ühendused on valmis seatud.
import java.sql.*;
public class Laulud1{
public static void main(String argumendid[]) throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
Statement st=cn.createStatement();
String lause="SELECT pealkiri, esitaja FROM laulud";
ResultSet rs=st.executeQuery(lause);
while(rs.next()){
System.out.println(rs.getString("pealkiri")+
" "+rs.getString("esitaja"));
}
cn.close();
}
}
Kui programm tööle panna, võib ka väljundit näha.
C:\temp>Java Laulud1
Valged Roosid Joala
Kuldkannike Joala
Mererannal Linna
Kungla Rahvas Veskimaja
Koolisellid Tammik
Kui andmeid rohkem või kliendid üle võrgu kaugemal, siis muudab servletiväljund andmed
kergemini kättesaadavaks. Kui veebiserver jookseb avalikult kättesaadavas masinas, siis piisab kasutajal
teada vaid rakenduse aadressi ning võibki omale vajaliku teabe ekraanilt ammutada.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class Laulud2 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/plain");
PrintWriter valja = vastus.getWriter();
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
Statement st=cn.createStatement();
String lause="SELECT pealkiri, esitaja FROM laulud";
ResultSet rs=st.executeQuery(lause);
while(rs.next()){
valja.println(rs.getString("pealkiri")+
" "+rs.getString("esitaja")+"\n");
}
cn.close();
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Standardile vastav lehekülg.
HTMLi keel on aastate jooksul arenenud. Algsest kümnekonna käsuga
tekstiillustreerimisvahendist kasvas välja sajakonna käsuga kujundusvahend. Seiluritootjad on omalt
poolt võimalusi lisanud ning aegapidi on neid ka standardisse võetud. W3-konsortsium on taoline
firmade ja muude asutuste ühendus, mille kaudu lepitakse kokku veebiga seotud standardeid. Nõnda on
rohkem lootust, et kusagil koostatud leheküljed või muud failid ka mujal kasutatavad on.
Standard pannakse kirja kas tekstilise kirjeldusena, tabelina, XML-failide puhul ka DTD või
Schema abil. Programmeerija loeb kirjeldust ja püüab tulemuse selle järgi sättida, kuid iga inimene tahab ja
vajab tagasisidet, et kas tema koostatu ka loodetud ootustele vastab. Kui Pascali, C või Java koodi
kirjutada, siis teatab kompilaator süntaksivead julgesti välja. Veebilehte avades aga on seilur
tagasihoidlikum ning väikesed näpuvead jäävad enamasti märkamata. Mõnikord võib aimamine nii ilusti
välja tulla, et lehte vaadates ei leia mingit märki HTML-koodi trükiveast. Teises seiluris võib aga aimamise
algoritm muud moodi käituda ning lehe sisu võib imelikult paista või sootuks märkamatuks jääda. Taoliste
viperuste vältimiseks saab kasutada HTMLi validaatorit - programmi kontrollimaks HTMLi õigekirja. Siin
uuritakse, et elementide nimed oleks õigesti kirjutatud, lõpetamist vajavad elemendid lõpetatud ning et
elemendid paikneksid ka üksteise sees lubatud kujul.
Et validaator teaks millise standardi järgi kontrollida, peab vastav rida ka faili alguses kirjas
olema. Lisaks on päises nõutud ka pealkirja ja kooditabeli märkimine. Edasi mõistab validaatorprogramm
ülal kirjeldatud standardi järgi lehte kontrollima hakata.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class Laulud3 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("");
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
Statement st=cn.createStatement();
String lause="SELECT pealkiri, esitaja FROM laulud";
ResultSet rs=st.executeQuery(lause);
valja.println("
");
while(rs.next()){
valja.println("
"+rs.getString("pealkiri")+
"
"+rs.getString("esitaja")+"
");
}
valja.println("
");
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Laulude nimekiri
Valged Roosid
Joala
Kuldkannike
Joala
Mererannal
Linna
Kungla Rahvas
Veskimaja
Koolisellid
Tammik
Kontrollija leiab aadressilt http://validator.w3.org/ Seal võimalik kontrollimiseks pakkuda nii
veebiaadress kui oma kettal leiduv fail. Esimest võimalust on mugav kasutada, kui koostatav leht avalikult
veebi kaudu kättesaadav. Kui aga kohalik veebiserver otse avalikku võrku ei paista, siis saab servleti
loodud HTML-faili kohalikku masinasse salvestada ning siis võrku üles laadida.
Leht kontrollijasse loetud, antakse sealt vastus. Kui päiserida puudu või vigane või mõni
tähtsam element puudu, siis antakse vastav teade. Suudab aga validaator lehe struktuurist aru saada, siis
vaadatakse kõik kujunduselemendid ükshaaval üle ning kui kusagil midagi kahtlast, siis antakse teada.
Mitmete teineteise sees paiknevate tabelite või taanete korral oleks käsitsi käskude ja nende paiknemise
õigsuse kontroll küllaltki vaevaline. Validaator aga leiab kohe üles kui mõni märk vales kohas, üle või
puudu. Siis ei jää muud midagi üle, kui veateadet uurida, leida, et mille poolest siis loodud HTML kahtlane
on. Koodis püüda koht parandada ning uuesti proovida. Kui pilt tundub väga segane ning kuidagi ei
oska veateate asukohta leida, siis aitab jupikaupa uurimine. Et algul tõsta uude tühja faili vaid päise ja
tühja body-osaga leht ning kontrollida selle korrektsust. Siis tasapisi lisada/kopeerida sisemisi elemente ja
iga sammu järel kehtivust kontrollida. Nii on kohe selge, millise sammu juures raskus tekkis ning võimalik
seda sammu lähemalt uurida. Vajadusel osadeks jagada ning uuesti uurida. Mõnikod võib ka juhtuda, et
pealtnäha oleks justkui kõik korras, aga sellegipoolest näidatakse, et ühes kindlas kohas on midagi viltu.
Sellisel juhul võib põhjuseks olla miski klahvikombinatsiooni tulemusena tekkinud salapärane sümbol,
millest siis kustutamise ja uuesti kirjutamise abil võitu saab. Ning kui lõpuks õnnestub validaatorilt välja
meelitada teade lehekülje korrektsuse kohta, siis võib tulemusega rahule jääda. Ehkki tasuks korrektsust
kontrollida ka mitmesuguste andmete põhjal sama servleti loodud lehtede puhul.
Sortimine
Vähegi pikemate loetelude puhul võib sobiva väärtuse leidmine päris tülikaks osutuda. Find-
käsu kõrval on sobivaks vahendiks järjestamine. Andmebaasi põhjal toimivate rakenduste eeliseks on
võimalus paari sõna abil määrata väljastatavad andmed soovitavasse järjekorda. Suuremaks nuputamiseks
on, et mille põhjal seda järjestust määrata. Lihtsam tundub olema jagada ülesanne kaheks osaks. Ühelt
poolt valmistada ette lehekülg, mis väljastaks andmed etteantud parameetritele vastavas järjekorras. Ning
teiselt poolt hoolitseda, et kasutajal oleks võimalikult mugav järjestust määrata. Esimeses lähenduses võib
andmeid väljastavat lehte vaadelda kui musta kasti. Servletile antakse andmeid ette nagu veebilehtede
puhul ikka - aadressirea parameetrite kaudu. Määran, et parameetri nimeks oleks "sorttulp" ning
väärtuseks tulba nimi, mille järgi soovitatakse sortida. Seega siis, kui lehe aadressiks oleks
http://masinanimi/kataloog/servletinimi?sorttulp=esitaja
siis tuleksid andmed lehele järjestatuna tähestikulises järjekorras esitaja järgi. Kui aga
http://masinanimi/kataloog/servletinimi?sorttulp=pealkiri
siis tuleksid tähestiku algupoolel asuvad pealkirjad eespool nähtavale.
SQL-keeles määratakse sorteerimist määrav tulp lauseosa ORDER BY järgi. Lihtsaim lahendus oleks
kasutaja poolt tulnud tulba nimi otse sellesse lausesse paigutada ning tulemus välja näidata. Et aga
veebirakenduste puhul soovitakse kõiki veebi poolt tulevaid andmeid umbusaldada, siis saabuvat
parameetrit SQL-lausesse ei kirjutata. Esiteks tekiks probleem pahatahtliku sisestuse korral, mis võiks
sobivalt seatuna muudele andmetele liiga teha või neid välja näidata. Teiseks mureks on aga, et vigase
sisestuse korral tuleks nähtavale süsteemi loodud veateade või sootuks mitte midagi. Viisakam oleks aga
kasutajale sõnaliselt teada anda, mis lahti on.
Siin näites on sorteerimist määrava tulba nimi küsitud muutujasse sort. Vaikimisi väärtuseks on
"id". On aga parameetriks pealkiri või esitaja, siis määratakse see sorteerimist seadvaks tulbaks. Nõnda
pole võimalust, et kasutaja saadetud suvaline sisestus andmeid võiks rikkuda või kahtlase veateate
ekraanile manada, vaid igasuguse vigase sisestuse korral näidatakse ekraanile read sorteerituna id järgi.
Kui sobiva tulba nimi lauses olemas, siis võib andmed ekraanile näidata nagu eelmiselgi korral.
Teiseks tuleb miskil kasutajale mugavamal ja vastuvõetavamal moel anda võimalus
sorteerimisjärjekord valida. Ning tulba nime aadressireal sissetippimine ei pruugi selleks mitte olla. Siinne
"
"+
näitab, kuidas võimalik viide nõnda koostada, et see samale lehele näitaks ning sorteerimiseks sobiva
tulba nimi andmetena kaasa antaks. HttpServletRequesti käsklus getRequestURI annab tekstina välja
jooksva lehe URLi alates serveri juurkataloogist, piisab juurde lisada vaid sobivad parameetrid. Teise
võimalusena saaks jooksva kataloogi puhul kirjutada ka vaid failinime. Kui iga tulba pealkirjal taoline viide
küljes, jääbki kasutajale võimalus, et igale pealkirjale vajutades sorteeritakse andmed vastava tulba järgi.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class Laulud4 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("
Laulude nimekiri
");
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
Statement st=cn.createStatement();
String sort="id";
String sortTulp=kysimus.getParameter("sorttulp");
if(sortTulp==null){sortTulp="id";}
if(sortTulp.equals("pealkiri")){sort="pealkiri";}
if(sortTulp.equals("esitaja")){sort="esitaja";}
String lause="SELECT pealkiri, esitaja FROM laulud ORDER BY "+sort;
ResultSet rs=st.executeQuery(lause);
valja.println("
");
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Vaikimisi järjestus id järgi ning
määratud järjestus pealkirja järgi.
Andmete lisamine
Lihtsa otsinguvahendi puhul võib baasi veebiliides piirduda andmete välja näitamisega.
Muutmised-lisamised võetakse ette andmebaasi enese vahenditega, või on selle tarvis hoopis omaette
rakendus loodud. Turvalisuse mõttes on veebist vaid vaatamist lubav rakendus lihtsam - pahalastel pole
kuigivõrd põhjust ega võimalust andmeid ohtu seada. Ohtudeks jäävad vaid serveri või võrguühenduse
võimsust ületav päringute hulk või logifailide abil ketta täiskirjutamise oht. Kui ka veebi kaudu liituval
kasutajanimel pole andmebaasis muutmise õigusi, siis võib taolisel rakendusel suhteliselt mureta toimida
lasta.
Nõudmiste kasvades aga vaid vaatamiseks mõeldud rakendusest ei piisa. Andmete
veebikaudseks lisamiseks on võimalused täiesti olemas, lihtsalt peab arvestama pahatahtlike kasutajate
tekitatud segaduste ohuga.
Andmete lisamiseks tuleb need kasutajalt kõigepealt kätte saada. Praegusel juhul on selleks
otstarbeks tarvitatud tekstivälju. Graafikakomponendid jällegi vormi sees. Atribuut action='#' tähendab
jällegi, et andmete vastuvõtjaks on sama leht uuel avamisel. Võrreldes muude näidetega, on siin ka
submit-nupule nimi antud. Vormi andmete teele saatmisel pannakse kaasa nimega komponentide andmed,
subm
it-nupu puhul vajutatud nupu andmed. Nõnda on võimalik kontrollida, kas nuppu vajutati. Ning mitme
nupu puhul kontrollida, et millist nuppu vajutati.
Siinse lehe avamisel kontrollitakse, kas parameeter "lisamine" pole null. Et kas vajutati
lisamisnuppu. Vaid sel juhul koostatakse SQL-lause andmete lisamiseks ning käivitatakse.
PreparedStatement'i eelis tavalise käskluse ees on, et kasutaja saadetud erisümbolid ei saa serveris
segadust tekitada, vaid need kirjutatakse samasuguse rahuga edasi andmebaasi tabelisse. Muu andmete
näitamise osa sarnane kui eespool.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class Laulud5 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("
Laulude nimekiri
");
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
Statement st=cn.createStatement();
if(kysimus.getParameter("lisamine")!=null){
PreparedStatement ps=cn.prepareStatement(
"INSERT INTO laulud (pealkiri, esitaja) VALUES (?, ?)"
);
ps.setString(1, kysimus.getParameter("pealkiri"));
ps.setString(2, kysimus.getParameter("esitaja"));
ps.executeUpdate();
}
String sort="id";
String sortTulp=kysimus.getParameter("sorttulp");
if(sortTulp==null){sortTulp="id";}
if(sortTulp.equals("pealkiri")){sort="pealkiri";}
if(sortTulp.equals("esitaja")){sort="esitaja";}
String lause="SELECT pealkiri, esitaja FROM laulud ORDER BY "+sort;
ResultSet rs=st.executeQuery(lause);
valja.println("
");
valja.println(
""
);
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Lisamisleht
? Algul
? Väärtuste sisestamine
? Pikenenud loetelu
Mitme tabeliga rakendus
Märgatav osa lihtsatest veebirakendustest võibki ühe tabeliga piirduda. Harilikuks andmete
lisamiseks ning välja näitamiseks piisab sellest sageli. Kui mõista sobivalt pealkirju valida ning mõnikord
ka andmete põhjal tulemusi arvutada, siis võib ühe tabeliga näite edukalt kokku panna nii külalisraamatu,
tunniplaani, sünnipäevade hoidla kui muudki. Keerukamate andmete puhul ei pruugi aga kõige ühes
tabelis hoidmine kuigi ökonoomne olla.
Andmebaasiteoreetikud soovitavad, et võimaluse korral tuleks andmed sättida nii, et midagi
korduvalt ei hoitaks. Andmete korrektsuse jaoks kasutatagu pigem kontrollsummasid, kui mingi väärtus
aga kusagil kirjas, siis sama asja poleks võimaluse korral mõistlik enam kusagile mujale kirjutada. Ehk sama
põhimõte nagu koodigi kirjutamise juures: mis kord tehtud, seda püüa ka tulevikus kasutada, mitte sama
asja uuesti kirja panema hakata.
Liiatigi võib vaja minna küllalt erinevaid andmeid. Näiteks inimeste ja bussiliinide andmeid oleks
teoreetiliselt võimalik hoida samas tabelis, kuid üldjuhul ei tundu see loogiline. Ehkki taolist andmete
segamist vahel kasutatakse, ei tohiks see mitte harilike rakenduste puhul tavaks saada. Pigem tulevad
kindla semantikata tabelitulbad ette rakendustes, kus programmeerija mõtleb objektide tasandil ning
andmete talletamise ja väljalugemise eest otsustab eraldi koostatud vahelüli. Siin aga püüame tabeli
andmed mõistetavad hoida ning nende kohale üheselt mõistetava veebiliidese koostada.
Lisaks laulude andmetele koostame tabelid heliplaatide tarbeks: väljadeks plaadi nimi ning
väljalaskeaasta.
CREATE TABLE plaadid (
id int(11) NOT NULL auto_increment,
plaadinimi varchar(30) default NULL,
aasta int(11) default NULL,
PRIMARY KEY (id)
);
Ning mõned andmed ka sisse.
INSERT INTO plaadid VALUES (1,'Tantsulood',1985);
INSERT INTO plaadid VALUES (2,'Laululood',1988);
Samuti loome juurde tabeli, mille abil määratakse, millised lood millise plaadi juurde kuuluvad.
Seosetabelisse otseseid tekste ei kirjutatagi. Tabelis on kolm tulpa: id, plaadi_id ning laulu_id. Iga taoline
rida märgib üht seost, kus märgitakse, et vastava reanumbriga laul kõlab märgitud numbriga plaadil.
Sellist teise tabeli reale viitava lahtri väärtust nimetatakse võõrvõtmeks (foreign key). MySQL
neljandas tavaversioonis veel ei kontrolli võõrvõtmete viidete korrektsust, see jäetakse programmeerija
hooleks. Mõne tabeliga ja paarisaja andmereaga rakenduse puhul saab sellise järje pidamisega täiesti
hakkama. Kui aga andmete hulk ja keerukus märkimisväärselt kasvab, siis aitab andmebaasi võime viidete
korrektsust kontrollida süsteemi töökindlust tagada ning murdunud ja vigastest viidetest hoiduda.
mysql> CREATE TABLE laulud_plaadid(id INT NOT NULL auto_increment PRIMARY KEY,
laulu_id INT, FOREIGN KEY lauluvoti (laulu_id) REFERENCES laulud (id), plaadi_id
INT, FOREIGN KEY plaadivoti(plaadi_id) REFERENCES plaadid(id));
Kui võõrvõtme loomist käsuna eraldi mitte kirja panna, siis tegelikult on tegemist lihtsa kolme
tulbaga tabeliga.
CREATE TABLE laulud_plaadid (
id int(11) NOT NULL auto_increment,
laulu_id int(11) default NULL,
plaadi_id int(11) default NULL,
PRIMARY KEY (id)
)
Järgnevalt näide, kuidas võivad andmed tabelites paikneda. Kõigepealt laulude tabel:
mysql> select * from laulud;
+----+---------------+-----------+
| id | pealkiri | esitaja |
+----+---------------+-----------+
| 1 | Valged Roosid | Joala |
| 2 | Kuldkannike | Joala |
| 3 | Mererannal | Linna |
| 4 | Kungla Rahvas | Veskimaja |
| 5 | Koolisellid | Tammik |
| 6 | Karsumm | Kask |
+----+---------------+-----------+
6 rows in set (0.03 sec)
Siis plaadid:
mysql> select * from plaadid;
+----+------------+-------+
| id | plaadinimi | aasta |
+----+------------+-------+
| 1 | Tantsulood | 1985 |
| 2 | Laululood | 1988 |
+----+------------+-------+
2 rows in set (0.05 sec)
Ning edasi seosetabel, milliseid laule millistelt plaatidelt leida võib. Näiteks viimasest reast annab
välja lugeda, et seos id-numbriga 3 teatab, et laulu number 4 ehk "Kungla rahvas" võib kuulda plaadilt
number 1 ehk "Tantsulood".
mysql> select * from laulud_plaadid;
+----+----------+-----------+
| id | laulu_id | plaadi_id |
+----+----------+-----------+
| 1 | 1 | 1 |
| 6 | 2 | 2 |
| 3 | 4 | 1 |
+----+----------+-----------+
3 rows in set (0.06 sec)
Hoolimata sellest, et tabelitesse laiali jagatuna võib andmete paiknemine keerukas tunduda, annab SQL-
lausetega väärtused küllalt sobival kujul välja küsida. Tabeleid ühendavaid lauseid annab mitmel kujul
kirja panna, üks neist näha järgnevalt; seletus:
Küsimiseseks SELECT-lause nagu ikka. Järgneb näha soovitavate tulpade loetelu. Mõnikord
võivad tulpade nimed eraldi tabelitest kattuda. Sellisel juhul tuleb määrata tulba nimi koos tabeli nimega -
näiteks kujul plaadid.id . Edasi järgneb FROM ning kasutatavate tabelite loetelu. Edasi tuleb seada,
milliste tabelite millised tulbad omavahel seotud on. Kuna praegu tabeli laulud_plaadid laulu_id näitab
laulude tabeli id-veerule ning laulud_plaadid plaadi_id näitab plaatide tabeli id-veerule, siis see tuleb siin
ka kirja panna.
mysql> SELECT pealkiri, plaadinimi FROM laulud, laulud_plaadid, plaadid WHERE
laulud.id=laulud_plaadid.laulu_id AND laulud_plaadid.plaadi_id=plaadid.id;
Päringu tulemuseks nagu ikka - andmed tabeli kujul.
+---------------+------------+
| pealkiri | plaadinimi |
+---------------+------------+
| Valged Roosid | Tantsulood |
| Kungla Rahvas | Tantsulood |
| Kuldkannike | Laululood |
+---------------+------------+
3 rows in set (0.03 sec)
Edasi tuleb vaid servlet päringule ümber kirjutada ning tulemus lehele välja näidata. Väärtusi saab küsida
sarnaselt tulba nime järgi nagu ühestki tabelist andmeid korjava päringu korral.
valja.println("
"+rs.getString("pealkiri")+
"
"+rs.getString("plaadinimi")+"
");
Teiseks võimaluseks oleks väärtuste küsimine tulba järjekorranumbri järgi. Eriti on sellest kasu
juhul, kui tabelite liitmise tulemusena võivad loetellu sattuda samanimelised tulbad. Samuti tasuks
väärtusi küsida tulpadega samas järjekorras. Mõne draiveri ja baasi puhul on võimalik väärtusi küsida
vaid ühes järjekorras: tulpi vasakult paremale ning ridu ülevalt alla. Kui pole olulist põhjust taolisest
järjestusest kõrvale hoidmiseks, siis tasuks seda järgida. Siis tekib vähem probleeme rakenduse teise
serverisse või teise baasi külge tööle panekul.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class LauludPlaadil1 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("
");
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Ning servleti töö tulemuseks on ilus lihtne tabel.
Nõnda nagu laule üldloendisse lisatakse, nii ka tuleb luua võimalus määramiseks, millised laulud
millistele plaatidele lindistatud on. Ehkki tehniliselt tuleb vaid laule ja plaate siduvasse tabelisse lisada
kaks id-numbrit, on kasutajal mugavam nime järgi valida, milline plaat ja milline pala omavahel ühendatud
on.
Et nii laulude kui plaatide nimed juba kirjas, siis pole neid mõistlik enam uuesti sisse kirjutada.
Kuni andmeil on kuni mõniteist, siis võiks sobiva väärtuse välja valimiseks sobida rippmenüü. Suuremate
mahtude puhul tuleks kasutada järjestamist, raadionuppe või otsinguvahendit. Siinne näide töötab
rippmenüüga. Samuti on mõistlik lasta kasutajal valida väärtuste hulgast, tegelikult serverisse
salvestamiseks saata aga id-numbrid. Rippmenüül tuleb selleks iga väärtuse (option) juurde kirjutada
väärtus (value), mida siis tegelikult serverisse saata soovitakse.
valja.println(" ");
Selline ID-numbrite järgi valimine ja andmete näitamine lihtsustaks ka näiteks rakenduse
tõlkimist: kui andmeid oleks vaja mitmes keeles näidata, siis numbrid võiksid samaks jääda, tõlkida oleks
tarvis vaid sõnu.
Lisamine näeb laulude tabeliga võrreldes välja küllalt sarnane. Et servleti parameetritena
saabuvad vaid ühendatava laulu ja plaadi ID-numbrid ning need numbrid ongi vaja seosetabelisse
kirjutada, siis pole muud kui väärtused tabelisse kirjutada. Et andmete lisamine on lehel tulemuste
näitamisest eespool, siis võib juba kohe pärast lisamist näha, et valitud laulu ja plaadi paar ka loetellu on
ilmunud.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class LauludPlaadil2 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("
");
valja.println("");
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Järgnevatel piltidel võibki imetleda, kuidas lisati laul "Mererannal" plaadile "Tantsulood",
Kustutamine
Enamikel asjadel on nii algus kui ots. Ehkki suuremate andmebaaside puhul soovitatakse andmed
kustutamise puhul arhiveerida ning arhiveerimisatribuudi abil määrata, et seda rida pole vaja enam
arvestada. Nõnda on kergem jälgida baasi arengulugu ning võimalike vigade puhul olukord taastada.
Rakendust jälle lihtsam teha ning omal pilt selgem, juhul kui toimimiseks hädavajalikke veerge ja andmeid
vähem on.
Arvutikettalt kustudades kipuvad andmed kasutaja jaoks jäädavalt kadunud olema. Kas ja kui
täpselt on endist seisu võimalik taastada, sõltub serveri andmete varundamisest ning andmebaasimootori
vastavatest võimalustest. Küllalt sageli seetõttu lisatakse pärast kustutatavate andmete märkimist
kasutaja tarbeks koht üle kontrollimaks, kas ikka soovitakse kustutada. Siin näites aga on piirdutud
lihtsama võimalusega ning lisakontrolli ette ei võeta.
Kustutuskasutajaliidese võimalusi on mitmeid. Lihtsaim on luua arvatavasti lehte, kus iga
andmerea taga on viide, millele vajutades vastav rida baasis kustutatakse. Kas siis kustutustoiming
tehakse eraldi servleti abil ning siis suunatakse kasutaja loetelulehele tagasi või on kustutusoskused juba
loetelulehele sisse ehitatud, see on juba realiseerimise küsimus. Esimese võimaluse eeliseks on, et lehe
värskenduse korral ei saadeta kustutusteavet uuesti.
Tehniliselt ligikaudu sama lihtne või keerukas on võimalus lasta kasutajal raadionupuga määrata
kustutatav rida. Ikkagi jõuab andmeid vastu võtvale lehele kaasa kustutamist määrav väärtus, enamasti
kustutatava rea id-number.
Siin näites aga tehti läbi võimalus, kus kasutaja saab kustutamiseks valida need read, mida ta
parajasti soovib. Sellise olukorra lahendus on keeleti erinev. Üheks ning siingi kasutatud võimaluseks on
määrata kõigile märkeruutudele sama nimi. Vaid elemendi väärtus on igal puhul erinev.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class LauludPlaadil3 extends HttpServlet {
public void doGet(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
vastus.setContentType("text/html");
PrintWriter valja = vastus.getWriter();
valja.println("");
valja.println("");
valja.println("");
valja.println("Laulude nimekiri\n");
valja.println("
");
valja.println("");
valja.println(" Laulude haldus");
cn.close();
valja.println("");
valja.println("");
}catch(Exception viga){
viga.printStackTrace(valja);
}
}
}
Nõnda koostatuna jõuab serverisse sama nimega nii mitu parameetrit, palju kasutaja märkeruute
märkinud on. Mõnikord on taoliste andmete välja lugemine keeruline – ka Java puhul suudab käsklus
getParameter väljastada vaid parameetri ühe väärtuse. Õnneks on siin kasutada ka käsklus
getParameterValues, mis väljastab küsitud nimega parameetrite väärtused massiivina. Ning siin on näha
ka PreparedStatement'i tähtsam kasutusvaldkond. Olukord, kus sarnast lauset tuleb käivitada mitmete
andmetega. Kui õnnestub, siis PreparedStatement'i luues harutatakse SQL-tekst lahti masina jaoks
kiiremini käsitsetavale kujule (näiteks kahendpuusse) ning järgmistel kordadel kulub energiat vaid uute
andmetega käskluse andmebaasiserveris käivitamiseks.
Kustutuslehelt saadetakse kasutaja jälle algsele lehele tagasi.
vastus.sendRedirect("LauludPlaadil3");
Sel käsklusel on mõtet olukorras, kus midagi pole veel välja trükitud. Nii saab HTTP päiste kaudu
teada anda, et soovitakse hoopis järgmise lehe avamist. Ning seiluri ülesandeks on siis sobivat lehte
avama hakata.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class Kustutus extends HttpServlet {
public void doPost(HttpServletRequest kysimus,
HttpServletResponse vastus)
throws IOException, ServletException
{
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn=DriverManager.getConnection(
"jdbc:odbc:esimene", "", "");
PreparedStatement ps=cn.prepareStatement(
"DELETE FROM laulud_plaadid WHERE id=?"
);
String[] vastused=kysimus.getParameterValues("kustutus");
if(vastused!=null){
for(int i=0; iPealkiri"+
"