About

对象炼金术 - 体验 SQLAlchemy

一直听说 SQLAlchemy 是个神一般的 ORM, 近期终于忍不住打算搞一搞, 还是小有收获的, 写一点出来, 欢迎大家来搞.
本文中的例子都是基于 Python 2.7, SQLAlchemy 0.7 的. 数据库使用 SQLite.

废话少说, 先来一段代码
import sqlalchemy as sqla
import sqlalchemy.orm as sqlorm
from sqlalchemy.ext.declarative import declarative_base as sqla_declarative_base

Base = sqla_declarative_base()
engine = sqla.create_engine('sqlite:///test.db', echo=True)

class Artist(Base):
    __tablename__ = 'artist'
    artist_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)

Base.metadata.bind = engine
Base.metadata.create_all()

Session = sqlorm.scoped_session(sqlorm.sessionmaker(bind=engine))

def save_artist():
    artist = Artist(name='aki misawa')
    session = Session()
    try:
        session.add(artist)
        session.flush()
        print 'Artist id:', artist.artist_id
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save_artist()
    运行这一段代码将会在当前目录创建 test.db 文件作为数据文件, 同时建立一个 Artist 表, 其中有主键字段 artist_id 跟表示名字的字符串列 name. 然后向这个表中插入一个对象, 并输出这个 artistartist_id
    在这一行
Base = sqla_declarative_base()
产生了 SQLAlchemy 中用于构造表的基类. 而接下来 create_engine 调用则绑定数据库以及文件, 并设置回显 SQL.

    然后重头来了, 声明类型 Artist
class Artist(Base):
    __tablename__ = 'artist'
    artist_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
以及类成员 __tablename__ 表示表名, 接着以整数类型声明主键, 以及另一列 name 为字符串类型. 不用 varchar 真心舒畅.

    接着两句
Base.metadata.bind = engine
Base.metadata.create_all()
则是在数据库中建立表. 如果已经建立 (譬如第二次运行这个例子), 删去这两句似乎也没问题.
    虽然没看 SQLAlchemy 的实现, 但是构建 Base 类, 以及调用 create_all 建表有点多此一举的感觉. 我还是比较喜欢 GAE 存储那种直白的风格.

    后面的
Session = sqlorm.scoped_session(sqlorm.sessionmaker(bind=engine))
则是构造一个存储会话 (呃, 不是 HTTP 会话) 类, 存储对象时需要用到这个会话, 也就是接下来的
def save_artist():
    artist = Artist(name='aki misawa')
    session = Session()
    try:
        session.add(artist) # a
        session.flush()
        print 'Artist id:', artist.artist_id
        session.commit()
    finally:
        session.close()
注 a: 较早的 SQLAlchemy 在这里使用的不是 add 而是 save 函数, SQLAlchemy 0.7 版本好像这里修改了, 而且 save 还不能用了, 这种不向前兼容还真是秉承 Python 的大胆.
    而在将 artist 对象存入数据库之前, 成员 artist_id 是没有手动赋值的, 保存之后, session 会自动为之关联一个数据库中的主键值. 不过这个主键值在会话关闭后就失效了. 也就是说, 如果把上面的函数改成下面这个样子
def save_artist():
    artist = Artist(name='aki misawa')
    session = Session()
    try:
        session.add(artist)
        session.flush()
        # print 'Artist id:', artist.artist_id
        session.commit()
    finally:
        session.close()
    print 'Artist id:', artist.artist_id
就会抛出异常提示属性已经失效.

    接下来一个尝试便是外键. 现在增加一个类型 Album, 包含一个 Artist 作为外键
class Album(Base):
    __tablename__ = 'album'
    album_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
    artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))
话说回来, 似乎 ForeignKey 就是个整数 (sqlalchemy.Integer), 因此保存代码不能这样写
def save():
    artist = Artist(name='aki misawa')
    album = Album(name='stella musica', artist_id=artist)
    session = Session()
    try:
        session.add(artist)
        session.add(album)
        session.flush()
        session.commit()
    finally:
        session.close()
否则会悲剧, 提示 album.artist 不是个合适的类型没法丢进数据表. 因此只能很残念地改成 artist=artist.artist_id.
    但是根据刚才所述, artist_id 这个成员要等到 artist 被丢进数据库才行, 那只好这样来了
def save():
    artist = Artist(name='aki misawa')
    session = Session()
    try:
        session.add(artist)
        session.flush() # 0
        album = Album(name='stella musica', artist_id=artist.artist_id)
        session.add(album)
        session.flush() # 1
        session.commit()
    finally:
        session.close()
    呃, 这两个 flush 真心看着很想吐槽啊, 要如何来改进这一点呢? 敬请关注下节: 外键与关系.

Tags: ORM Python SQLAlchemy Tutorial

Loading comments


. Back to Tobary book
Tobary book - Copyright (C) ZhePlus @ Bit Focus
About