Bruk av objektorientert programmering får et ekstra "puff" når vi klarer å lagre objektene på disk eller overføre dem i et nettverk. Men dette er ikke enkelt, og vi trenger hjelp av språksystemet for å få det til.
Trykket i Nettverk & Kommunikasjon nr.01/2000
(c) Anders Fongen 2000
Anders Fongen er høgskolelektor og fagsjef ved Den Polytekniske Høgskolen. Web-stedet hans er på www.fongen.no
Objektorientert programmering innebærer at vi strukturerer dataene våre. Vi kan lage objekter som lar dataelementer høre sammen, f.eks. en feilkode sammen med en tekstbeskrivelse. Et telefonnummer kan høre sammen med navn, adresse og andre objekter av alle slag, f.eks. andre telefonnummer i samme gate.
I et objektorientert programmeringspråk er det sentralt at et objekt kan bli brukt av flere. Det oppnår vi ved at de som deler på bruken av et felles objekt ikke har sin egen private kopi inne sine egne private objekter, men kun en henvisning eller peker til dette felles objektet. Disse pekerne kan danne et ganske komplekst nettverk av objekter. Det programtekniske begrepet for et slikt nettverk er en graf, og teorien rundt bruken av grafer er av stor betydning når vi vil lage metoder for å overføre objekter til og fra harddisk eller datanett.
Så hvordan overfører vi et "Familie"-objekt til en fil på en slik måte at det kan leses inn igjen med strukturen intakt?
Alle dem som tenker "vi følger alle henvisningene til alle objektene og skriver dataene som ligger inne i dem" rekker en hånd i været. Godt tenkt, men vi må huske på to ting: Vi må sikre at hvert objekt bare skrives en gang, og vi må passe på å skrive dem med en slik syntaks at det blir mulig å skille dataelementene og gjenkjenne datastrukturen under innlesing igjen. Dere kan ta hendene ned igjen nå.
De som gjerne vil vite hvordan vi kan programmere med såkalte serialiserte objekter uten å interessere seg for de programtekniske detaljene kan hoppe over neste avsnitt, dere andre kan lese videre:
Et objekt er representert som en graf, og gjennom en algoritme for å gjennomløpe den grafen slik at hvert element (node) blir besøkt kun én gang kan vi skrive både dataene og henvisningene til andre objekter i serialisert form. Henvisningene kan ikke skrives med sin pekerverdi direkte, men må erstattes med en verdi som henviser til objektet der det ligger i serialisert form (i datastrømmen), ikke til objektet i strukturert form (i internminnet). Hvert objekt må også skrives ut med et "hode" som inneholder klassenavn og et "versjonsnummer". Versjonsnummeret skal sikre at klassekoden som behandlet objektet før det ble serialisert, er den samme som behandler objektet etter at det blir "strukturert" igjen.
Men serialisering kan også brukes til å oppnå "permanente objekter" (persistent objects), ved at et serialisert objekt kan legges i en diskfil eller en database, og dermed gjøres permanent tilgjengelig også etter at programmet er avsluttet og maskinen slått av.
Det vi skal se litt nærmere på, er et eksempel på bruk av serialiserte objekter som protokollenheter mellom en databasetjener og -klient. Vi ønsket å la en enkel databasetjener motta SQL-spørresetninger og returnere det resulterende radsettet som et serialiserte objekt, og på den måten unngå mye programmeringsarbeid med koding og analysering av byte-strenger.
Om vi f.eks. ønsker å sende et serialisert Java-objekt over en TCP-forbindelse (port 4567), bruker vi denne koden:
(import java.io.*;)
(import java.net.*;)
Socket s0 = new Socket("myserver.fongen.no",4567);
ObjectOutputStream oos =
new ObjectOutputStream(s0.getOutputStream());
oos.writeObject(thisIsMyObjectOfAnyClass);
s0.close();
Dette programmet forutsetter selvfølgelig at det er noen i andre enden av denne TCP-forbindelsen som har gjort en tilsvarende leseoperasjon. Dessuten kan det oppstå en del feilsituasjoner underveis, så det er nødvendig å sette opp en try/catch-blokk rundt koden for å takle feilsituasjoner.
Det er dette ResultSet-objektet som inneholder både selve dataene i radsettet, og opplysninger om kolonnebredde, datatyper m.m. (metadata). Og det er dette objektet som vi ønsker å serialisere for å sende tilbake til klienten.
Uheldigvis kan ikke alle Java-objekter serialiseres, og det gjelder bl.a. objekter av klassen ResultSet. Objekter kan bare serialiseres dersom visse vilkår er oppfylt, og grunnen til at ResultSet ikke kan serialiseres er at det inneholder referanser til ressurser utenfor Java-systemet, nemlig databaseforbindelsen.
Så for å få returnert et resultatsett måtte vi lage en egen klasse som inneholder de opplysningene i resultatsettet som vi trenger, dvs. selve dataene og noe av metadatene. Vi kaller denne klassen for SerialResultSet. Den er i hovedsak en lagringsklasse, men som i tillegg til radsettet også kan inneholde opplysninger om oppståtte feilsituasjoner.
Nesten like enkelt er det å sette opp tjenersiden av en slik forbindelse. Java har en innebygget klasse som heter ServerSocket, og som har det som trengs. Se bare på denne programkoden:
Accept-metoden returnerer ikke før en oppkopling er mottatt, og returnerer et Socket-objekt som representerer den aktive forbindelsen. Vi kan lese og skrive data gjennom en InputStream/OutputStream som vi får ut av dette objektet.ServerSocket s = new ServerSocket(4567); Socket incoming = s.accept(); ObjectInputStream ois = new ObjectInputStream(incoming.getInputStream()); String sqlComm = (String)ois.readObject(); incoming.close();
En ting som bør nevnes når man vil bruke nettverket som bærer av en to-veis "objekt"-strøm: Konstruktøren til ObjectOutputStream vil sende noe data til ut-strømmen, tilsvarende vil konstruktøren til ObjectInputStream lese noe data (en header) fra inn-strømmen. Om en klient først lager en ObjectInputStream og deretter en ObjectOutputStream, må tjeneren gjøre det i motsatt rekkefølge. Ellers vil begge vente på hverandre i det uendelige (deadlock).
Men når man lager et program i flere tråder, er det viktig å forvisse seg om at man ikke lager feilsituasjoner fordi den underliggende databasen (eller andre slags systemtjenester) ikke tåler forespørsler som "overlappes" i tid. Om det f.eks. er slik at databasen benytter seg av en "global" buffer til å lagre SQL-setninger, kan det skje at vi med forespørsel nr.2 overskriver bufferet med nytt innhold før forespørsel nr.1 er ferdig utført. Slike tilstander kan vi selvfølgelig ikke leve med, så det er viktig at databasen er såkalt "Threadsafe". Threadsafe innebærer at den er programmert fri for ubeskyttede felles ressurser, slik at flere utføringstråder kan kalle på operasjoner i databasen samtidig. Er f.eks. MS Access Threadsafe? Vi vet ikke, men finner det naturlig at et program som kan kjøres med flere klienter på samme maskin har den nødvendige beskyttelsen for å betjene klienter i et nettverk. Vi har dessuten gjort noen enkle tester på samtidige operasjoner uten å ha sett noen feilsituasjoner. InstantDB sier uttrykkelig at den er Threadsafe.