SQLAlchemy
Az SQLAlchemy a pytonhoz írt open source ORM rendszer. Széles körű szolgáltatást nyújt adatbázis függetlenül, kezdve az egyszerű lekérdezés generálásig az átfogó, akár többszörös összekapcsoláson át egészen a táblák alapvető információinak kinyeréséig. Könnyű használhatósága és teljesítménye miatt ez a ma leggyakrabban használt ORM eszköz python rendszerekhez.
Az SQLAlchemy egyik nagy előnye, hogy képes mind rendkívül magas, mind pedig alacsony absztrakciót nyújtani, attól függően, hogy az adott rendszer milyen elvárásokat támaszt. Ennek elérése érdekében a hibernate-ben használt Data Mapper mintát használja. Rendkívüli előnye, hogy a leggyakrabban használt python keretrendszerek nagy mértékű támogatást biztosítanak hozzá, így a fejlesztők válláról lekerülhetnek a session kezelés, illetve a perzisztenciával kapcsolatos problémák jelentős része, de esetleg a felhasználó kezelés és felhasználói csoportok kezelésének problémája is.
Példa
A következő példa az angol Wikipédia cikkből való, és két tábla (mozik és rendezők), illetve a köztük lévő n-1 kapcsolat megvalósítását mutatja be. Látható, hogyan hozható létre a felhasználó által definiált osztályokból az adatbázis tábla, illetve hogyan lehet az adott táblához oszlopokat rendelni, ezen oszlopok tulajdonságait (például típus) megadni, lekérdezni, illetve adatbázisba menteni.
Definíció
Két tábla létrehozása:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, sessionmaker
Base = declarative_base()
class Movie(Base):
__tablename__ = 'movies'
id = Column(Integer, primary_key=True)
title = Column(String(255), nullable=False)
year = Column(Integer)
directed_by = Column(Integer, ForeignKey('directors.id'))
director = relation("Director", backref='movies', lazy=False)
def __init__(self, title=None, year=None):
self.title = title
self.year = year
def __repr__(self):
return "Movie(%r, %r, %r)" % (self.title, self.year, self.director)
class Director(Base):
__tablename__ = 'directors'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False, unique=True)
def __init__(self, name=None):
self.name = name
def __repr__(self):
return "Director(%r)" % (self.name)
engine = create_engine('dbms://user:pwd@host/dbname')
Base.metadata.create_all(engine)
Látható, hogy az egyes táblákat egy-egy osztály reprezentálja, a táblák oszlopait pedig ezen osztályok attribútumai. A __tablename__ attribútum határozza meg, hogy az adatbázisban a tábla milyen néven fog létrejönni. Egy oszlopot a Column osztály reprezentál, melynek átadhatók az adott oszlop főbb tulajdonságai. Így például a
Column(Integer, primary_key=True)
parancs egy olyan oszlopot definiál a táblában, melynek típusa integer, és elsődleges kulcs. Fontos megjegyezni, hogy a python minden esetben megköveteli azt, hogy az így létrehozott tábláknak legyen elsődleges kulcsuk. Az osztályok közötti relációkat speciálisan a relationship függvény segítségével adhatjuk meg. Ezen függvénynek különböző paramétereivel a kapcsolat több tulajdonságát is definiálhatjuk. A függvény pontos leírása és paraméterei itt találhatóak, most csak azokat vesszük át, amelyek a példában is láthatóak. A függvény első paramétere a tábla neve, vagy a táblát reprezentáló osztály. Jelen esetben a kapcsolási feltétel adott, hiszen directed_by = Column(Integer, ForeignKey('directors.id')) paranccsal egy külső kulcsot definiáltunk a Director táblához. A backref segítségével megadhatjuk, hogy a kapcsolat másik oldaláról (jelen esetben a Director osztályból) milyen attribútumon keresztül érjük el a relációt (jelen esetben a rendező filmjeit). A lazy feltétel segítségével az SQLAlchemy-t informáljuk, hogy a relációban szereplő adatokat mikor töltse be az adatbázisból. Ennek két értéke van, lazy=True, illetve lazy=False. True esetén a relációban szereplő adatok a reláció első eléréskor kerülnek betöltésre, False esetében pedig közvetlen az objektum feltöltésekor.
Adat beszúrás
Adatok beszúrása az egyes tálákba:
Session = sessionmaker(bind=engine)
session = Session()
m1 = Movie("Star Trek", 2009)
m1.director = Director("JJ Abrams")
d2 = Director("George Lucas")
d2.movies = [Movie("Star Wars", 1977), Movie("THX 1138", 1971)]
try:
session.add(m1)
session.add(d2)
session.commit()
except:
session.rollback()
Hasonlóan a java-ban használt Hibernate-hez itt is az adatbázis műveleteket a session-ok végzik. Minden session alapja az úgynevezett engine, amely az adott adatbázissal való kapcsolatot reprezentálja, az általunk megadott connection string segítségével. A táblákba beszúrás annyit jelent, hogy az általunk létrehozott osztályok megfelelő attribútumait feltöltjük az értékekkel, majd beszúrjuk őket az adott adatbázishoz tartozó session-be. A session nem csak a beszúrást, de a perzisztenciát is kezeli, így már létező rekordot reprezentáló osztályon végzett módosítás akár azonnal megjelenhet az adatbázisban is. A session-ök nagy előnye, hogy viszonylag egyszerűen bővíthetőek, vagyis egyszerűen adható hozzá új funkcionalitás. Többek között speciális eseménykezelő függvények, amelyek flush, commit, vagy a sessionhoz tartozó valamely objektum változása esetén hívódnak meg, ezzel lehetővé téve, hogy ezekre megfelelően reagálhassunk.
Lekérdezés
alldata = session.query(Movie).all()
for somedata in alldata:
print somedata
Az SQLAlchemy a következő lekérdezést küldi el az adatbázis kezelőnek:
SELECT movies.id, movies.title, movies.year, movies.directed_by, directors.id, directors.name
FROM movies LEFT OUTER JOIN directors ON directors.id = movies.directed_by
A végeredmény:
Movie('Star Trek', 2009L, Director('JJ Abrams'))
Movie('Star Wars', 1977L, Director('George Lucas'))
Movie('THX 1138', 1971L, Director('George Lucas'))
Itt megfigyelhető a relációban szereplő lazy feltétel hatása. True esetében ugyan lekérdezné az egyes mozikat, de nem kérdezné le a mozik rendezőit csak akkor, amikor a rendező relációt először használjuk. False esetén a mozik lekérdezésével egy időben minden mozihoz lekérdezi a rendezőt is.
Magának a lekérdezésnek az alapja a query osztály. Egy-egy ilyen query-t a session segítségével építünk fel. Fontos megjegyezni, hogy maga a query osztály létrehozása nem egyenlő a lekérdezés futtatásával, a lekérdezés futtatása akár a query létrehozása után is történhet. Futtatásra többféle parancs használható, például az all, amely minden eredményt lekérdez, vagy a first, ami csak akkor ad eredményt, ha létezik, egyébként hibát kapunk. Maga a query függvény egy osztályt, vagy oszlopok (Column) sorozatát várja paraméterül, attól függően, hogy mit szeretnénk lekérdezni. Lekérdezéshez feltételeket a filter függvény segítségével kapcsolhatunk. Maga a lekérdezés elkészítése a mapper osztályok segítségével rendkívül egyszerű. Például ha csak azon mozikat szeretnénk lekérdezni, amelyek címe 'Star Wars', akkor azt a következőképpen tehetjük meg:
alldata = session.query(Movie).filter(Movie.title=='Star Wars').all()
for somedata in alldata:
print somedata
Lekérdezéshez tábla kapcsolását a join segítségével tehetjük meg. A join-nak nem feltétlenül kell kapcsolási feltételt megadnunk, ha a kapcsolás egyértelmű, akkor azt a definíciókból az SQLAlchemy elvégzi helyettünk. Lekérdezés létrehozásakor több tulajdonságot is állíthatunk, így például az egyes táblák alias-ait.
Referenciák
- Notes
- Gift, Noah: Using SQLAlchemy. Developerworks. IBM, 2008. augusztus 12. (Hozzáférés: 2011. február 8.)
- Rick Copeland, Essential SQLAlchemy, O'Reilly, 2008, ISBN 0-596-51614-2