Sonntag, 12. Februar 2012

entwickler.com Magazine Konferenzen Entwickler Akademie Entwickler-Forum Jobbörse Bücher
Software & Support Verlag




April 2006
aus Linux Enterprise Ausgabe: 3.2005
Objekt Relational Mapping mit Python
SQLObject, ein Objekt-Relational-Mapper in Python
von Markus Jais

Python bietet Module für den Zugriff auf nahezu alle wichtigen Datenbanksysteme. Diese implementieren meist die Python-DB-API-Spezifikation [1]. Die entsprechenden Module finden Sie unter [2]. Seit einiger Zeit sind so genannte Objekt Relationale Mapper (ORM) in Mode gekommen, die es erlauben, ohne Kenntnisse von SQL mit Hilfe von Objekten auf die jeweilige Datenbank zuzugreifen. Natürlich gibt es auch für Python solche ORMs, wie z.B. das hier näher besprochene SQLObject. Meist ist die Benutzung relativ einfach und erfordert keine allzu lange Einarbeitungszeit. Hat man erst einmal ein wenig damit gearbeitet findet man schnell gefallen an ORMs. Ich hoffe, ich kann Sie mit diesem Artikel auch auf den Geschmack bringen.


SQLObject
Der Vorteil eines OR Mappers besteht darin, dass man für den Zugriff auf relationale Datenbanken kein SQL mehr schreiben muss sondern dass man Objekte benutzt um auf die Daten in der Datenbank zuzugreifen. Oft repräsentiert dabei eine Klasse eine Tabelle. Man kann also weiterhin relationale Datenbanken verwenden und muss so nicht auf ihre Vorteile (Schnelligkeit, Robustheit, vorhandenes Know-how usw.) verzichten, kann aber trotzdem objektorientiert entwickeln. Oft wird das Mapping der Tabellen auf Objekte in Konfigurationsdateien (meist im XML-Format) spezifiziert. Das hier vorgestellte Python-Modul SQLObject verzichtet auf Konfigurationsdateien. Stattdessen schreibt man gleich die Python-Objekte. Das Einzige was man vorher machen muss ist eine Datenbank anzulegen. In unserem Fall verwenden wir das Datenbank-Management-System MySQL [3], da dies weit verbreitet und einfach zu benutzen ist. Zusätzlich müssen Sie noch SQLObjekt installieren, zu finden unter[4]. Ich habe für diesen Artikel die Version 0.6 verwendet. Es ist durchaus möglich, dass es, wenn Sie diesen Artikel lesen, bereits eine neuere Version gibt. Eventuell funktioniert dann auch nicht mehr alles wie hier beschrieben. Allzu viel dürfte sich aber nicht ändern. Bei Problemen finden Sie auf den Mailinglisten zu SQLObject Hilfe. Die Installation ist denkbar einfach. Nach dem Entpacken von SQLObject rufen Sie einfach folgenden Befehl auf (ggf. als root): $ python setup.py install
Bedenken Sie, dass Sie für den Zugriff auf MySQL auch noch MySQL-python brauchen, welches Sie unter [5] finden. Neben MySQL werden auch PostgreSQL, Firebird und noch einige weitere Systeme unterstützt. Und Unterstützung für weitere Datenbanksysteme ist geplant. Wenn Ihr Lieblingssystem noch nicht dabei ist, ist es gut möglich, dass sich das in naher Zukunft ändern wird.

Beispieldatenbank
Unsere Beispieldatenbank nennen wir books. Um die nachfolgenden Beispiele nachvollziehen zu können, legen Sie bitte die Datenbank auf Ihrem Rechner an. Sie müssen sich dazu als root-User bei MySQL einloggen (nicht zu verwechseln mit dem Linux-root-User). Auch jeder andere MySQL-User mit den entsprechenden Privilegien ist okay. Nachfolgend die genauen Befehle für die Einrichtung der Datenbank.

create Database books;
GRANT all on books.* to bookuser@localhost IDENTIFIED BY 'bookpasswd';


Alles Weitere geht dann direkt über SQLObject. Bitte bedenken Sie auch, dass ein GRANT all nicht gerade einen Preis für Sicherheit gewinnt. Aber für unser Beispiel ist das vollkommen okay. Für weitere Informationen bezüglich GRANT konsultieren Sie bitte die MySQL-Dokumentation. Unsere Datenbank besteht aus zwei einfachen Tabellen, author und book. Ein Author kann dabei mehrere Bücher geschrieben haben. Für den Autor speichern wir Vor- und Nachnamen und für die Bücher den Titel und den Autor. Als Erstes wollen wir mal nur die Tabelle author erzeugen, ohne uns Gedanken über die Bücher zu machen. Wie man das mit SQLObject anstellt, sehen Sie in Listing 1.

Listing 1

#!/usr/bin/env python

from sqlobject import *

mysql_connection = connectionForURI('mysql://bookuser:bookpasswd@localhost/books')

class Author(SQLObject):

_connection = mysql_connection
#_connection.debug = 1
firstName = StringCol(length=30)
lastName = StringCol(length=30)

Author.createTable(ifNotExists=True)

author1 = Author(firstName="J.R.R.", lastName="Tolkien")
author2 = Author(firstName="Axes", lastName="Martelli")


author2.firstName = "Alex"


Das Wichtigste an diesem Beispiel ist die Klasse Author. Diese muss von SQLObject erben. Jede Klasse muss eine Variable _connection besitzen. Alternativ können Sie auch auf Model-Ebene die Variable _connection_ definieren welche verwendet wird, falls _connection nicht vorhanden ist. Die Klasse bekommt die Attribute firstName und lastName. welche die Namen der Spalten in der Tabelle wiedergeben. (Genaugenommen wird aus firstName der Spaltenname first_name und als lastName wird last_name). Die Attribute sind Strings, darum verwenden wir hier StringCol(). Dadurch wird die Spalte mit dem Spaltentyp VARCHAR angelegt. Mit dem Attribut length geben wir die Länge an, also hier bei firstName VARCHAR(30). Geben Sie keine Länge an, wird als Spaltentyp TEXT verwendet. Wie man andere Typen anlegt, findet Sie in der SQLObject-Dokumentation. Die Verbindung zu MySQL erzeugen wir mit connectionForURI, der wir in einem speziellen Format die Parameter für die Verbindung übergeben. Wie das für andere Datenbanksysteme wie z.B. PostgreSQL funktioniert, entnehmen Sie bitte der SQLObject-Dokumentation. Mit der Methode createTable lässt sich dann ganz einfach die Tabelle erzeugen. Es ist eine gute Idee, immer ifNotExists=True zu übergeben, denn wenn die Tabelle schon besteht, braucht sie ja nicht noch einmal angelegt zu werden. Wenn Sie nun einen Datensatz in die Tabelle einfügen möchten, dann können Sie das dadurch erreichen, dass Sie ein Objekt der Klasse Author erzeugen und dem Konstruktor Vor- und Nachnamen übergeben. In unserem Beispiel habe ich bewusst den Vornamen des zweiten Autors falsch geschrieben, um Ihnen zu zeigen, wie einfach es ist, diesen nachträglich zu ändern. Wenn Sie mal genauer wissen wollen, was SQLObject eigentlich macht, dann können Sie das debug-Attribut von _connection auf 1 setzen. Dann wird unter anderem das komplette SQL ausgegeben, was SQLObject an die Datenbank schickt. Bei der Entwicklung von neuen Anwendungen ist dieses Feature äußerst hilfreich.

Bücher hinzufügen
Jetzt müssen wir noch die Bücher hinzufügen. Außerdem werden wir die Erzeugung der Tabellen und der Klassen in ein eigenes kleines Modul bookdb.py auslagern. Um das Ganze auszuprobieren, löschen Sie am besten vorher mit DROP TABLE author die alte Tabelle noch einmal. Den Quellcode zu bookdb.py sehen Sie in Listing 2:

Listing 2

from sqlobject import *

class Author(SQLObject):

firstName = StringCol(length=30)
lastName = StringCol(length=30)

books = MultipleJoin('Book')

def __str__(self):
return "%s %s" % (self.firstName, self.lastName)


class Book(SQLObject):

title = StringCol()
author = ForeignKey('Author')

def __str__(self):
return "%s" % (self.title)


def createTables(connection):
Author._connection = connection
Author.createTable(ifNotExists = True)
Book._connection = connection
Book.createTable(ifNotExists = True)

def connect(connection):
Author._connection = connection
Book._connection = connection

def insertAuthor(first, last):
return Author(firstName = first, lastName = last)

def insertBook(bookTitle, bookAuthor):
return Book(title = bookTitle, author = bookAuthor)

createTables(connectionForURI('mysql://bookuser:bookpasswd@localhost/books'))


Zuerst definieren wir die Klassen Author und Book mit den jeweiligen Datentypen ihrer Attribute bzw. Spaltentypen. Die Methode _str_ wird von Python immer dann aufgerufen wenn ein Objekt mit print ausgegeben wird. Das ist nicht notwendig, erleichtert aber die Ausgabe der Objekte. Es ist vom Gedanken her ähnlich wie Java's toString()-Methode. Da wir in der book-Tabelle für den Author nur eine ID speichern, welche automatisch beim Anlegen eines jeden Authors in der author-Tabelle angelegt wird, spezifizieren wir hier einen ForeignKey. In der Author-Klasse verweisen wir mittels MultipleJoin auf die book-Tabelle. MultipleJoin deswegen, weil in unserem Beispiel ein Author mehrere Bücher haben kann. Anschließend definieren wir noch ein paar Hilfsfunktionen, um die Tabellen zu erzeugen, eine Verbindung aufzubauen und neue Datensätze einzufügen. Am Schluss erzeugen wir dann die Tabellen. Beachten Sie, dass die letzte Zeile sich nicht innerhalb einer Funktion oder Klasse befindet, d.h. sie wird jedes Mal aufgerufen wenn das Modul importiert wird. Dadurch stellen wir sicher, dass die Tabellen, falls noch nicht vorhanden, generiert werden. Das hier vorgestellte Modul ist nur eine von vielen möglichen Lösungen, wie man so etwas angehen könnte. Bedenken Sie auch, dass man Usernamen und Passwort bei größeren Anwendungen besser nicht in den Quellcode kodiert, sondern z.B. aus einer Konfigurationsdatei einliest. Ein Beispiel für die Verwendung des Moduls sehen Sie in Listing 3.

Listing 3

from bookdb import *

connect(connectionForURI('mysql://bookuser:bookpasswd@localhost/books'))


a1 = insertAuthor("J.R.R.", "Tolkien")
a2 = insertAuthor("Alexandra", "Morton")
a3 = insertAuthor("Ales", "Martelli")
insertBook("The Lord of the Rings", a1)
insertBook("The Hobbit", a1)
insertBook("Listening to Whales", a2)
insertBook("Python in a Nutshell", a3)

result = Author.select(Author.q.firstName == "J.R.R.")

print "got %d rows" % (result.count())

for author in result:
print author
for book in author.books:
print book

result = Author.select(Author.q.lastName == "Martelli")
if result.count() > 0:
author = result[0]
author.firstName = "Alex"
print author


Hier sehen Sie nun, wie einfach es ist, neue Datensätze einzufügen. Beachten Sie, dass Sie beim Einfügen eines neuen Buches ein Author-Objekt mit angeben müssen. Abschließend sehen Sie noch, wie man Daten aus der Datenbank auslesen kann, was wohl die häufigste Operation im täglichen Umgang mit Datenbanken sein dürfte. Da jede Klasse von SQLObject erbt, steht für jede Klasse die Methode select zur Verfügung. Wichtig ist die Verwendung des Attributs q, welches zum Erzeugen der SQL Query notwendig ist. Sie brauchen sich um nichts weiter zu kümmern, nur vergessen dürfen Sie es nicht. Wenn Sie wissen wollen, wie viele Datensätze zurückgeliefert wurden, können Sie dafür die Methode count() nutzen. Über das zurückgelieferte Ergebnis lässt sich mit Hilfe einer for-Schleife iterieren, da es sich dabei um einen Generator handelt. Da wir für die Klassen die Methode _str_ überladen haben, können wir hier mit einfach mit print ausgeben. Abschließend korrigieren wir noch einen Tippfehler im Namen von Alex Martelli, dem Author des äußert empfehlenswerten Buches Python in a Nutshell.

Ruby
Auch wenn der Schwerpunkt dieses Artikels auf Python lag, möchte ich noch kurz erwähnen, dass es natürlich auch für Ruby OR Mapper gibt. Einer davon, ActiveRecord, ist Teil des RubyOnRails-Projektes [6], welches in letztes Zeit viel Aufmerksamkeit erlangt hat. ActiveRecord kann aber auch ohne den Rest von Rails benutzt werden. Eine Alternative dazu ist Lafcadio [7], welches momentan nur MySQL unterstützt, aber in Zukunft auch andere Systeme unterstützen soll. Wenn Sie mal beim Ruby Application Archiv [8] oder bei RubyForge [9] vorbeischauen, finden Sie bestimmt noch mehr.

Fazit
Ich hoffe, ich konnte mit den Beispielen demonstrieren, dass SQLObject einfach zu benutzen ist und dass es sich um eine interessante Alternative zur direkten Verwendung von SQL innerhalb des Python-Code handelt. Ob es Sinn macht, SQLObject einzusetzen, muss natürlich von Projekt zu Projekt entschieden werden. Ein Vorteil der direkten Verwendung von SQL wären eventuell Performance Vorteile, aber das dürfte sich in Grenzen halten und bei viel zu vielen Programmen wurde mehr Wert auf Performance als auf gutes Design und Einfachheit gelegt. Machen Sie also hier im Zweifel Messungen und entscheiden Sie dann, ob SQLObject wirklich einen Nachteil hinsichtlich der Performance darstellt. Ein weiterer Vorteil der direkten Verwendung von SQL ist die etwas größere Flexibilität, denn SQLObject bietet mit seinen Möglichkeiten nicht alles, was SQL bietet, aber in der Regel stellt das kein Problem dar. Natürlich gibt es noch weitere OR Mapper für Python. Eine Übersicht finden Sie unter [10]. Zum Schluss möchte ich mich noch bei den Mitgliedern der SQLObject-Mailingliste und besonders bei Ian Bicking, dem Author von SQLObject für die Beantwortung meiner Fragen und die Entwicklung von SQLObject bedanken.
Markus Jais ist Softwareentwickler. In seiner Freizeit trifft er sich mit Freunden, geht Wandern und beschäftigt sich mit Ruby, Perl, Python, Java, PHP, XML, MySQL, PostgreSQL und lernt Spanisch und Französisch. Im Internet ist er unter www.mjais.de zu finden.

Links


    Hat Ihnen dieser Artikel gefallen? Dann abonnieren Sie das Entwickler Magazin direkt über unser Online-Formular.

zur vorherigen Seite
zurück
an den Anfang der Seite
nach oben
Diesen Artikel drucken
drucken
Diesen Artikel weiterempfehlen
empfehlen

Software & Support Verlag GmbH