Relationale Datenbanken für Objekt-orientiert-Geschädigte

  • Beitrags-Autor:
  • Beitrags-Kategorie:Software & Co.
  • Lesedauer:8 min Lesezeit

Es gibt auf dieser Welt Entwickler, die von Oben nach Unten, von A nach Z oder von Pontius zum Pilatus programmieren. Man reiht die abzuarbeitenden Aufgaben als Code-Schnipsel hintereinander ein…

Dann gibt es noch die anderen Entwickler, die mit oder ähnlichen Sprachen groß geworden sind. Ohne „class“, „package“ und „import“ läuft da gar nichts… Sie bilden Ihre Welt mit Hilfe von Klassen ab und „verwirklichen“ sie, indem sie davon Objekte ableiten. Dabei kann ein Objekt andere Objekte beinhalten.

Im Vergleich zu einem solchen Denksystem ist ein Haufen von Tabellen mit Spalten etc flach wie die Welt von Lactantius (seine Philosophie teilte die katholische noch nie – auch wenn die „Halbgebildeten“ das immer behaupten). Wie soll man da Abhilfe schaffen? Mit einem SQLObject!

SQLObject ist ein sog. objektrelationale Wrapper. Das bedeutet: man leitet von dieser Klasse eigene Klassen ab – beschreibt dabei nicht nur die Eigenschaften der Klasse selbst, sondern auch ihre Zusammenhänge untereinander: die Relationen. SQLObject ist für geschrieben und ermöglich die Verwendung mehrerer relationalen Datenbanken, ohne SQL-Kenntnisse. SQLObject ist der integrale Bestandteil von Turbogears, dem Framework für – wie es schön heißt – rapid development. (Insgesamt ist Turbogears mit seinem Prinzip „Front to back development“ sehr interessant – die genaue Umkehrung des üblichen Vorgehens in der Entwicklung: ich weiß, was rauskommen soll und fange damit an!)

Die heilige Kuh – die Import-Anweisung lässt sich nicht umgehen, deshalb:

from sqlobject import *

damit python und SQLObject wissen, was die Datenbank ist (an der gearbeitet wird), muss sie angegeben werden. Dies geschieht mit einem String wie diesem:

scheme://[user[:password]@]host[:port]/database[?parameters]

Es erklärt sich fast von selbst. Als schema wird eine der möglichen Datenbanktypen bezeichnet: sqlite, , postgres, firebird, interbase, maxdb, sapdb, mssql, sybase. Ganz schön viele oder?

connection = connectionForURI(connection_string)
sqlhub.processConnection = connection

stellt die Verbindung her. Jetzt können wir anfangen!

SQLObject stellt und einige Datentypen zur Verfügung, die er intern auch auf die richtige Spaltentypen für die verwendete Datenbank (schema) abbildet. Möglich sind:

  • StringCol
  • UnicodeCol (wenn man nicht nur latainisch / englisch schreiben will)
  • IntCol (für Ganzzahlen)
  • FloatCol (für Zahlen mit Komma)
  • EnumCol (für Aufzählungen – eine begrenzte Auswahl)
  • DateCol (Datum = ein datetime-Objekt)
  • TimeCol (Uhrzeit = ein datetime-Objekt)
  • DateTimeCol (für Datum und Zeit = ein datetime-Objekt)
  • BoolCol (wahr oder nicht?)

Andere sind an ein bestimmtes Schema gebunden (wie BLOBCol bei MySQL, PostgreSQL und SQLite oder CurrencyCol bzw. DecimalCol für Preise – 10-Stellig mit 2 Stellen hinter'm Komma) und sind hier irrelevant. Interessant sind vielmehr:

  • ForeignKey
  • SingleJoin
  • MultipleJoin
  • RelatedJoin

Die Grundtypen können wir weiter begrenzen indem wir in den Klammern z. B. die Länge begrenzen:

   name = UnicodeCol(length=100)

Wenn wir unsere Autos beschreiben wollten, müssten wir schon mal in Klassen denken, um SQL-Objekte zu bekommen. Deshalb fangen wir an.

class (SQLObject):
    marke = StringCol()
    typ = StringCol(length=50, alternateID=True)
    werke = MultipleJoin('Werk')
    farben = RelatedJoin('Farbe')
    sprit = RelatedJoin('Sprit')
    logo = ForeignKey('Logo')

Was ist dazu zu sagen? Nun: Ein Auto-Typ hat immer eine Bezeichnung, kann aber in mehreren Werken produziert werden (mehr als ein Werk möglich: MultipleJoin). Trotzdem besitzt es immer das EINE gleiche Logo (aber das Logo wird auf mehreren Typen desselben Herselelers zu sehen sein) – deshalb verlangt der Autotyp nach einem schon existierendem Logo. Farbe und Sprit sind ebenso wie Werk oder Logo ganz eigene Klassen, nur das ihre Objekte (rot, grün, Diesel, Benzin) von mehreren Autotypen verwendet werden – aber auch die Autotypen können potenziell mehr als eine Farbe oder Spritart eigen haben. Eine solche Relation (m-zu-n = auf beiden Seiten die Zahl kann höher als 1 sein) wird durch RelatedJoin (auf beiden Seiten) realisiert: eine Farbe kann mehreren Objekten des Typs Auto zugeordnet werden, so wie Auto mehreren Objekten des Typs Farbe zugeordnet werden kann:

class Farbe(SQLObject):
    name = UnicodeCol()
    autos = RelatedJoin('Auto')

Man gibt in Klammern (und Anführungszeichen) den Namen der anderen Klasse an. Das ist die ganze Magie! (Genauso sieht auch die Sprit-Klasse aus)

class Logo(SQLObject):
    beschreibung = UnicodeCol()
    autos = MultipleJoin('Auto')

Und schon ist eine 1-zu-m-Relation realisiert!

Wenn man der Einfachheit halber annimmt, dass ein Werk nur einen Typ produziert, stimmt unser Entwurf, der um „Werk“ vervollständigt wird.

class Werk(SQLObject):
    ort = UnicodeCol()
    plz = IntCol()
    erbaut = DateCol()
    auto = ForeignKey('Auto')

Eines ist uns entgangen: „Typ“ ist so eindeutig, dass sie fast als ID dienen könnte. Doch das haben wir schon berücksichtigt: alternateID=True besagt, dass es nicht nur einfach ‚unique‘ ist. Nur deshalb können wir ein Objekt abrufen indem wir sagen: Auto.byTyp(‚Golf‘)…

Apropos: wie kriegt man die denn die Objekte aus der Datenbank wieder heraus?

Welche Objekte? Wir haben die Tabellen noch gar nicht erstellt! Das erledigen wir schnell. Zuerst die Klassen, von denen die anderen Abhängen:

Farbe.createTable()
Sprit.createTable()
Werk.createTable()
Logo.createTable()
Auto.createTable()

Fertig!

Jetzt die Befüllung mit Werten:

l1 = Logo(beschreibung="Vier Sschrägstriche")
a1 = Auto(marke="Fiat", typ="500", logo=l1)
w1 = Werk(ort="Rom", plz=1111, erbaut=DateTimeCol.now())
a1.werk=w1
f1 = Farbe(name="rot")
l2=Logo(beschreibung="Vier Kreise")
a2 = Auto(marke="Audi", typ="A4", logo=l2)
# relationen: Rot können Audi's und Fiat's sein, grau nur Audi's
f1.addAuto(a1)
f1.addAuto(a1)
f2 = Farbe(name="grau")
a2.addFarbe(f2)
# Sorry, rote Audi's gibt es nicht!
a2.removeFarbe(f1)

Das ist neu! Man kann kein Auto-Objekt ohne Logo erzeugen. Dafür gibt es von Anfang an addX und removeX (wobei X eine MultipleJoin-Relation ist d.h. ein Auto kann mehrere Farben haben oder mehrere Sprit-Arten Fahren). Und man muss nicht ausdrücklich speichern. Das geschieht automatisch! Will man etwas loswerden, sagt man a'dél zum Objekt: „del a2“.

Jetzt fassen wir einige Zeilen zusammen und machen Fiat in einem Schritt in grau und rot verfügbar:

a2.farbe=(a1,a2)

Einfach oder? Noch einfacher kann man etwas Abrufen:

myAuto = Auto.get(1)

Ist das nicht toll?! Ich habe in „myAuto“ einen Fiat 500 reingeholt. Wenn ich nicht wüsste, welche ID so ein Fiat 500 hat könnte ich alternativ sagen:

myAuto=Auto.byTyp("500")

weil „typ = StringCol(length=50, alternateID=True)“ eine alternative ID darstellt: einen Golf gibt es nur von VW!

Um die Datenbank nicht zu stark zu belasten, wenn ein Objekt mit vielen Abhängigkeiten herausgeholt wird, haben sich die schlauen Jungs die sog. „lazy updates“ einfallen lassen. Ein Objekt des Typs Farbe wird passend zum Auto erst dann aus der Datenbank gelesen, wenn es auch wirklich benötigt wird. Will man andere Kriterien nutzen, setzt man select() ein:

fiats=Auto.select(Auto.q.marke="Fiat")
audisRot=Auto.select(AND(Auto.q.marke="Audi", Auto.q.farbe=f2))

So bekommt man alle Fiats bzw. alle Audi-Typen, die grau (entspricht dem Objekt f2) lackiert werden. Ist das nicht klasse? Es geht aber noch besser. Mit:

Auto.select(Auto.q.marke="Fiat",
   extraColumns = [func.COUNT(Auto.q.id)],
   groupBy = Auto.q.farbe,
   orderBy = [DESC(func.COUNT(Auto.q.werk)),Auto.q.Sprit])

Damit bekommen wir alle Fiats, gruppiert nach dem selben Angebot an Farben (rot und grau), geordnet absteigend nach (1.) Anzahl der produzierenden Werke und (2.) Sprit-Typen („Diesel“ kommt hier natürlich vor „Benzin“!).

Fazit

Mit SQLObject kriegt man schnell und einfach Objekte aus der DB heraus (oder dort hinein), die man sofort woanders (in -Templates?) verwenden kann. Es mag hier und da langsamer sein als pures SQL. Doch so Backend-unabhängig und genial einfach war das Leben in einer Datenbanken-Welt noch nie!

Es ist egal, welchen Schaden man im Laufe des Lebens abbekommen hat (wie z. B. hier die Objekt-Orientierung) oder ob man die relationale Welt verstehen kann oder nicht. Weiterbildung und offene Augen dafür, was andere bereits geleistet haben, erspart viel und seltsame bzw. veraltete Lösungen.

Wenn Sie noch Fragen haben, dann scheiben Sie sie einfach in das Kommentarfeld rein. „Wer anklopft, dem wird aufgetan!“