ORM
ORM 全拼 Object-Relation Mapping. 中文意為 對象-關系映射. 主要實現模型對象到關系數據庫數據的映射. 和Java中的JDBC 有異曲同工之處
優點
- 通過改變數據庫模型改變表結構
- 通過模型類進行數據庫的增刪改查操作.
- 只需要面向對象編程, 不需要面向數據庫編寫代碼.
- 對數據庫的操作都轉化成對類屬性和方法的操作.
- 不用編寫各種數據庫的sql語句.
- 實現了數據模型與數據庫的解耦, 屏蔽了不同數據庫操作上的差異.
- 不在關注用的是mysql、oracle...等.
- 通過簡單的配置就可以輕松更換數據庫, 而不需要修改代碼.
缺點
- 相比較直接使用SQL語句操作數據庫,有性能損失.
- 根據對象的操作轉換成SQL語句,根據查詢的結果轉化成對象, 在映射過程中有性能損失.
關系
關系的分類
- 一對一[1], 幾乎不用
- 一對多( 或多對一 )
- 多對多
SQLALchemy
- SQLALchemy 實際上是對數據庫的抽象,讓開發者不用直接和 SQL 語句打交道,而是通過 Python 對象來操作數據庫,在舍棄一些性能開銷的同時,換來的是開發效率的較大提升
- SQLAlchemy是一個關系型數據庫框架,它提供了高層的 ORM 和底層的原生數據庫的操作。flask-sqlalchemy 是一個簡化了 SQLAlchemy 操作的flask擴展。
一對多( 或多對一 )
如圖, 一個角色可以有多個用戶. 如果用戶是管理員角色, 就不能是普通用戶; 如果用戶是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 表
role 表
運行結果
根據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一起使用)。
一個表的兩個外鍵都是同一張表的主鍵
如圖, 因為用戶有角色, 當用戶(角色) 發布新聞, 經過編輯(角色) 審核. 通過審核之后, 如果出現什么問題, 責任人是編輯, 而不是用戶. 如果有以上的需求就出現了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 指定關系引用作用的于哪個外鍵, 或者說關系引用作用于主鍵與哪個外鍵之間的關聯關系.
多對多
如圖, 用戶和評論之間是一個多對多的關系, 一個用戶可以給多條評論點贊, 一條評論可以被多個用戶點贊. 從圖上可以很容易的看出, 多對多是由兩個一對多構成的, 并且有一張表存儲著是多對多的關系.
數據庫模型
# 評論點贊
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 的方式可以方便繼承.
復雜的多對多
如圖,新聞類型和新聞分類是一個多對多關系, 一個類型可以有多個分類的新聞, 一個分類可以有多個類型的新聞. 剛剛建立這個關系的時候一臉蒙圈, 關系存放在哪里? 單獨摘出這一部分瞬間明白了, 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 表中.
自關聯
一對多( 多對一 )
一個父評論可以有多個子評論, 評論表的一對多自關聯
# 評論表
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]
多對多
如圖, 一個用戶可以有多個粉絲, 一個用戶可以被多個人關注, 用戶表的多對多自關聯
# 用戶粉絲表
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