Flaskc初探七 ( 數據庫模型 / SQLAlchemy / ORM)

ORM

ORM 全拼 Object-Relation Mapping. 中文意為 對象-關系映射. 主要實現模型對象到關系數據庫數據的映射. 和Java中的JDBC 有異曲同工之處

優點

  • 通過改變數據庫模型改變表結構
  • 通過模型類進行數據庫的增刪改查操作.
  • 只需要面向對象編程, 不需要面向數據庫編寫代碼.
    • 對數據庫的操作都轉化成對類屬性和方法的操作.
    • 不用編寫各種數據庫的sql語句.
  • 實現了數據模型與數據庫的解耦, 屏蔽了不同數據庫操作上的差異.
    • 不在關注用的是mysql、oracle...等.
    • 通過簡單的配置就可以輕松更換數據庫, 而不需要修改代碼.

缺點

  • 相比較直接使用SQL語句操作數據庫,有性能損失.
  • 根據對象的操作轉換成SQL語句,根據查詢的結果轉化成對象, 在映射過程中有性能損失.

關系

關系的分類

  • 一對一[1], 幾乎不用
  • 一對多( 或多對一 )
  • 多對多

SQLALchemy

  • SQLALchemy 實際上是對數據庫的抽象,讓開發者不用直接和 SQL 語句打交道,而是通過 Python 對象來操作數據庫,在舍棄一些性能開銷的同時,換來的是開發效率的較大提升
  • SQLAlchemy是一個關系型數據庫框架,它提供了高層的 ORM 和底層的原生數據庫的操作。flask-sqlalchemy 是一個簡化了 SQLAlchemy 操作的flask擴展。

一對多( 或多對一 )

01-一對多關系.png

如圖, 一個角色可以有多個用戶. 如果用戶是管理員角色, 就不能是普通用戶; 如果用戶是VIP會員, 就不能是普通用戶.

db_model.py


from datetime import datetime 
from application import db


# 設置基類方便管理公共字段
class BaseModel(object):
    id = db.Column(db.Integer, primary_key=True)  # 主鍵
    is_del = db.Column(db.Boolean, default=False)  # 默認為False,不刪除/顯示;當為True時,刪除/不顯示
    create_time = db.Column(db.DateTime, default=datetime.now)  # 記錄的創建時間
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)  # 記錄的更新時間

# 多繼承,通過列表或者元組繼承,壞處:父類屬性不提示
base_db_model = (BaseModel, db.Model)


# 角色類
class Role(db.Model):
    """角色類"""
    __tablename__ = "role"

    id = db.Column(db.Integer, primary_key=True)  # 角色id
    name_role = db.Column(db.String(32))  # 角色名稱
    
    # 關聯引用 Role關聯User
    # db.relationship("要關聯的數據庫模型類", lazy='dynamic')
    list_user = db.relationship("User", lazy='dynamic')


# 用戶表, 通過拆包base_db_model 多繼承
class User(*base_db_model):
    """用戶表"""
    __tablename__ = "user"

    name_nick = db.Column(db.String(32), nullable=False)  # 昵稱
    password_hash = db.Column(db.String(128), nullable=False)  # 密碼
    mobile = db.Column(db.String(128), unique=True, nullable=False)  # 手機號碼

    id_role = db.Column(db.Integer, db.ForeignKey("role.id"), default=2)  # 角色id


在role 指定了一個單向的關聯關系, role 關聯user, 那么怎么讓user 關聯role?如下


class User(*base_db_model):
    """用戶表"""
    __tablename__ = "user"

    name_nick = db.Column(db.String(32), nullable=False)  # 昵稱
    password_hash = db.Column(db.String(128), nullable=False)  # 密碼
    mobile = db.Column(db.String(128), unique=True, nullable=False)  # 手機號碼

    id_role = db.Column(db.Integer, db.ForeignKey("role.id"), default=2)  # 角色id

    # 添加關聯關系  user關聯role
    role = db.relationship("Role", lazy='dynamic') 


合并,將兩個單向的關聯關系合并成一個雙向的關系


# 角色類
class Role(db.Model):
    """角色類"""
    __tablename__ = "role"

    id = db.Column(db.Integer, primary_key=True)  # 角色id
    name_role = db.Column(db.String(32))  # 角色名稱
    
    # 通過relationship 指定正向引用, backref 指定反向引用 ,構成一個雙向的引用關系
    list_user = db.relationship("User", backref="role" , lazy='dynamic')


# 用戶表, 通過拆包base_db_model 多繼承
class User(*base_db_model):
    """用戶表"""
    __tablename__ = "user"

    name_nick = db.Column(db.String(32), nullable=False)  # 昵稱
    password_hash = db.Column(db.String(128), nullable=False)  # 密碼
    mobile = db.Column(db.String(128), unique=True, nullable=False)  # 手機號碼

    id_role = db.Column(db.Integer, db.ForeignKey("role.id"), default=2)  # 角色id


user 表

user表.png

role 表

role表.png

運行結果

02-運行結果.png

根據Debug結果

  • 一對多, user 表(多的一方) 定義外鍵, role 表 (一的一方) 定義關系引用.
  • list_user = db.relationship("User", backref="role" , lazy='dynamic')
    • relationship 指定正向關系引用, 在這里是role 向user 的方向, 既一對多的方向
    • backref 指定反向關系引用, 在這里是user 向role 的方向, 既多對一的方向
    • lazy 決定了什么時候SQLALchemy從數據庫中加載數據
      • lazy="subquery", 默認方式, 子查詢方式. 當加載查詢對象時, 直接加載關聯對象的數據. 如果本次查詢不需要關聯對象的數據或者關聯對象的數據數量龐大, 就造成了浪費或者加載緩慢的問題.
      • lazy="dynamic", 動態方式. 不會直接加載關聯對象的數據( users ), 只有在使用關聯對象數據的時候才會進行加載(users_).
      • lazy="dynamic", 'dynamic' loaders cannot be used with many-to-one/one-to-one relationships and/or uselist=False (不能與多對一 / 一對一關系 和/或 uselist = False一起使用)。

一個表的兩個外鍵都是同一張表的主鍵

03-兩個外鍵都是另一張表的主鍵.png

如圖, 因為用戶有角色, 當用戶(角色) 發布新聞, 經過編輯(角色) 審核. 通過審核之后, 如果出現什么問題, 責任人是編輯, 而不是用戶. 如果有以上的需求就出現了news 表的id_author 和id_charge_editor 字段的外鍵都是user 表的主鍵. 數據庫是允許這樣設計的, 那么SQLAlchemy 中怎樣表示這樣的關系?


# 一個用戶可以發布多篇新聞
list_news = db.relationship("News", foreign_keys=[News.id_author], backref="author", lazy="dynamic")

# 一個編輯可以編輯多篇新聞
list_edit_news = db.relationship("News", foreign_keys=[News.id_charge_editor], backref="editor", lazy="dynamic")


和一個表中只有一個外鍵是另一張表中的主鍵類似, relationship 指定關系引用, backref 指定反向的關系引用. 區別在于foreign_keys, foreign_keys 指定關系引用作用的于哪個外鍵, 或者說關系引用作用于主鍵與哪個外鍵之間的關聯關系.

多對多

04-多對多關系.png

如圖, 用戶和評論之間是一個多對多的關系, 一個用戶可以給多條評論點贊, 一條評論可以被多個用戶點贊. 從圖上可以很容易的看出, 多對多是由兩個一對多構成的, 并且有一張表存儲著是多對多的關系.

數據庫模型


# 評論點贊
class CommentPraise(*base_db_model):
    """評論點贊"""

    __tablename__ = "comment_praise"

    id_comment = db.Column(db.Integer, db.ForeignKey("comment.id"))  # 評論id
    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id

# 評論表
class Comment(*base_db_model):
    """評論表"""

    __tablename__ = "comment"
    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id
    id_news = db.Column(db.Integer, db.ForeignKey("news.id"))  # 新聞id
    id_parent = db.Column(db.Integer, db.ForeignKey("comment.id"))  # 父評論id

    content = db.Column(db.Text, nullable=False)  # 評論內容
    praise_num = db.Column(db.Integer, default=0)  # 點贊數(喜歡數)
 

# 用戶表
class User(*base_db_model):
    """用戶表"""
    __tablename__ = "user"

    name_nick = db.Column(db.String(32), nullable=False)  # 昵稱
    password_hash = db.Column(db.String(128), nullable=False)  # 密碼
    mobile = db.Column(db.String(128), unique=True, nullable=False)  # 手機號碼

    id_role = db.Column(db.Integer, db.ForeignKey("role.id"), default=2)  # 角色id
    last_login = db.Column(db.String(32), default=datetime.now)  # 最后登錄時間
    is_login = db.Column(db.Boolean, default=False)  # 是否登錄
    gender = db.Column(db.Enum("man", "woman"), default="man")

    last_login_ip = db.Column(db.String(64))  # 最后登錄ip
    avatar_url = db.Column(db.String(256))  # 頭像
    signature = db.Column(db.String(512))  # 個性簽名
   
    # 當前用戶發布的所有評論
    list_user_comment = db.relationship("Comment", backref="user", lazy="dynamic")

    # 當前用戶點贊的所有評論
    list_user_praise_comment = db.relationship("Comment", secondary="comment_praise",
                                               # 當前評論點贊的所有用戶
                                               backref=db.backref('list_praise_comment_user', lazy='dynamic'),
                                               lazy="dynamic")
 

如上數據庫模型, 在user 類中建立與Comment 的一對多關系引用.
對于多對多的關系

  • 將關聯關系放到第三張表中( comment_praise )
  • 在user 或者 comment 中通過relationship 指定關聯關系,
  • 通過secondary 指定關聯關系存放的地方

另一種寫法


# 通過Table 類得到Table 類的對象association_table 
association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    # secondary = association_table Table類的對象
                    secondary=association_table,
                    backref="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)


secondary 常見方式是使用Table 類的對象作為值, 也可以使用表名字符串作為值.

暫時未找到通過Table 作為數據庫模型怎樣繼承基類的方式, 所以暫時算作是一種缺陷. 使用繼承db.Model 的方式可以方便繼承.

復雜的多對多

05-復雜的多對多.png

如圖,新聞類型和新聞分類是一個多對多關系, 一個類型可以有多個分類的新聞, 一個分類可以有多個類型的新聞. 剛剛建立這個關系的時候一臉蒙圈, 關系存放在哪里? 單獨摘出這一部分瞬間明白了, news 本身就是一個表, 當然可以作為type 與category 的關聯關系表了, 不需要額外創建第四張表.


# 新聞類型
class NewsType(db.Model):
    """新聞類型"""
    __tablename__ = "news_type"

    id = db.Column(db.Integer, primary_key=True)  # 新聞類型id
    name_type = db.Column(db.String(32))  # 新聞類型

    list_news = db.relationship("News", backref="news_type", lazy='dynamic')

    # 當前新聞類型所屬于的所有分類
    list_news_category = db.relationship('NewsCategory', secondary="news", 
                                         backref=db.backref('list_news_type', lazy='dynamic'),
                                         lazy='dynamic')


# 新聞分類
class NewsCategory(db.Model):
    """新聞分類"""

    __tablename__ = "news_category"

    id = db.Column(db.Integer, primary_key=True)  # 新聞類型id
    name_type = db.Column(db.String(32))  # 新聞類型

    list_news = db.relationship("News", backref="news_category", lazy='dynamic')


按照多對多的規則, 在NewsType 或者 NewsCategory 定義一個雙向的關系引用. 這里在NewsType 定義了關系引用, 關聯關系存放在News 表中.

自關聯

一對多( 多對一 )

06-自關聯一對多.png

一個父評論可以有多個子評論, 評論表的一對多自關聯


# 評論表
class Comment(*base_db_model):
    """評論表"""

    __tablename__ = "comment"
    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id
    id_news = db.Column(db.Integer, db.ForeignKey("news.id"))  # 新聞id
    id_parent = db.Column(db.Integer, db.ForeignKey("comment.id"))  # 父評論id

    content = db.Column(db.Text, nullable=False)  # 評論內容
    praise_num = db.Column(db.Integer, default=0)  # 點贊數(喜歡數)

    parent = db.relationship("Comment", remote_side="comment.c.id",
                             backref=db.backref('childs', lazy='dynamic'))



自關聯一對多, 其本質也是一對多, 只不過兩端都是一張表, 所以和一對多基本類似.

  • 在一的一方定義關系, 在多的一方定義外鍵, 兩端都是一張表, 所以外鍵 和 關聯關系在同一個數據庫模型類里.
  • 在類中定義外鍵 id_parent = db.Column(db.Integer, db.ForeignKey("comment.id")) # 父評論id
  • 在類中定義關系 parent = db.relationship("Comment", remote_side="comment.c.id",backref=
    db.backref('childs', lazy='dynamic'))
  • 通過remote_side 指定遠端主鍵
    • remote_side="comment.c.id" 等價 remote_side=[id]

多對多

07-自關聯多對多.png

如圖, 一個用戶可以有多個粉絲, 一個用戶可以被多個人關注, 用戶表的多對多自關聯


# 用戶粉絲表
class FollowsUser(*base_db_model):
    """用戶粉絲表"""

    __tablename__ = "user_follows"

    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id
    id_follower = db.Column(db.Integer, db.ForeignKey("user.id"))  # 新聞id

# 用戶表
class User(*base_db_model):
    """用戶表"""
    __tablename__ = "user"

    name_nick = db.Column(db.String(32), nullable=False)  # 昵稱
    password_hash = db.Column(db.String(128), nullable=False)  # 密碼
    mobile = db.Column(db.String(128), unique=True, nullable=False)  # 手機號碼

    id_role = db.Column(db.Integer, db.ForeignKey("role.id"), default=2)  # 角色id
    last_login = db.Column(db.String(32), default=datetime.now)  # 最后登錄時間
    is_login = db.Column(db.Boolean, default=False)  # 是否登錄
    gender = db.Column(db.Enum("man", "woman"), default="man")

    last_login_ip = db.Column(db.String(64))  # 最后登錄ip
    avatar_url = db.Column(db.String(256))  # 頭像
    signature = db.Column(db.String(512))  # 個性簽名

    followers = db.relationship('User',
                                secondary="user_follows",
                                primaryjoin="user.c.id == user_follows.c.id_user",
                                secondaryjoin="user.c.id == user_follows.c.id_follower",
                                backref=db.backref('user', lazy='dynamic'),
                                lazy='dynamic')


自關聯多對多, 其本質也是多對多, 只不過兩端都是一張表, 所以和多對多基本類似, 同時多對多是兩個一對多組成的, 并且是自關聯, 所以類似于兩個自關聯一對多.

  • 需要第三張表存儲關聯關系 user_follows
  • 兩端都是自己需要進行區分, 在自關聯多對多中使用primaryjoin 和/或 secondaryjoin 進行區分.
    • primaryjoin 一個SQL表達式,它將用作此子對象與父對象的主要連接,或者在多對多關系中,主對象與關聯表的連接. 默認情況下,此值基于父表和子表(或關聯表)的外鍵關系計算。
    • secondaryjoin 一個SQL表達式,將用作關聯表與子對象的連接。默認情況下,此值基于關聯和子表的外鍵關系計算。

關聯關系模板

多表之間

一對多


class Role(db.Model):
    """角色表"""
    __tablename__ = 'roles'

    id = db.Column(db.Integer, primary_key=True) 
    users = db.relationship('User', backref='role', lazy='dynamic')

class User(db.Model):
    """用戶表"""
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True) 


多對多

通過Table 類


tb_student_course = db.Table('tb_student_course',
                             db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
                             db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
                             )

class Student(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer, primary_key=True) 

    courses = db.relationship('Course', secondary=tb_student_course,
                              backref=db.backref('students', lazy='dynamic'),
                              lazy='dynamic')

class Course(db.Model):
    __tablename__ = "courses"
    id = db.Column(db.Integer, primary_key=True) 

**通過Model **


# 評論點贊  db SQLAlchemy的實例對象
class CommentPraise(db.Model):
    """評論點贊"""

    __tablename__ = "comment_praise"

    id_comment = db.Column(db.Integer, db.ForeignKey("comment.id"))  # 評論id
    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id

# 評論表
class Comment(db.Model):
    """評論表"""

    __tablename__ = "comment"
    
    id = db.Column(db.Integer, primary_key=True)  
 

# 用戶表
class User(db.Model):
    """用戶表"""
    __tablename__ = "user"

    id = db.Column(db.Integer, primary_key=True)  

    # 當前用戶點贊的所有評論
    list_user_praise_comment = db.relationship("Comment", secondary="comment_praise",
                                               # 當前評論點贊的所有用戶
                                               backref=db.backref('list_praise_comment_user', lazy='dynamic'),
                                               lazy="dynamic")
 

自關聯

一對多


class Comment(db.Model):
    """評論"""
    __tablename__ = "comments"

    id = db.Column(db.Integer, primary_key=True) 
     
    parent_id = db.Column(db.Integer, db.ForeignKey("comments.id"))
 
    parent = db.relationship("Comment", remote_side=[id],
                             backref=db.backref('childs', lazy='dynamic'))


多對多

通過Table


tb_user_follows = db.Table(
    "tb_user_follows",
    db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True),  # 粉絲id
    db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True)  # 被關注人的id
)

class User(db.Model):
    """用戶表"""
    __tablename__ = "info_user"

    id = db.Column(db.Integer, primary_key=True)   
    
    # 用戶所有的粉絲,添加了反向引用followed,代表用戶都關注了哪些人
    followers = db.relationship('User',
                                secondary=tb_user_follows,
                                primaryjoin=id == tb_user_follows.c.followed_id,
                                secondaryjoin=id == tb_user_follows.c.follower_id,
                                backref=db.backref('followed', lazy='dynamic'),
                                lazy='dynamic')


通過Model



# 用戶粉絲表
class FollowsUser(db.Model):
    """用戶粉絲表"""

    __tablename__ = "user_follows"

    id_user = db.Column(db.Integer, db.ForeignKey("user.id"))  # 用戶id
    id_follower = db.Column(db.Integer, db.ForeignKey("user.id"))  # 新聞id

# 用戶表
class User(db.Model):
    """用戶表"""
    __tablename__ = "user"
 
    id = db.Column(db.Integer, primary_key=True)    

    followers = db.relationship('User', secondary="user_follows",
                                primaryjoin="user.c.id == user_follows.c.id_user",
                                secondaryjoin="user.c.id == user_follows.c.id_follower",
                                backref=db.backref('user', lazy='dynamic'),
                                lazy='dynamic')



到此結? DragonFangQy 2018.7.12


  1. 一對一 ?

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容