python學習筆記SQLAlchemy(八)

ORM 與 SQLAlchemy 簡介

ORM 全稱 Object Relational Mapping, 翻譯過來叫對象關係映射。簡單的說,ORM 將數據庫中的表與面嚮對象語言中的類建立了一種對應關係。這樣,我們要操作數據庫,數據庫中的表或者表中的一條記錄就可以直接通過操作類或者類實例來完成。

python學習筆記SQLAlchemy(八)

SQLAlchemy 是Python 社區最知名的 ORM 工具之一,爲高效和高性能的數據庫訪問設計,實現了完整的企業級持久模型。

連接與創建

安裝SQLAlchemy:

cq@ubuntu:~$ sudo pip3 install sqlalchemy
The directory '/home/cq/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/cq/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting sqlalchemy
  Downloading SQLAlchemy-1.2.2.tar.gz (5.5MB)
    100% |████████████████████████████████| 5.5MB 115kB/s 
Installing collected packages: sqlalchemy
  Running setup.py install for sqlalchemy ... done
Successfully installed sqlalchemy-1.2.2

另外,需要安裝一個 Python 與 MySQL 之間的驅動程序:

apt-get install python-mysqldb
pip3 install mysqlclient

連接數據庫
創建py文件寫入下面的內容:

#coding=utf-8
from sqlalchemy import create_engine
engine = create_engine('mysql+mysqldb://root:@localhost:3306/blog')
engine.execute('select * from user').fetchall()
print(engine)

在上面的程序中,我們連接了默認運行在 3306 端口的 MySQL 中的 blog 數據庫。
首先導入了 create_engine, 該方法用於創建 Engine 實例,傳遞給 create_engine 的參數定義了 MySQL 服務器的訪問地址,其格式爲 mysql://<user>:<password>@<host>/<db_name>。接着通過 engine.execute 方法執行了一條 SQL 語句,查詢了 user 表中的所有用戶。

對象關係映射

要使用 ORM, 我們需要將數據表的結構用 ORM 的語言描述出來。SQLAlchmey 提供了一套 Declarative 系統來完成這個任務。我們以創建一個 users 表爲例,看看它是怎麼用 SQLAlchemy 的語言來描述的:

#coding=utf-8
from sqlalchemy import create_engine,Column,String,Text,Integer
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('mysql+mysqldb://root:@localhost:3306/blog')
Base = declarative_base()

class User(Base):
    __table__ = 'user'
    id = Column(Integer,primary_key=True)
    username = Column(String(64),nullable=False,index=True)
    password = Column(String(64),nullable=False)
    email = Column(String(64),nullable=False,index=True)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__,self.username)

Base.metadata.create_all(engine)

如果想使 Python 類映射到數據庫表中,需要基於 SQLAlchemy 的 declarative base class,也就是宣言基類創建類。當基於此基類,創建 Python 類時,就會自動映射到相應的數據庫表上。創建宣言基類,可以通過declarative_base 方法進行

from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('mysql+mysqldb://root:@localhost:3306/blog')
Base = declarative_base()

在 User 類中,用 tablename 指定在 MySQL 中表的名字。我們創建了三個基本字段,類中的每一個 Column 代表數據庫中的一列,在 Colunm 中,指定該列的一些配置。第一個字段代表類的數據類型,上面我們使用 String, Integer 倆個最常用的類型,其他常用的包括:

  • Text
  • Boolean
  • SmallInteger
  • DateTime
    nullable=False 代表這一列不可以爲空,index=True 表示在該列創建索引。
    另外定義 repr 是爲了方便調試,你可以不定義,也可以定義的更詳細一些。
    運行程序,程序不會有輸出信息,但是 sqlalchemy 已經在 MySQL 數據庫裏面爲我們創建了 users 表。
    此時 User 有一個 table 屬性,記錄了定義的表信息
In [1]: from sql import User

In [2]: User.__table__
Out[2]: Table('users', MetaData(bind=None), Column('id', Integer(), table=<users>, primary_key=True, nullable=False), Column('username', String(length=64), table=<users>, nullable=False), Column('password', String(length=64), table=<users>, nullable=False), Column('email', String(length=64), table=<users>, nullable=False), schema=None)

一對多關係

對於一個普通的博客應用來說,用戶和文章顯然是一個一對多的關係,一篇文章屬於一個用戶,一個用戶可以寫很多篇文章,那麼他們之間的關係可以這樣定義:

from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import Column, String, Integer, Text
class User(Base):

    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(64), nullable=False, index=True)
    password = Column(String(64), nullable=False)
    email = Column(String(64), nullable=False, index=True)
    articles = relationship('Article')

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.username)

class Article(Base):

    __tablename__ = 'articles'

    id = Column(Integer, primary_key=True)
    title = Column(String(255), nullable=False, index=True)
    content = Column(Text)
    user_id = Column(Integer, ForeignKey('users.id'))
    author = relationship('User')

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.title)

每篇文章有一個外鍵指向 users 表中的主鍵 id, 而在 User 中使用 SQLAlchemy 提供的 relationship 描述 關係。而用戶與文章的之間的這個關係是雙向的,所以我們看到上面的兩張表中都定義了 relationship。

創建的 articles 表有外鍵 userid, 在 SQLAlchemy 中可以使用 ForeignKey 設置外鍵。設置外鍵後,如果能夠直接從 articles 的實例上訪問到相應的 users 表中的記錄會非常方便,而這可以通過 relationship 實現。上面的代碼通過 relationship 定義了 author 屬性,這樣就可以直接通過 articles.author 獲取相應的用戶記錄。

SQLAlchemy 提供了 backref 讓我們可以只需要定義一個關係:
articles = relationship('Article', backref='author')
添加了這個就可以不用再在 Article 中定義 relationship 了!

一對一關係
在 User 中我們只定義了幾個必須的字段, 但通常用戶還有很多其他信息,但這些信息可能不是必須填寫的,我們可以把它們放到另一張 UserInfo 表中,這樣 User 和 UserInfo 就形成了一對一的關係。你可能會奇怪一對一關係爲什麼不在一對多關係前面?那是因爲一對一關係是基於一對多定義的:

class User(Base):

    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(64), nullable=False, index=True)
    password = Column(String(64), nullable=False)
    email = Column(String(64), nullable=False, index=True)
    articles = relationship('Article', backref='author')
    userinfo = relationship('UserInfo', backref='user', uselist=False)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.username)

class UserInfo(Base):

    __tablename__ = 'userinfos'

    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    qq = Column(String(11))
    phone = Column(String(11))
    link = Column(String(64))
    user_id = Column(Integer, ForeignKey('users.id'))

定義方法和一對多相同,只是需要添加 uselist=False 。
需要注意的地方是定義 users 屬性時,使用了 relationship 的 backref 參數,該參數使得可以在 UserInfo 實例中,通過 userinfos.user 訪問關聯的所有用戶信息。

多對多關係
一遍博客通常有一個分類,好幾個標籤。標籤與博客之間就是一個多對多的關係。多對多關係不能直接定義,需要分解成倆個一對多的關係,爲此,需要一張額外的表來協助完成,通常對於這種多對多關係的輔助表不會再去創建一個類,而是使用 sqlalchemy 的 Table 類:

# 在原來代碼的基礎上導入
from sqlalchemy import Table

article_tag = Table(
    # 第一個參數爲表名稱,第二個參數是 metadata,這倆個是必須的,Base.metadata 是 sqlalchemy.schema.MetaData 對象,表示所有 Table 對象集合, create_all() 會觸發 CREATE TABLE 語句創建所有的表。
    'article_tag', Base.metadata,
    # 對於輔助表,一般存儲要關聯的倆個表的 id,並設置爲外鍵
        #course_tag 是雙主鍵,雙主鍵的目的就是爲了約束避免出現重複的一對主鍵記錄,大部分情況都是應用在這種多對多的中間表中。
    Column('article_id', Integer, ForeignKey('articles.id'), primary_key=True),
    Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True)
)

class Tag(Base):

    __tablename__ = 'tags'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False, index=True)
    articles = relationship('Articles',
                              secondary=article_tag,
                              backref='tages')
    #secondary 指的是中間表,backref 指向自己的這個表

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.name)

映射到數據
表已經描述好了,在文件末尾使用下面的命令在我們連接的數據庫中創建對應的表:

if __name__ == '__main__':
    Base.metadata.create_all(engine)

查看mysql:

mysql> show tables;
+----------------+
| Tables_in_blog |
+----------------+
| article_tag    |
| articles       |
| tags           |
| userinfos      |
| users          |
+----------------+
5 rows in set (0.00 sec)

簡單 CURD

session 內部的實現都是調用 engine 的各種接口,相當於 session 是 engine 的一個封裝,比如 session.commit 的時候會先調用 engine.connect() 去連接數據庫,再調用執行 sql 相關的接口。

當你想打電話給朋友時,你是否得用手機撥通他的號碼才能建立起一個會話?同樣的,你想和 MySQL 交談也得先通過 SQLAlchemy 建立一個會話:

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()

你可以把 sessionmaker 想象成一個手機,engine 當做 MySQL 的號碼,撥通這個“號碼”我們就創建了一個 Session 類,下面就可以通過這個類的實例與 MySQL 愉快的交談了!

Create
如果你玩過LOL, 我想你一定知道Faker。而在 Python的世界中,Faker 是用來生成虛假數據的庫。 安裝它:

$ sudo pip install faker

# 導入 faker 工廠對象
    from faker import Factory

    # 創建一個 faker 工廠對象
    faker = Factory.create()
    Session = sessionmaker(bind=engine)
    session = Session()

    faker_users = [User(
        # 使用 faker 生成一個人名
        username=faker.name(),
        # 使用 faker 生成一個單詞
        password=faker.word(),
         # 使用 faker 生成一個郵箱
        email=faker.email(),
    ) for i in range(10)]
    # add_all 一次性添加多個對象
    session.add_all(faker_users)

    # 生成 5 個分類
    faker_categories = [Category(name=faker.word()) for i in range(5)]
    session.add_all(faker_categories)

    # 生成 20 個標籤
    faker_tags= [Tag(name=faker.word()) for i in range(20)]
    session.add_all(faker_tags)

    # 生成 100 篇文章
    for i in range(100):
        article = Article(
            # sentence() 生成一句話作爲標題
            title=faker.sentence(),
            # 文章內容爲隨機生成的 10-20句話
            content=' '.join(faker.sentences(nb=random.randint(10, 20))),
            # 從生成的用戶中隨機取一個作爲作者
            author=random.choice(faker_users),
            # 從生成的分類中隨機取一個作爲分類
            category=random.choice(faker_categories)
        )
        # 從生成的標籤中隨機取 2-5 個作爲分類,注意 sample() 函數的用法
        for tag in random.sample(faker_tags, random.randint(2, 5)):
            article.tags.append(tag)
        session.add(article)

    session.commit()

在上面的代碼中我們創建了10個用戶,5個分類,20個標籤,100篇文章,並且爲每篇文章隨機選擇了2~5個標籤。

使用 SQLAlchemy 往數據庫中添加數據,我們只需要創建相關類的實例,調用 session.add() 添加一個,或者 session.add_all() 一次添加多個, 最後 session.commit() 就可以了。

Retrieve
python學習筆記SQLAlchemy(八)

如果我們知道用戶 id,就可以用 get 方法, filter_by 用於按某一個字段過濾,而 filter 可以讓我們按多個字段過濾,all 則是獲取所有。

獲取某一字段值可以直接類的屬性獲取:
python學習筆記SQLAlchemy(八)

Update
更新一個字段:

>>> a = session.query(Article).get(10)
>>> a.title = 'My test blog post'
>>> session.add(a)
>>> session.commit()

添加一個標籤:

>>> a = session.query(Article).get(10)
>>> a.tags.append(Tag(name='python'))
>>> session.add(a)
>>> session.commit()

Delete

>>> a = session.query(Article).get(10)
>>> session.delete(a)
>>> session.commit()

刪除直接調用 delete 刪除獲取到的對象,提交 session 即可。

完整代碼

# coding: utf-8

import random
from faker import Factory

from sqlalchemy import create_engine, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey
from sqlalchemy import Column, String, Integer, Text
from sqlalchemy.orm import sessionmaker, relationship

engine = create_engine('mysql+mysqldb://root@localhost:3306/blog?charset=utf8')
Base = declarative_base()

class User(Base):

    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(64), nullable=False, index=True)
    password = Column(String(64), nullable=False)
    email = Column(String(64), nullable=False, index=True)
    articles = relationship('Article', backref='author')
    userinfo = relationship('UserInfo', backref='user', uselist=False)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.username)

class UserInfo(Base):

    __tablename__ = 'userinfos'

    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    qq = Column(String(11))
    phone = Column(String(11))
    link = Column(String(64))
    user_id = Column(Integer, ForeignKey('users.id'))

class Article(Base):

    __tablename__ = 'articles'

    id = Column(Integer, primary_key=True)
    title = Column(String(255), nullable=False, index=True)
    content = Column(Text)
    user_id = Column(Integer, ForeignKey('users.id'))
    cate_id = Column(Integer, ForeignKey('categories.id'))
    tags = relationship('Tag', secondary='article_tag', backref='articles')

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.title)

class Category(Base):

    __tablename__ = 'categories'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False, index=True)
    articles = relationship('Article', backref='category')

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.name)

article_tag = Table(
    'article_tag', Base.metadata,
    Column('article_id', Integer, ForeignKey('articles.id')),
    Column('tag_id', Integer, ForeignKey('tags.id'))
)

class Tag(Base):

    __tablename__ = 'tags'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False, index=True)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.name)

if __name__ == '__main__':
    Base.metadata.create_all(engine)

    faker = Factory.create()
    Session = sessionmaker(bind=engine)
    session = Session()

    faker_users = [User(
        username=faker.name(),
        password=faker.word(),
        email=faker.email(),
    ) for i in range(10)]
    session.add_all(faker_users)

    faker_categories = [Category(name=faker.word()) for i in range(5)]
    session.add_all(faker_categories)

    faker_tags= [Tag(name=faker.word()) for i in range(20)]
    session.add_all(faker_tags)

    for i in range(100):
        article = Article(
            title=faker.sentence(),
            content=' '.join(faker.sentences(nb=random.randint(10, 20))),
            author=random.choice(faker_users),
            category=random.choice(faker_categories)
        )
        for tag in random.sample(faker_tags, random.randint(2, 5)):
            article.tags.append(tag)
        session.add(article)

    session.commit()

快速入門Flask-SQLAlchemy

Flask-SQLAlchemy 使用起來非常有趣,對於基本應用十分容易使用,並且對於大型項目易於擴展。有關完整的指南,請參閱 SQLAlchemy 的 API 文檔。

一個最小應用
常見情況下對於只有一個 Flask 應用,所有您需要做的事情就是創建 Flask 應用,選擇加載配置接着創建 SQLAlchemy 對象時候把 Flask 應用傳遞給它作爲參數。

一旦創建,這個對象就包含 sqlalchemy 和 sqlalchemy.orm 中的所有函數和助手。此外它還提供一個名爲 Model 的類,用於作爲聲明模型時的 delarative 基類:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120), unique=True)

    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __repr__(self):
        return '<User %r>' % self.username

爲了創建初始數據庫,只需要從交互式 Python shell 中導入 db 對象並且調用 SQLAlchemy.create_all() 方法來創建表和數據庫:

>>> from yourapplication import db
>>> db.create_all()

Boom, 您的數據庫已經生成。現在來創建一些用戶:

>>> from yourapplication import User
>>> admin = User('admin', '[email protected]')
>>> guest = User('guest', '[email protected]')

但是它們還沒有真正地寫入到數據庫中,因此讓我們來確保它們已經寫入到數據庫中:

>>> db.session.add(admin)
>>> db.session.add(guest)
>>> db.session.commit()

訪問數據庫中的數據也是十分簡單的:

>>> users = User.query.all()
[<User u'admin'>, <User u'guest'>]
>>> admin = User.query.filter_by(username='admin').first()
<User u'admin'>

簡單的關係
SQLAlchemy 連接到關係型數據庫,關係型數據最擅長的東西就是關係。因此,我們將創建一個使用兩張相互關聯的表的應用作爲例子:

from datetime import datetime

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80))
    body = db.Column(db.Text)
    pub_date = db.Column(db.DateTime)

    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
    category = db.relationship('Category',
        backref=db.backref('posts', lazy='dynamic'))

    def __init__(self, title, body, category, pub_date=None):
        self.title = title
        self.body = body
        if pub_date is None:
            pub_date = datetime.utcnow()
        self.pub_date = pub_date
        self.category = category

    def __repr__(self):
        return '<Post %r>' % self.title

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '<Category %r>' % self.name

首先讓我們創建一些對象:

>>> py = Category('Python')
>>> p = Post('Hello Python!', 'Python is pretty cool', py)
>>> db.session.add(py)
>>> db.session.add(p)

現在因爲我們在 backref 中聲明瞭 posts 作爲動態關係,查詢顯示爲:

>>> py.posts
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x1027d37d0>

它的行爲像一個普通的查詢對象,因此我們可以查詢與我們測試的 “Python” 分類相關的所有文章(posts):

>>> py.posts.all()
[<Post 'Hello Python!'>]

啓蒙之路
您僅需要知道與普通的 SQLAlchemy 不同之處:

  • SQLAlchemy 允許您訪問下面的東西:
  • sqlalchemy 和 sqlalchemy.orm 下所有的函數和類
  • 一個叫做 session 的預配置範圍的會話(session)
  • metadata 屬性
  • engine 屬性
  • SQLAlchemy.create_all() 和 SQLAlchemy.drop_all(),根據模型用來創建以及刪除表格的方法
  • 一個 Model 基類,即是一個已配置的聲明(declarative)的基礎(base)
  • Model 聲明基類行爲類似一個常規的 Python 類,不過有個 query 屬性,可以用來查詢模型 (Model 和 BaseQuery)
    您必須提交會話,但是沒有必要在每個請求後刪除它(session),Flask-SQLAlchemy 會幫您完成刪除操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章