SQLAlchemy入門(一)

jopen 8年前發布 | 19K 次閱讀 數據庫 Python開發 SQLAlchemy

環境:Ubuntu 15.10 64-bit

SQLAlchemy是Python的ORM框架,它的理念是:數據庫的量級和性能重要于對象集合,而對象集合的抽象又重要于表和行。

安裝

直接通過pip安裝:

$ pip install sqlalchemy

打開Python,測試是否安裝成功:

>>> import sqlalchemy
>>> sqlalchemy.__version__
'1.0.9'

創建引擎

SQLite

首先以SQLite為例,因為它比較簡單。

from sqlalchemy import create_engine, MetaData

engine = create_engine('sqlite:///foo.db', echo=True)
metadata = MetaData(engine)

參數 sqlite:///foo.db 解釋為:

sqlite://<nohostname>/<path>

其中foo.db是相對路徑。也可寫成:

sqlite:///./foo.db

SQLAlchemy缺省使用Python內建的sqlite3模塊來連接SQLite數據庫。執行完后,可以發現當前目錄多了foo.db文件,不妨用sqlite打開看看。

$ sqlite3 foo.db
SQLite version 3.8.11.1 2015-07-29 20:00:57
Enter ".help" for usage hints.
sqlite> .tables
users

注意這里用的是sqlite3而非sqlite,因為foo.db是經由Python內建的sqlite3模塊創建的。

MySQL

再來看看連接MySQL時怎么創建引擎。

本文后續示例全部基于MySQL,這是與官方文檔不同的地方。

先在MySQL里創建一個測試數據庫:sa_test,后續示例都將基于這個數據庫。

mysql> CREATE DATABASE sa_test DEFAULT CHARACTER SET UTF8;
from sqlalchemy import create_engine, MetaData

engine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=True)
metadata = MetaData(engine)

這里的參數看上去就比較復雜了,完整的格式為:

dialect+driver://username:password@host:port/database

這里driver用了mysqldb,詳見: MySQLdb:Python操作MySQL數據庫

引擎配置的詳細信息可參考官方文檔: Engine Configuration

MetaData

前面在創建MetaData時綁定了引擎:

metadata = MetaData(engine)

當然也可以不綁定。綁定的好處是,后續很多調用 (比如 MetaData.create_all(),Table.create(),等等)就不用指定引擎了。

創建表

創建兩張表,user和address,address表里有一個user id的外鍵。

注意:表名沒有像官方文檔及很多人推薦的那樣使用復數形式,個人偏好而已,詳細討論請見StackOverflow的這個問題: Table Naming Dilemma: Singular vs. Plural Names中文版

from sqlalchemy import create_engine, MetaData,\
        Table, Column, Integer, String, ForeignKey

engine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=True)
metadata = MetaData(engine)
user_table = Table('user', metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('fullname', String(100))
        )

address_table = Table('address', metadata,
        Column('id', Integer, primary_key=True),
        Column('user_id', None, ForeignKey('user.id')),
        Column('email', String(128), nullable=False)
        )

metadata.create_all()

執行完metadata.create_all()這一句,兩張表就創建好了,可以在MySQL里立即查看。

MetaData.create_all()可以多次調用,不會報錯,它在內部會檢查表是否已經創建。因為MetaData創建時已經綁定了引擎,所以此處create_all()就不必再指定了,否則得寫成:

metadata.create_all(engine)

創建引擎時,echo參數為True,程序運行時便有很多調試信息打印出來。在這些調試信息中,可以看到如下兩條MySQL的CREATE TABLE語句:

CREATE TABLE user (
    id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(50),
    fullname VARCHAR(100),
    PRIMARY KEY (id)
)

CREATE TABLE address (
    id INTEGER NOT NULL AUTO_INCREMENT,
    user_id INTEGER,
    email VARCHAR(128) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY(user_id) REFERENCES user (id)
)

除了metadata.create_all(),Table自己也有create方法:

create(bind=None, checkfirst=False)

參數bind一般就是指引擎。

參數checkfirst表示是否檢查表已經存在。為True時,若表已經存在,不報錯,只是什么也不做;為False時,若表已經存在,則將引發異常。

使用這個方法來創建這兩張表:

user_table.create(checkfirst=True)
address_table.create(checkfirst=True)

這里忽略了bind參數,因為創建MetaData對象時已經綁定了引擎,而創建表對象時又傳入了metadata,所以順藤摸瓜,表自己是知道引擎的。如果調整一下表的創建順序,就會報錯,因為address表里有一個user表的外鍵,而這時候user表還沒創建呢。所以,還是建議使用MetaData.create_all()吧,畢竟它也會檢查表是否已經存在。

表的反射 Table Reflection

表創建好了,一般也就不動了。所以實際應用時,往往表都已經存在,并不需要創建,只需把它們”導入”進來即可,這時就得使用autoload參數。

from sqlalchemy import create_engine, MetaData, Table

engine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=False)
metadata = MetaData(engine)

user_table = Table('user', metadata, autoload=True)

print 'user' in metadata.tables
print [c.name for c in user_table.columns]

address_table = Table('address', metadata, autoload=True)
print 'address' in metadata.tables

輸出:

True
['id', 'name', 'fullname']
True

如果MetaData沒有綁定引擎,則另需指定autoload_with參數:

user_table = Table('user', metadata, autoload=True, autoload_with=engine)

如果被反射的表外鍵引用了另一個表,那么被引用的表也會一并被反射。比如只反射address表,user表也一并被反射了。

from sqlalchemy import create_engine, MetaData, Table

engine = create_engine('mysql+mysqldb://root:******@localhost/sa_test', echo=False)
metadata = MetaData(engine)

address_table = Table('address', metadata, autoload=True)

print 'user' in metadata.tables
print 'address' in metadata.tables

輸出:

True
True

插入數據

插入數據之前,必須要有表對象,不管是新創建的,還是通過反射導入的。

Insert對象

要往表里插數據,先創建一個Insert對象:

ins = user_table.insert()
print ins

打印這個Insert對象,可以看到它所對應的SQL語句:

INSERT INTO user (id, name, fullname) VALUES (%s, %s, %s)

如果連接的數據庫不是MySQL而是SQLite,那輸出可能就是下面這樣:

INSERT INTO user (id, name, fullname) VALUES (?, ?, ?)

可見SQLAlchemy幫我們封裝了不同數據庫之間語法的差異。如果MetaData創建時沒有綁定引擎,那么輸出會略有不同:

INSERT INTO "user" (id, name, fullname) VALUES (:id, :name, :fullname)

這時SQLAlchemy還不知道具體的數據庫語法,表名加了引號(”user”),列名也改用為:id之類一般性的格式。此外,這條INSERT語句列出了user表里的每一列,而id在插入時一般是不需要指定的,可以通過Insert.values()方法加以限制:

ins = ins.values(name='adam', fullname='Adam Gu')
print ins

限制后,id列已經沒有了:

INSERT INTO user (name, fullname) VALUES (%s, %s)

可見values()方法限制了INSERT語句所包含的列。但是我們指定的name和fullname的值并沒有打印出來,這兩個值保存在Insert對象里,只有等到執行時才會用到。

執行

我們一直在說的引擎,可以理解成一個數據庫連接對象的倉庫,通過連接對象可以往數據庫發送具體的SQL語句。調用引擎的connect()方法可以獲取一個連接:

conn = engine.connect()

現在把前面的Insert對象丟給它來執行:

result = conn.execute(ins)

由調試信息可見具體的INSERT語句:

INSERT INTO user (name, fullname) VALUES (%s, %s)
('adam', 'Adam Gu')
COMMIT

返回值result是一個ResultProxy對象,ResultProxy是對DB-API中cursor的封裝。插入語句的結果并不常用,但是查詢語句肯定是要用到它的。不妨在MySQL里看一下剛插入的數據。

mysql> select * from user;
+----+------+----------+
| id | name | fullname |
+----+------+----------+
|  1 | adam | Adam Gu  |
+----+------+----------+
1 row in set (0.00 sec)

執行多條語句

還記得前面的Insert對象使用values()方法來限制列嗎?

ins = ins.values(name='adam', fullname='Adam Gu')

這種方式其實不利于Insert對象的復用,更好的做法是把參數通過execute()方法傳進去:

ins = user_table.insert()
conn.execute(ins, name='adam', fullname='Adam Gu')

Insert對象本身還是會包含所有列,最終INSERT語句里的列由execute()的參數決定。由調試信息可見具體的INSERT語句:

INSERT INTO user (name, fullname) VALUES (%s, %s)
('adam', 'Adam Gu')
COMMIT

一次插入多條記錄也很簡單,只要傳一個字典列表(每個字典的鍵必須一致)給execute()即可。

conn.execute(address_table.insert(), [
    { 'user_id': 1, 'email': 'sprinfall@gmail.com' },
    { 'user_id': 1, 'email': 'sprinfall@hotmail.com' },
    ])

調試信息里具體的INSERT語句:

INSERT INTO address (user_id, email) VALUES (%s, %s)
((1, 'sprinfall@gmail.com'), (1, 'sprinfall@hotmail.com'))
COMMIT

在MySQL里看一下插入的地址:

mysql> select * from address;
+----+---------+-----------------------+
| id | user_id | email                 |
+----+---------+-----------------------+
|  1 |       1 | sprinfall@gmail.com   |
|  2 |       1 | sprinfall@hotmail.com |
+----+---------+-----------------------+
2 rows in set (0.00 sec)

第一部分到此結束。

來自: http://segmentfault.com/a/1190000004288061

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!