About
RSS

Bit Focus


对象炼金术 - 体验 SQLAlchemy

Posted at 2012-03-09 13:46:26 | Updated at 2018-08-15 15:41:27

一直听说 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 真心看着很想吐槽啊, 要如何来改进这一点呢? 敬请关注下节: 外键与关系.

Post tags:   ORM  SQLAlchemy  Python  Tutorial

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site