Tekijä: Marko Seppänen
Teostyyppi: harjoitustyö
Oppilaitos: Saimaan ammattikorkeakoulu
Julkaisuajankohta: Joulukuu 2009
Kurssi: Tietokantaohjelmointi
1 Johdanto
2 Google Web Toolkitistä
3 Ext GWT:stä
4 Google Web Toolkitin RPC (harjoitus 1)
5 Vastaussanomien tietorakenteita tietokantakyselyissä (harjoitus 2)
5.1 Käyttötapauskaavio
5.2 Vastaussanoman tietorakenteena serialisoitu luokka
5.3 Vastaussanoman tietorakenteena HTML-koodia String-kääreessä
5.4 Vastaussanoman tietorakenteena XML, Grid-komponentille
6 Otteita tietokannan täyttöskriptistä
7 JDBC
7.1 Vaiheet tietokannan käyttämiseksi Java-ohjelmassa
7.2 JDBC-ajureita
8 Termiluettelo
Tarkoituksenani oli laajentaa ja syventää Java-osaamistani toteuttamalla
Google Web Toolkitillä ja Ext Gwt:llä prototyyppimainen sovellus, jonka toteuttamisessa
joutuisin käsittelemään erilaisia tietorakenteita, tietokannasta saatujen
vastausten transformointia, sekä käyttöliittymäkomponentteja. Tämä on myös
amk:n Tietokantaohjelmointi-kurssin korvaava harjoitustyö. Kurssilla
ohjelmointikielenä olisi ollut C# ja ohjelmointiympäristönä Visual Studio,
mutta valitsin mieluummin jatkavani parempaan vauhtiin päässyttä Java/Eclipse
-kombinaation käyttöä.
Näin tässä myös hyvän tilaisuuden luoda mentaalisia yhteyksiä muihin käymiini kursseihin kanssa mm. UML-kaavioiden (Ohjelmiston määrittely ja suunnittelu), RPC:n (Hajautetut sovellukset), suunnittelumallit (Olio-ohjelmoinnin suunnittelumallit) osalta. Käytetty tietokanta oli alun perin tarkoitus tehdä uusiksi noudattaen hyvää suunnittelutapaa eli käsiteanalyysiä ja ER-kaavioita, mutta nämä tulivat paremmin käsitellyiksi toisessa yhteydessä, joten päätin jättää tämän prototyyppisovelluksen osalta tietokannan nykyisenlaiseksi. Sovellus itsessään on syntynyt kahteen mainitun ohjelmistokehyksen ominaisuuksiin tutustumisen tahdissa, kertaalleen rönsyjä karsien.
Kehityksen vaiheista on kolme videota Vimeossa:
· http://vimeo.com/7108775 (vaihe 3)
· http://vimeo.com/6955869 (vaihe 2)
· http://vimeo.com/6709667 (vaihe 1)
Google Web Toolkit (GWT) on ohjelmistokehityspaketti
selainpohjaisten sovelluksien rakentamiseen.

Googlen Web Toolkit mahdollistaa websovellusten kirjoittamisen Java-kielellä, ilman että sovelluskehittäjän tarvitsee tietää Javascriptistä yhtään mitään. GWT kääntää asiakaspuolen koodin sellaiseksi optimoiduksi Javascript-koodiksi, että sovellus toimii kaikissa merkittävimmissä selaimissa yhtäläisesti. Henkilölle, jonka ohjelmointikielien oppiminen on mennyt järjestyksessä ”ensin Javascript, HTML, PHP ja sitten vasta Java” tässä on jotain fantastista.
Google Web Toolkit, kuten myös Ext GWT käyttävät etäkutsuihin (sovelluksen selainpuolen osa, asiakas, lähettää kutsun palvelinosalle) RPC:tä (en. remote procedure call). RPC on yleisnimike, joka voidaan toteuttaa usealla eri tavalla. GWT tarjoaa mekanismin, joka sisältää tehokkaan ajonaikaisen asiakas- ja palvelinpuolen koodin generoinnin siirrettävien objektien serialisointiin ja käännöksen Javasta Javascriptiksi. GWT ei kuitenkaan sido ohjelmoijaa jonkin tietyn RPC mekanismin käyttäjäksi, sillä tarjolla on RequestBuilder-luokka omien siirtotemekanismien rakentamiseen, sekä mahdollisuus kolmannen osapuolen kehittelemien kirjastojen käyttöön.
Ajonaikainen muunnos Javasta Javascriptiksi yksinkertaisesti vain toimii, sitä ei missään vaiheessa joudu erityisesti ajatelleeksikaan, että koodi mitä selainpuolella ajetaan, on Javascriptiä. Myös olioiden, primitiivityyppien, sekä erilaisten tietorakenteiden ja mallien siirto palvelimelta selaimelle, ja toisinpäin, sujuu näppärästi serialisointia hyödyntäen; Javalla toteutetut oliot ja muut muuntuvat lennossa Javascriptin vastaaviksi.
GWT-sovelluksia ajetaan webselaimessa, mutta kutsut, viestit ja käskyt, joita sovellus lähettää sovelluksen palvelinosalle, eivät aiheuta websivun uudelleen lataamista, vaan Ajax-hengessä sovellus käyttää taustalla Javascriptin XMLHttpRequest-kutsuja, jotka päivittävät sovelluksesta/sivusta vain tarpeellisen osan tai hakevat palvelimelta käynnissä olevan metodin tarvitsemaa lisädataa.
Javascript asettaa tietyt rajoitukset sille mitä kaikkea voidaan tai on haluttu Javasta emuloida. Tämä pätee kuitenkin vain sovelluksen selaimen puoleiseen osaan. Palvelimen puolella emulointia ei tehdä, vaan mahdollisia rajoituksia asettaa lähinnä asennuskonfiguraatio (J2EE:n versio, sovelluspalvelimen versio, jne.). Lista selainpuolella emuloiduista Java-kirjastoista löytyy osoitteesta:
http://code.google.com/webtoolkit/doc/1.6/RefJreEmulation.html
GWT:ssä on mukana omatkin käyttöliittymäkomponenttinsa, mutta tässä dokumentissa esiteltyjen harjoitustöiden osalta komponentteina käytetään Ext GWT:n komponentteja. Esimerkit on toteutettu GWT:n versiolla 1.7. Kirjoitushetkellä uusin versio oli 2.0.
Ext GWT on Java-kirjasto, joka tarvitsee osakseen ja laajentaa Google
Web Toolkittiä. Ext GWT avulla on mahdollista toteuttaa Rikkaita
Internet-sovelluksia, jollaiseksi Rich Internet-Applications (RIA) tavataan
suomeksi kääntää. RIA-sovellukset muistuttavat toiminnoiltaan perinteisiä
työpöytäsovelluksia, joita ajetaan webselaimessa ja joka keskustelee palvelimen
kanssa asynkronisesti Ajax-teknologioita hyödyntäen.
Ext GWT tarjoaa runsaan joukon erilaisia käyttöliittymäkomponentteja, joita on nappuloista, erilaisista paneeleista ja ruudukoista asettelumanagereihin, kaavioihin ja työkalupalkkeihin. Komponenteilla on tietty perusulkoasu, mutta niitä voi kustomoida CSS-tyylisivustandardin avulla. Kuten GWT:kin se tukee etäkutsuja (vastaussanomiksi kelpaavat niin JSON, XML kuin GWT RPC) ja Javascriptistä ei tarvitsee tietää välttämättä mitään (ohjelmointi Java-kielellä, Javan versio 1.5 ominaisuudet käytettävissä).

Seuraavassa käydään läpi sellaisen yksinkertaisen
GWT-sovelluksen vaatima koodi, missä palvelin vastaa metodikutsuun
palauttamalla List-tietorakenteessa nipun itse luodun Message-luokan
instansseja. Oman luokan instassin palauttamiseksi verkon yli on luokan
implementoitava Serializable-rajapinta. Javan omien luokkien ja primitiivityyppien
palauttamiseksi pätevät samat säännöt kuin tietotyyppien määrityksessä
yleensäkin eli niiden geneerisyys on määriteltävä.

Kaikki esimerkkiohjelmaan liittyvät tiedostot, joita ohjelmoija itse on joutunut käsittelemään, näkyvät ylhäällä.
Hakemistoon war/hello GWT:n kääntäjä vie mm. optimisoidun Javascript-koodin ja muita tarpeellisiksi katsomiaan tiedostoja. Konfiguraatiotiedostossa hello/hello.gwt.xml on olennaista määritys entry-point. Se kertoo mistä luokasta ohjelman käynnistys alkaa. Luokka myös implementoi EntryPoint-rajapinnan.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <module rename-to="hello"> <!-- Inherit the core Web Toolkit stuff. --> <inherits name="com.google.gwt.user.User" />
<!-- Inherit the default GWT style sheet. You can change --> <!-- the theme of your GWT application by uncommenting --> <!-- any one of the following lines. --> <inherits name="com.google.gwt.user.theme.standard.Standard" /> <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> --> <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->
<!-- Other module inherits -->
<!-- Specify the app entry point class. --> <entry-point class="hello.client.Hello" /> </module> |
Kaavamaisesti GWT RPC -kutsun voisi esittää allaolevanlaisesti. Kuva on lähes vastaava kuin ohjelmistokehyksen verkkosivuilla oleva.

Sovelluksen käynnistävä luokka ei tee sen kummempaa kuin sen, että se heti alkuunsa lähettää asynkronisen kyselyn palvelimelle ja jää odottamaan vastausta. Vastauksen tullessa ajetaan metodi onSuccess, joka käsittelee onnistuneen kyselyn palauttaman vastauksen.
package hello.client;
import java.util.Iterator; import java.util.List;
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel;
public class Hello implements EntryPoint {
private final MessageFillerAsync messagefiller = GWT.create(MessageFiller.class);
public void onModuleLoad() {
AsyncCallback<List<Message>> callback = new AsyncCallback<List<Message>>() {
@Override public void onFailure(Throwable caught) {}
@Override public void onSuccess(List<Message> messages) { for (Iterator iterator = messages.iterator(); iterator.hasNext();) { Message message = (Message) iterator.next(); Label label = new Label(message.getTitle()); RootPanel.get().add(label); } } }; messagefiller.getMessages("hello", callback); } } |
MessageFiller-rajapinta implementoi RemoteService-rajapinnan, joka on marker interface ja listaa käytettävissä olevat metodit. Käynnistävässä luokassa luodaan (GWT.create) luokkailmentymä MessageFilleristä, josta tehdään tyyppimuunnos asynkroniseen versioon MessageFillerAsync. Tämän on tarkoitus määrittää takaisinkutsu-metodi, jota kutsutaan kun kysely on käsitelty palvelimella.
package hello.client;
import java.util.List; import hello.client.Message; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.google.gwt.user.client.rpc.RemoteService;
/** * The client side stub for the RPC service. */ @RemoteServiceRelativePath("messagefiller") public interface MessageFiller extends RemoteService {
public List<Message> getMessages(String keyword);
} |
package hello.client;
import java.util.List; import com.google.gwt.user.client.rpc.AsyncCallback;
/** * The async counterpart of <code>MessageFiller</code>. */ public interface MessageFillerAsync {
public void getMessages(String keyword, AsyncCallback<List<Message>> callback);
} |
Eclipseen asennettu GWT-plugini avustaa kehitystyön yhteydessä huomioimaan validin RPC-kutsun mallin luomisessa ja huomauttaa kyllä, jos geneerisyydet, tietotyypit tai parametrit ovat väärin.
Jäljellä olevan palvelinpuolen koodin lisäksi on vielä tiedoston war/WEB-INF/web.xml tiedoston muokkaaminen siltä osin, että sinne lisätään kutsuttavan servletin tiedot. Kun näitä tarvittavia toimenpiteitä tekee ensimmäisiä kertoja, niiden muistaminen voi tuntua hankalalta, mutta jo piankin ne muodostuvat triviaaliksi toimenpiteeksi. Sovelluksen koon kasvaessa voi olla hyödyllistä niputtaa ja lajitella metodikutsuja sopivasti nimettyihin ryhmiin (luokkiin ja paketteihin), jotta sovelluksen rakenteen hahmottaminen omassa mielessään olisi helpompaa.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets --> <servlet> <servlet-name>MessageFillerServlet</servlet-name> <servlet-class>hello.server.MessageFillerServlet</servlet-class> </servlet>
<servlet-mapping> <servlet-name>MessageFillerServlet</servlet-name> <url-pattern>/hello/messagefiller</url-pattern> </servlet-mapping>
<!-- Default page to serve --> <welcome-file-list> <welcome-file>hello.html</welcome-file> </welcome-file-list>
</web-app> |
MessageFillerServlet-luokka sisältää MessageFiller-rajapinnassa määritellyn getMessages-metodin. Tässä tapauksessa se kääräisee List-tietorakenteeseen 3 kpl Message-luokan ilmentymiä, joihin on kirjattu mukaan lyhyt viesti.
package hello.server;
import java.util.ArrayList; import java.util.List;
import com.google.gwt.user.server.rpc.RemoteServiceServlet; import hello.client.MessageFiller; import hello.client.Message;
/** * The server side implementation of the RPC service. */ @SuppressWarnings("serial") public class MessageFillerServlet extends RemoteServiceServlet implements MessageFiller {
public List<Message> getMessages(String keyword) {
List<Message> messages = new ArrayList<Message>();
if (keyword.equals("hello")) {
for (int i = 0; i < 3; i++) { Message message = new Message(); message.setTitle("Hello message nro " + i); messages.add(message); }
} return messages; } } |
Message-luokka:
package hello.client; import java.io.Serializable;
public class Message implements Serializable {
private String title; private String content;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; } } |
On tiettyjä rajoitteita ja ehtoja sille, mitä voidaan välittää serialisoituna. Tietotyyppi on serialisoitavissa, jos jokin seuraavista ehdoista pätee:
- primitiivityyppi, kuten char, byte, short, int, long,
boolean, float tai double.
- Stringin tai Daten instanssi, tai primitiivityypin kääreluokka kuten Character,
Byte, Short, Integer, Long, Boolean, Float tai Double.
- serialisoivan rajapinnan implementoiva luokka
Muista ehdoista lisää tietoa osoitteesta:
http://code.google.com/webtoolkit/doc/1.6/DevGuideServerCommunication.html#DevGuideSerializableTypes
GWT RPC -etäkutsujen toiminnan ja käytettävyyden
ymmärtämiseksi on harjoituksen vuoksi kehitelty graafisen sovelluksen
prototyyppi, joka toteuttaa kolme melko tavanomaista tietokantakyselyä (select-
tai insert-lauseita), joiden palauttamaa tietoa transformoidaan sopiviksi
tietorakenteiksi ja palautetaan tuo tietorakenne sovelluksen asiakasosalle.

Harjoitussovelluksen kuvitellulla käyttäjällä on käytettävissään mm. seuraavanlaisia toimintoja (käyttötapaukset).

Vain yhden etäkutsun aiheuttavat toiminnot ovat sekvenssikaavioilla (esimerkeissä käytetyt sekvenssikaaviot RUP:n analyysivaiheesta) ajateltuina varsin samanlaisia:
Käyttötapaus: Sisäänkirjautuminen

Tässä tarvitaan kaksi etäkutsua, sisäänkirjautumisen tekemiseksi tietoturvallisemmaksi. Selainpuolella tässä käytetään sellaista netistä löytämääni SHA1-algoritmia, joka käyttää vain GWT:n emuloinnin hyväksymiä Java-metodeita. Selainpuolella käytetään Java-luokkaa MessageDigest SHA1-muotoiseen kryptaamiseen. Jälkimmäinen kutsu palauttaa oman Profile-luokan instanssin, joka toteuttaa IsSerializable-rajapinnan, jonka avulla GWT ymmärtää suorittaa automaattisen serialisoinnin siirrettävälle luokan instanssille. Vastaussanomana saadun Profile-olion sisältöä jatkokäsitellään selainpuolella.
Tässä toteutuu suunnittelumalli double hashing. Katkelma kirjasta Ajax Design Patterns (korostukset omat):
With double hashing, the server generates a one-time random seed. The browser then hashes twice: first, it hashes the password to yield what's hopefully stored on the database. But instead of sending that, the browser combines it with the one-time seed to form a new hash. This new hash is sent to the server. The server then pulls out the stored hash from the database and combines it with the original one-time seed to form a new hash, which must match the hash that was uploaded. This works because in both cases, the initial password has been passed through the same two hash functions. In the browser, the user's attempt is passed through a fixed hash function and the result is immediately passed to a new hash function with one-time seed. And in the server, the database already holds the result of hashing the real password using the fixed hash function.
Kyseinen Michael Mahemoffin Ajax-kirja näytti olevan
saatavilla osoitteessa:
http://www.scribd.com/doc/15490788/Ajax-Design-Patterns-by-OReilly-Media
SHA1-algoritmin Java-implementaatio saatavilla osoitteessa:
http://www.docjar.com/html/api/Freenet/crypt/SHA1.java.html
Otteita kutsusta 1, joka palauttaa natiivin HashMap-luokan
Palautettavan HashMapin alustus
HashMap<String, String> seedMap = new
HashMap<String, String>();
Tietokantakyselyn valmistelu
PreparedStatement query = connection.prepareStatement("INSERT
INTO seed (seed) VALUES (?)", Statement.RETURN_GENERATED_KEYS);
Tietokantakyselyn aukkokohtien täyttäminen
query.setString(1, seedMap.get("seed"));
Tietokantakyselyn suorittaminen
int resultIndicator = query.executeUpdate();
Tietokantakyselyn vastauksen talteen ottaminen
ResultSet rs = query.getGeneratedKeys();
Vastaussanoman muodostus
generatedId = rs.getString(1);
seedMap.put("seedid", generatedId);
Otteita kutsusta 2, joka palauttaa oman Profile-luokan, joka toteuttaa
IsSerializable-rajanpinnan
Kryptauksen alustus
md = MessageDigest.getInstance("SHA-1");
Vertailtavan hashin muodostus
String concatted = passwordHash.concat(dbSeed);
String uppercased = concatted.toUpperCase();
byte[] digest = md.digest(uppercased.getBytes());
String temp = digest.toString();
BigInteger bigInt = new BigInteger(1, digest);
String hash = bigInt.toString(16);
Tietokantakyselyn valmistelu
PreparedStatement query =
connection.prepareStatement("SELECT s.seed, ah.passwordhash, ah.forename,
ah.surname " + "FROM seed s, accountholder ah " + "WHERE
s.seedid = ? AND ah.loginname = ?");
Vastaussanoman muodostus
Profile profile = new Profile();
profile.setLoginname(accountHash.get("loginname"));
profile.setForename(firstName);
profile.setSurname(lastName);

Käyttötapaus: Klikkaa sanan selitettäväksi

Tässä hyödynnetään laajaa WordNet-sanalistaa, josta oli saatavilla sellaisenaan mm. MySQL-tietokannan tauluiksi siirrettävissä oleva nippu SQL-skriptejä. WordNet on nykyisin Princetonin yliopiston vaalima projekti, joka sisältää muutakin ja on tarkoitettu paljon muuhunkin kuin vain yksittäisten sanojen kuvailuun. Kuvaus yliopiston sivuilta:
WordNet® is a large lexical database of English, developed under the direction of George A. Miller. Nouns, verbs, adjectives and adverbs are grouped into sets of cognitive synonyms (synsets), each expressing a distinct concept. Synsets are interlinked by means of conceptual-semantic and lexical relations. The resulting network of meaningfully related words and concepts can be navigated with the browser. WordNet is also freely and publicly available for download. WordNet's structure makes it a useful tool for computational linguistics and natural language processing.
Vastaussanomana palautetaan String-tietotyyppinä HTML-standardin mukaisessa muodossa olevaa tekstisisältöä, joka sijoitetaan selaimen puolella paneelin sisällöksi sellaisenaan. Tietokannan palauttamista riveistä muodostetaan ensin DOM-puu, joka sitten transformoidaan XSLT-tyylitiedostolla HTML:lläksi.
WordNet-sivusto:
http://wordnet.princeton.edu
Wordnet SQL Builder:
http://wnsqlbuilder.sourceforge.net
Otteita kutsusta, joka palauttaa String-tietotyypissä HTML-koodia
Transformoijan alustus (oma luokka)
Morph morph = new Morph();
morph.BuildDoc("definitions");
Tietokantakysely ilman valmistelua
String queryString = "select w.lemma,
sy.definition " + "FROM words w " + "LEFT JOIN senses se ON
w.wordid = se.wordid " + "LEFT JOIN synsets sy ON se.synsetid =
sy.synsetid " + "where w.lemma = '" + word + "'";
Tietokantakyselyn suorittaminen ja vastauksen talteenotto
samassa
ResultSet rs = connection.createStatement().executeQuery(queryString);
Muodostetaan tietokannan palauttamista riveistä DOM-puu
morph.FillDocAttr(rs, "row");
Katkelma Morph-luokan
FillDocAttr-metodista
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
while (rs.next()) {
Element row = doc.createElement(rowname);
results.appendChild(row);
for (int i = 1; i <= colCount; i++) {
String columnName = rsmd.getColumnName(i);
Object value = rs.getObject(i);
Attr attr = doc.createAttribute(columnName);
attr.setValue(value.toString());
row.setAttributeNode(attr);
Vastauksen transformointi XSLT-tyylisivulla
morph.Transform("sample-definition.xsl", "html");
output = morph.getTransformOutput();
Katkelma Morph-luokan XSLT-transformoijasta
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
"yes");
transformer.setOutputProperty(OutputKeys.METHOD, method);
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
StringWriter sw = new StringWriter();
StreamResult sr = new StreamResult(sw);
transformer.transform(domSource, sr);
transformOutput = sw.toString();

Käyttötapaus: Haku hakusanalla

Tässä tapauksessa Grid-komponenttiin on liitetty joukko sisällön lataamisen ja hallitsemiseen erikoistuneita luokkia. Haettaessa hakusanalla, tullaan suorittaneeksi etäkutsu, joka tiedustelee tietokannalta, joska hakusanaa vastaavia kuvia löytyisi ja jos löytyy, palautetaan kuvista lista String-tietotyypissä, joka sisältää XML-muotoista dataa.
Otteita kutsusta, joka palauttaa String-tietotyypissä XML-koodia
Välipalvelin RPC:lle (ei tarvita kaikissa asynkronisissa
kutsuissa)
RpcProxy<String> proxy = new RpcProxy<String>() { @Override
protected void load(Object loadConfig, AsyncCallback<String> callback) {
searchfiller.getSomePhotos(searchstring.getValue(), callback);} };
Grid-komponentin lataimet
XmlLoadResultReader<ListLoadResult<ModelData>>
reader = new XmlLoadResultReader<ListLoadResult<ModelData>>(type);
final BaseListLoader<ListLoadResult<ModelData>> loader = new BaseListLoader<ListLoadResult<ModelData>>(proxy,
reader);
Grid-komponentin sisällön säilöjä
final ListStore<ModelData> store = new
ListStore<ModelData>(loader)
Grid-komponentti itse
Grid<ModelData> grid = new
Grid<ModelData>(store, cm);
Tietokantakyselyn valmistelu
PreparedStatement query =
connection.prepareStatement("SELECT id, title, ownerloginname FROM photo
WHERE FIND_IN_SET('"+ ? + "',photo.tags) > 0");

Tietokannan rakennetta ei sovelluksen (harjoitus 2) protyyppiä
varten ole kummoisemmin kasvatettu käyttäen käsiteanalyysiä ja ER-kaavioita,
vaan taulut on luotu ihan perinteistä mutua käyttäen. Seed- ja
session-tauluissa esiintyvät samana toistuvat merkkisarjat johtuvat siitä, että
arvot ovat ennalta määritetty. Todellisuudessa ne arvottaisiin satunnaisesti.
INSERT INTO `accountholder` (`loginname`, `passwordhash`, `forename`,
`surname`) VALUES
('hessu','D52D2540417AF7940F01837B9A706A4341E92557','Harald','Henriksson'),
('niilo','42C3AF9C9F3B340A51FA778E5E3285B10F06BF08','Niilo','Paasilinna'),
('pacce','D9E4C803CC8977FA6EE6783785778EEE7FBA4B32','Pacce','Niklansson');
COMMIT;
INSERT INTO
`eventype` (`eventtype`, `description`) VALUES
('ownerchange','Ownership of an image is changed.');
COMMIT;
INSERT INTO
`photo` (`id`, `title`, `tags`, `ownerloginname`, `price`, `inquiredby`) VALUES
(1,'Aatami
kylvyssä','adam,bath,water,bubble','hessu',8,'niilo'),
(2,'Maarianhaminassa','island,view,grass,rock,boat','pacce',16,''),
(3,'Huoltoasemalla','petrol,station,oil,tank,car','niilo',32,''),
(4,'Kuunvalo','moon,light,dim,night,dark','niilo',64,'hessu'),
(5,'Presidentinlinnan
eteinen','president,building,government','hessu',32,'niilo'),
(6,'Ninjakuoro','ninja,chorus,singin,song,soundwave,happy','hessu',8,''),
(7,'Unikkopelto','field,drug,leaf,plant','hessu',128,'');
COMMIT;
INSERT INTO
`photoediting` (`id`, `photoid`, `event`, `earliername`, `newtitle`,
`earliertags`, `newtags`, `date`) VALUES
(1,1,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(2,2,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(3,3,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(4,4,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(5,5,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(6,6,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18'),
(7,7,'upload',NULL,NULL,NULL,NULL,'2009-09-14 23:22:18');
COMMIT;
INSERT INTO
`photoevent` (`id`, `photoid`, `event`, `fromwhomloginname`, `towhomloginname`,
`date`) VALUES
(8,1,'cancelownerchange','pacce','hessu','2009-10-17 01:52:20'),
(9,2,'cancelownerchange','pacce','hessu','2009-10-17 01:52:20'),
(10,1,'ownerchange','pacce','hessu','2009-10-17 01:55:00'),
(11,2,'ownerchange','pacce','hessu','2009-10-17 01:55:00'),
(12,1,'cancelownerchange','pacce','hessu','2009-10-17 01:55:16'),
(13,2,'cancelownerchange','pacce','hessu','2009-10-17 01:55:16'),
(14,3,'approvephotoownerchange','pacce','niilo','2009-10-17 02:45:14'),
(15,7,'declinephotoownerchange','pacce','niilo','2009-10-17 02:45:53'),
(16,1,'photoinquire','pacce','hessu','2009-10-17 04:07:08'),
COMMIT;
INSERT INTO `seed`
(`seedid`, `seed`, `addingtime`) VALUES
(51,'4yrhrh54','2009-10-16
23:45:50'),
(52,'4yrhrh54','2009-10-17 00:42:33'),
(53,'4yrhrh54','2009-10-17 01:05:34'),
COMMIT;
INSERT INTO
`session` (`id`, `sessionid`, `loginname`, `addingtime`) VALUES
(67,'12345678901234567890123456789012','hessu','2009-10-16
23:45:50'),
(68,'12345678901234567890123456789012','hessu','2009-10-17
00:42:33'),
(69,'12345678901234567890123456789012','hessu','2009-10-17 01:05:34'),
COMMIT;
Yksittäiseen tietokantaan kytkeytymiseksi Java-sovellus
tarvitsee JDBC-ajurin (Java Database Connectivity API), joka osaa käsitellä
käytettävää tietokantajärjestelmää. API koostuu kahdesta rajapinnasta, joista
toinen on Java-sovelluksen ohjelmoijalle ja toinen ajurien kirjoittajille.
Sovellusrajapinta on käytetystä tietokannasta riippumaton, mutta tietokantoja
itseään varten tarvitaan erillinen ajuri, jonka tietokannan valmistaja on
tehnyt ja saataville julkaissut. Näitä JDBC-ajureita on tarjolla mm. relaatiotietokantoihin
Oracle Database, Sybase, IBM DB2, Microsoft SQL Server, MySQL ja PostGreSQL).
Sovellusrajapinnan tietokantariippumattomuus tarkoittaa sitä, että samaa
ohjelmointikoodia voi käyttää esim. niin Oracle- kuin MySQL-tietokannankin
kohdalla.

Ajurin lataaminen:
String driver =
"com.mysql.jdbc.Driver"; |
Yhteysosoitteen ja tunnusten määritys:
String url =
"jdbc:mysql://localhost:3306/"; |
Yhteyden muodostus:
Connection conn = DriverManager.getConnection(url + db, user, pass); |
Luodaan Statement olio SQL-lauseiden suorittamiseksi:
PreparedStatement query = connection.prepareStatement("SELECT
s.seed, ah.passwordhash, ah.forename, ah.surname " + "FROM seed s,
accountholder ah " + "WHERE s.seedid = ? AND ah.loginname =
?"); |
Suoritetaan SQL-lause:
ResultSet result; |
Käydään tulosjoukko läpi:
while (result.next()) { |
Suljetaan ResultSet, Statament ja Connection:
query.close(); |
Yhteysosoite on aina muotoa protokolla:aliprotokolla:tietolähde. Protokollana on aina JDBC, mutta aliprotokolla on ajurikohtainen (esimerkiksi mysql tai odbc).
Yhden JDBC-yhteyden aikana voidaan suorittaa useita SQL-käskyjä tai -päivityksiä, jotka ajuri välittää tietolähteelle. Jokainen SQL-lause itsessään on transaktio eli tapahtumakokonaisuus, jonka voi suorituksen jälkeen peruuttaa, mutta transaktio voi koostua useammastakin SQL-lauseesta.
Yhteyden avaaminen aloittaa automaattisesti uuden transaktion, joka saatetaan päättymään itsestään jokaisen yksittäisen SQL-lauseen ajamisen jälkeen. Käytännössä Connection-olion commit-metodi ajetaan tuolloin automaattisesti, mikä tarkoittaa myös sitä, että kyseistä transaktiota ei voi enää peruuttaa vain tieto on kirjoitettu pysyvästi tietokantaan. Metodilla setAutoCommit automaattisen transaktion päättymisen voi kytkeä pois päältä. Tällöin transaktion rajoissa voi suorittaa yhden tai useampia SQL-lauseita, jotka voi peruuttaa rollback-metodilla. Suorituksen lopuksi ajetaan commit-metodi ja kytketään setAutoCommit takaisin päälle.
PreparedStatement on Statement-olion alaluokka. Olennaisena erottavana piirteenä on se, että esivalmisteltu kysely lähetetään tietokannalle heti kun se on luotu, jossa se esivalmistellaan jo ennen kuin varsinaista SQL-lauseen suorittavaa käskyä on ajettu. Jos samaa SQL-lausetta tarvitaan myöhemmin uudestaan, sitä on nopeampi käyttää, mutta tämä on vain yhteyskohtaista.
Yhteyden voi luoda myös DataSource-rajapintaa käyttäen. Esimerkissä yhteys luotiin DriverManager-rajapinnan avulla. DataSource tarjoaa paremman reitin tietolähteeseen kytkeytymiseen ja tunnistautumiselle, tehden ohjelmointikoodista vielä hieman helpommin alustasta tai järjestelmästä toiseen siirrettävän. Se mahdollistaa myös tietokantayhteysaltaan käytön ja hajautetut transaktiot. Tietokantayhteyden avaaminen on aina hidas operaatio, ja turhia yhteyden avaamisia tulisi välttää. Sovelluksen tarvitessa tietokantayhteyttä se ”lainataan” altaasta.
JDBC:n tietokantakohtaiset ajurit luokitellaan neljään eri luokkaan, joista kolmas ja neljäs ovat puhdasta Javaa. Tyypin neljä ajuri kääntää JDBC-kutsut suoraan tietokantajärjestelmän käyttämän verkkoprotokollan ymmärtämään muotoon. Tyypin kolme ajurit kääntävät kutsut väliohjelmiston valmistajan protokollalle, joka sitten vuorostaan kääntää kutsut sille tietokannalle, joka on kytkettynä väliohjelmiston palvelimeen. Jälkimmäinen mahdollistaa sen, että samalla yhteydellä voi olla yhteydessä useampaan erityyppiseen tietokantaan yhtä aikaa. Alla pieni otos saataville olevista JDBC-ajureista:
PostgreSQL JDBC Driver
http://jdbc.postgresql.org/
Sybase – jConnect for JDBC
http://www.sybase.com/products/allproductsa-z/softwaredeveloperkit/jconnect
Oracle – JDBC driver ja Universal
Connection Pool (UCP)
http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html
MySQL – Connector-J
http://dev.mysql.com/downloads/connector/j/
SQL Server – JDBC Driver 2.0 Documentation
http://msdn.microsoft.com/en-us/library/ee229547%28SQL.10%29.aspx
Ajax = asynkroninen
tiedonsiirtotekniikka, jolla websovelluksista voi tehdä vuorovaikutteisempia
ajuri = tarjoaa ohjelmistokehittäjälle määritellyn rajapinnan, jonka kautta jonkin laitteen toimintaa voi ohjata
analyysivaihe = eräs RUP-ohjelmistosuunnittelumallin vaiheista
asiakas = vastinpari palvelimelle, voi olla esim. työpöytäsovellus, websovellus tai yksittäinen websivu
asynkroninen viestintä = odotellessa vastausta viestiin muu sovelluksen toiminta jatkuu
DOM-puu = viittaa websivun/-sovelluksen sisäiseen rakenteeseen
Eclipse = eräs ohjelmistokehitysympäristö
emulointi = jonkin toiminnan jäljittely
Entry-point = viittaus Google Web Toolkittiä käyttävän sovelluksen aloitusluokkaan, joka suoritetaan käynnistettäessä
etäkutsu = sovellus kutsuu sellaista metodia, joka ei ole saatavilla paikallisesti, kutsutaan verkon yli/kautta
Ext GWT = Java-kirjasto, joka tarvitsee osakseen ja laajentaa Google Web Toolkittiä.
geneerisyys = tietorakenteiden ja säilöjen tyyppimääritys
GWT = Google Web Toolkit, ohjelmistokehys Ajax-sovellusten kehittämiseen
HTML = HyperText Markup Language, sivunkuvauskieli, josta erityisesti websivut rakentuvat
instanssi = luokan ilmentymä
J2EE = ohjelmistokehitysalusta ja spesifikaatio Java-sovellusten/-sovelluspalvelimien kehittämiseen
Java = eräs ohjelmointikieli
Javascript = eräs webselaimissa käytettävä ohjelmointikieli
JDBC = tietokantariippumattomien sovellusten tekemisen mahdollistava ohjelmointirajapinta
kirjasto = koostuu hierarkisesti lajitelluista luokista, järjesteltyinä paketteihin
konfiguraatiotiedosto = sisältää sovelluksen käynnistymiseen ja toimintaan vaikuttavia säätöjä
kryptaus = tiedon muuntaminen salattuun muotoon
käyttötapaus = yksittäinen UML-ohjelmistonmallinnuskielen mukainen selite käyttäjän toimintamahdollisuudella
käyttötapauskaavio = sisältää kaikki käyttäjälle mahdolliset toiminnot
kääntäjä = tuottaa lähdekoodin pohjalta ajettavan kokonaisuuden
kääreluokka = olion luokka, jota käytetään luokattoman primitiivityypin sisällyttämiseen
luokka = metodeista ja attribuuteista koostuva olion määritelmä
market-interface = käytetään liittämään metadataa luokkiin sellaisessa kielessä, joka ei tälläistä annotointia tue
metodi = luokan/olion sisältämä toiminto
olio = luokan ilmentymä
optimointi = jonkin toiminnan tehostaminen
paketti = nimetty osa hierarkiaa, joka sisältää joukon alipaketteja ja/tai luokkia
palvelin = vastinpari asiakkaalle, tarkoittaen tässä yhteydessä sovelluksen fyysisen palvelimen puoleista osaa
primitiivityyppi = tietotyypin alkeistyyppi
protokolla = yhteyskäytäntö ja standardi, joka määrittelee osapuolten väliset yhteydet
prototyyppi = sovelluksen nopeahkosti luotu versio, jota voi käyttää tulevien aikomusten hahmottamisen apuna
rajapinta = joukko metodeita, jotka niitä käyttävä luokka on toteuttanut rajapintaluokan nimeämästi
RIA = Rich Internet Applications, websovellus, jolla on samantapaisia piirteitä kuin työpöytäsovelluksilla
RPC = Remote Procedure Call, yleisnimike etäkutsujen toteuttamiselle
RUP = Rational Unified Process, iteratiivinen ohjelmistokehitysmalli
sekvenssikaavio = UML-kaavio, joka kuvaa olioiden välistä toimintaa
serialisointi = olion siirtäminen verkon yli muuntamalla olio yhteysprotokollan kelpuuttamaan muotoon
SHA1-algoritmi = kryptausmetodi, jolla tavataan suojata esim. salasanoja
SQL = Structured Query Language, standardoitu kyselykieli, jolla relaatiotietokantaa käskytetään
tietorakenne = Java-kielessä tietotyyppi, joka sisältää tietyn rakenteista tietoa
tietotyyppi = primitiivityyppi tai määritellyn tyyppistä tietoa kuvaava luokka
transaktio = joukko tapahtumia, jotka suoritetaan perätysten
transformointi = tietojoukon muuntaminen jonkin mallin mukaan toisenlaiseksi
websovellus = itsenäinen sovellus tai palvelu, joka käynnistyy tiettyyn webosoitteen mentäessä
XML = eXtensible Markup Language, merkintäkieli ja standardi, jolla tietomassoja voi jäsennellä ja kuvailla
XMLHttpRequest = DOMin rajapinta, jonka kautta websovellus keskustelee palvelimen kanssa
XSLT = Extensible Stylesheet Language Transformations, kieli XML-dokumenttien transformoimiseen