SQLAlchemy入門(一)

環境:Ubuntu 15.10 64-bithtml

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

安裝

直接經過 pip 安裝:sql

$ pip install sqlalchemy

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

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

建立引擎

SQLite

首先以 SQLite 爲例,由於它比較簡單。segmentfault

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 數據庫。執行完 create_engine 後,能夠發現當前目錄多了 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

注意這裏用的是 sqlite3 而非 sqlite,由於 foo.db 是經由 Python 內建的 sqlite3 模塊建立的。.net

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(),等等)就不用指定引擎了。

建立表

建立兩張表,useraddressaddress 表裏有一個 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 語句所包含的列。可是咱們指定的 namefullname 的值並無打印出來,這兩個值保存在 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)

第一部分到此結束。

相關文章
相關標籤/搜索