Co warto wiedzieć o

SqlAlchemy?

Grzegorz Biziel

Jan Słowik

wroc.py #17, 7 lipca 2015

Hello World

SqlAlchemy

  • ORM
  • wspomaga pracę z czystym SQLem
  • wspiera wiele różnych baz danych
  • dostarcza wiele wzorców (identity map, unit of work, dziedziczenia, etc.)

Mapowanie

deklaratywne

user_table = Table('USER', Metadata
          Column('id', Integer, primary_key=True, nullable=False),
          Column('name', String(length=64), nullable=False))

class User(Base):
    __table__ = user_table

    name = user_table.c.name

klasyczne

user_table = Table('USER', Metadata,
          Column('id', Integer, primary_key=True, nullable=False),
          Column('name', String(length=64), nullable=False))

class User(Base):
    def __init__(self, name:
        self.name = name

mapper(User, user_table)

Relacje

deklaratywne

class User(Base):
    __table__ = user_table

    tasks = relationship('Task', backref='user')

class Task(Base):
    ...

klasyczne

mapper(User, user_table, properties={
    'tasks' : relationship(Task, backref='user')
})

Pobieranie obiektów

session
    .query(Klasa_pobieranego_obiektu)
    .join(Inna_klasa)
    .filter(Inna_klasa.atrybut == wartosc)
    .all()

Dlaczego w ogóle SqlAlchemy?

ORM może być rozpatrywany jako anty-wzorzec

Co warto wiedzieć o SqlAlchemy?

Mapowanie z...

tabeli , połączenia tabeli , widoku

base_product_table = [...]
specialization_table = [...]

product_join = join(
    base_product_table,
    specialization_table)

class Product(Base):
    __table__ = product_join

    [...]

Dziedziczenie

  • z jednej tabeli
  • z wielu tabel
  • z połączenia tabel

class Order(Base):
    __table__ = order_table
    id = order_table.c.id
    state = order_table.c.state
    [...]

    __mapper_args__ = {
        'polymorphic_on': state
    }

class InProgressOrder(Order):
    [...]
    __mapper_args__ = {
        'polymorphic_identity': 'INPROGRESS',
    }

Dziedziczenie z wielu tabel

Dziedziczenie z połączenia tabel

Eager loading vs. lazy loading

joinedload

[task for task in user.tasks
if task.project.code == project_code ]

Domyślnie z powodu na lazy loading wykonanie tego fragmentu wiązałoby się z wieloma zapytaniami do bazy danych.

session.query(User)
.options(
joinedload(User.tasks)
.joinedload(Task.project))
.filter([...]).first()

Eager loading vs. lazy loading

subqueryload

session.query(User)
.options(
subquery(User.tasks)
.joinedload(Task.project))
.filter([...]).first()

Eager loading vs. lazy loading

contains_eager

Wczesne ładowanie na podstawie danych dostępnych w wyniku zapytaniu.

user = session.query(User)
.join(User.tasks)
.options(contains_eager(User.tasks))
.filter(User.id == user_id,
Task.state == state)
.first()

Co będzie w user.tasks?

Aliasowanie

from sqlalchemy.orm import aliased
adalias1 = aliased(Address)
adalias2 = aliased(Address)
session.query(User).\
    join(adalias1, User.addresses).\
    join(adalias2, User.addresses).\
    filter(adalias1.email_address=='jack@google.com').\
    filter(adalias2.email_address=='j25@yahoo.com')

Association proxy

Przenoszenie atrybutów między obiektami będącymi w relacji

class Project(Base):
    [...]
    codename = project_table.c.codename
    task = relationship('Task', backref='project')

class Task(Base):
    [...]
    project_codename
        = association_proxy('project', 'codename')

Stack trace

Arrrggg...

Podsumowanie

  • daje dużo możliwości/bardzo elastyczne
  • pozwala wykorzystać zalety relacyjnej bazy danych w obiektowym świecie
  • pozwala zbudować w model obiektowy na bazie danych relacyjnych
  • wymaga dużo pracy

Dzięki za uwagę!