miercuri, 19 august 2009

Python SQLAlchemy ORM

Zilele trecute a trebuit sa implementez niste entitati care sa mapeze tabele dintr-o baza de date la o logica obiectuala. In Java, acest lucru il fac utilizand Hibernate. In Python am ales sa folosesc SQLAlchemy. La inceput sintaxa a fost un pic derutanta dar dupa ce m-am obisnuit cu ea a fost extrem de usor sa implementez clasele de care aveam nevoie. Ca backend am folosit o baza de date Oracle XE. In continuare voi arata cum se poate implementa o relatie one-to-many si cum se pot implementa query-uri utilizand SQLAlchemy. Pentru a face lucrurile un pic mai interesante voi utiliza o cheie primara compozita. Voi presupune ca implementam entitatile in pachetul da.

1. In fisierul __init__.py am ales sa instantiez dependentele sqlalchemy ce vor fi folosite in fiecare entitate.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

BaseEntity = declarative_base()
strDbURL = "oracle://db_tes:xxx@xe"
objDbEngine = create_engine(strDbURL, echo=False)

Session = sessionmaker(bind=objDbEngine)
Session.configure(bind=objDbEngine)

def getConnection():
return Session()

In acest moment putem incepe sa implementam entitatile. Fiecare entitate va extinde BaseEntity. De fiecare data cand vrem sa obtinem o conexiune la baza de date utilizam metoda getConnection(). O sessiune SQLAlchemy este asemanatoare cu o sesiune Hibernate.

2. Definim o clasa utilizator care are ca si cheie primara(username si parola --- nu faceti asa in practica: puteti alege cnp-ul ca metoda de identificare). Aici vreau doar sa demonstrez utilizarea cheilor compozite.

from da import BaseEntity
from sqlalchemy import Column,Integer,String,Numeric,DateTime,ForeignKey,Sequence
from sqlalchemy.orm import relation, backref

class User(BaseEntity):
username = Column("username", String, primary_key=True)
password = Column("password", String, primary_key=True)
descriere = Column("descriere", String)

def __init__(self, strUsername, strPassword, strDesc):
self.username = strUsername
self.password = strPassword
self.descriere = strDesc

3. In continuare implementam clasa adresa.

from da import BaseEntity
from sqlalchemy import Column,Integer,String,Numeric,DateTime,ForeignKey,Sequence
from sqlalchemy.orm import relation, backref

class Adress(BaseEntity):
id = Column("id", Integer, Sequence("seq_xxx_id"), primary_key=True)
fk_username = Column("fk_username", String, ForeignKey("utilizatori.username"))
fk_password = Column("fk_password", String, ForeignKey("utilizatori.password"))
street = Column("street", String)
number = Column("number", String)

user = relation("User",
foreign_keys=[fk_username, fk_password],
primaryjoin="User.username == Address.fk_username and "
"User.password == Address.fk_password",
backref="adresses")

def __init__(self, strUser, strPasswd, strStreet, strNumber):
self.fk_username = strUser
self.fk_password = strPassword
self.street = strStreet
self.number = strNumber

Observatii: in acest exemplu incerc sa demonstrez puterea si flexibilitatea pe care o ofera SQLAlchemy. In primul rand, in orice entitate SQLAlchemy vom defini structura tabelei mapata la atribute ale entitatii. Utilizarea functiei relation permite implementarea legaturilor de tip one-to-one, one-to-many si many-to-many. De asemenea, utilizarea argumentului keyword backref indica frameworkului sa adauge automat un atribut la capatul celalalt al relatiei.

4. Dupa ce am implementat toate entitatile trebuie sa le si folosesc. In exemplul de mai jos arata o succesiune de linii de cod care demonstreaza functionalitatea entitatilor.

import da
from da import User, Address

# query pentru a selecta toti utilizatorii din bd
objSes = da.getConnection()
for objUsr, in objSes.query(User).all():
print(objUsr.addresses)

# query pentru a conditiona returnarea unui utilizator care are o anumita adresa
for objUsr in objSes.query(User, Address).filter(User.username == Address.fk_username and User.password == Address.fk_password).filter(Address.id == 1).all():
print(objUsr.username)

In concluzie, SQLAlchemy faciliteaza maparea tabelelor in logica obiect(la fel ca orice ORM) dar nu necesita un fisier de configurare separat sau anotari suplimentare(precum Hibernate). De asemenea, scrierea de query-uri este extrem de usoara. Ceea ce nu s-a aratat dar este intuitiv este adaugare de noi entitati in bd. Pentru aceasta folositi objSes.add(objInstance). Folositi add chiar daca vreti sa faceti update(SQLAlchemy va sti ce doriti sa faceti in functie de atributele setate in instanta).

Niciun comentariu:

Trimiteți un comentariu