惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
博客园_首页
W
WeLiveSecurity
S
Secure Thoughts
S
Security @ Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hugging Face - Blog
Hugging Face - Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Hacker News: Front Page
Project Zero
Project Zero
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
U
Unit 42
N
News and Events Feed by Topic
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Forbes - Security
Forbes - Security
T
Tor Project blog
I
Intezer
B
Blog
F
Full Disclosure
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Fortinet All Blogs
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
AI
AI
Google DeepMind News
Google DeepMind News
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
L
Lohrmann on Cybersecurity
WordPress大学
WordPress大学
博客园 - 聂微东
雷峰网
雷峰网
P
Privacy International News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
PCI Perspectives
PCI Perspectives
Y
Y Combinator Blog
Spread Privacy
Spread Privacy
Simon Willison's Weblog
Simon Willison's Weblog
罗磊的独立博客
Vercel News
Vercel News
A
Arctic Wolf
The Register - Security
The Register - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
H
Heimdal Security Blog
Know Your Adversary
Know Your Adversary
P
Proofpoint News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed

ORM - 标签 - This Cute World

SQLAlchemy 学习笔记(三):ORM 中的关系构建 SQLAlchemy 学习笔记(一):Engine 与 SQL 表达式语言
SQLAlchemy 学习笔记(二):ORM 基础
於清樂 · 2019-02-11 · via ORM - 标签 - This Cute World

照例先看层次图

SQLAlchemy 层次结构

使用 ORM 时,我们首先需要定义要操作的表(通过 Table),然后再定义该表对应的 Python class,并声明两者之间的映射关系(通过 Mapper)。

方便起见,SQLAlchemy 提供了 Declarative 系统来一次完成上述三个步骤,Declarative 系统提供 base class,这个 base class 会为继承了它的 Python class(可称作 model)创建 Table,并维护两者的映射关系。

from sqlalchemy.ext.declarative import declarative_base
from SQLAlchemy import Column, Integer, String

Base = declarative_base()  # 拿到 Base 类


class User(Base):
    id = Column(Integer, primary_key=True)
    username = Column(String(32), nullable=False, index=True)  # 添加 index 提升搜索效率
    fullname = Column(String(64))
    password = Column(String(32))  # 真实情况下一般只存 hash

    def __repr__(self):
        return f"<User {self.username}>"

这样就声明好了一个对象-关系映射,上一篇文章说过所有的 Table 都在某个 MetaData 中,可以通过Base.metadata 获取它。

Base.metadata.create_all(engine)  # 通过 metadata 创建表(或者说生成模式 schema)

engine 的创建请见上篇文档SQLAlchemy 学习笔记(一):Engine 与 SQL 表达式语言

可参考 SQL 基础笔记(三):约束SQLAlchemy 学习笔记(一):Engine 与 SQL 表达式语言 - 表定义中的约束

使用 ORM 来定义约束条件,与直接使用 SQL 表达式语言定义很类似,也有两种方法:

  1. 直接将约束条件作为 ColumnForeignKey 的参数传入。这种方式最简洁,也最常用。
  2. 使用 UniqueConstraintCheckConstraint 等类构造约束,然后放入 __table_args__ 属性中。举例:
class User(Base):
    id = Column(Integer, primary_key=True)
    username = Column(String(32), nullable=False, index=True)  # 添加 index 提升搜索效率
    fullname = Column(String(64))
    password = Column(String(32))  # 真实情况下一般只存 hash

    # 顾名思义,这是 `Table` 类的参数的序列。里面的约束条件会被用于构建 __table__
    __table_args__ = (UniqueConstraint('username', name='c_user'),)  # username 的唯一性约束

    def __repr__(self):
        return f"<User {self.username}>"

上一节讲 engine 时,我们是通过 connection 来与数据库交互,而在 ORM 中我们使用 Session 访问数据库。

from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)  # 获取 session

直接使用 SQL 表达式语言时,我们使用 insert()、select()、update()、delete() 四个函数构造 SQL,使用 where() 添加条件,使用 model.join(another_model) 进行 join 操作。而使用 ORM 时, 数据库操作不再与 SQL 直接对应。我们现在是通过操作 Python 对象来操作数据库了。

现在,我们通过 db.session.add()、db.session.delete() 进行添加与删除,使用 db.session.query(Model) 进行查询,通过 filter 和 filter_by 添加过滤条件。而修改,则是先查询出对应的 row 对象,直接修改这个对象,然后 commit 就行。

  1. 增添:
ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')  # 用构造器构造对象
session.add(ed_user)  # 添加,此外还有批量添加 add_all([user1, user2...])
session.commit()  # 必须手动 commit
  1. 修改:
ed_user = session.query(User).filter_by(name='ed').first()  # 先获取到 User 对象
ed_user.password = 'f8s7ccs'  # 改了密码
session.commit()  # 提交

# 批量修改
session.query(User).filter(User.home=='shanghai') \
    .update({User.login_num:0})  # 将所有上海的用户的 login_num 设为 0
session.commit()
  1. 删除:
ed_user = session.query(User).filter_by(name='ed').first()  # 先获取到 User 对象
session.delete(ed_user)  # 直接删除(session 知道 ed_user 属于哪个表)
session.commit()  # 提交

# 批量删除
session.query(User).filter(User.home=='shanghai') \
    .delete()  # 删除所有上海的用户

session.commit()

同样的,也可以在外面检查异常,然后调用 session.rollback() 实现失败回滚。

  1. filter_by:使用关键字参数进行过滤,前面的演示中已经用过多次了。
  2. filter:它对应 SQL 表达式语言中的 where,支持各种复杂的 SQL 语法。
  3. group_by: 通过指定 column 分组
  4. distinct(): 去重
  5. join(): 关联
query.filter(User.name == 'ed')  # 这个等同于 filter_by,但是更繁琐
query.filter(User.name != 'ed')  # 不等于,这个就是 filter_by 无法做到的了
query.filter(User.name.like('%ed%'))  # SQL like 的 like 语法
query.filter(User.name.in_(['ed', 'wendy', 'jack']))  # 包含

# 查询还可以嵌套
query.filter(User.name.in_(
    session.query(User.name).filter(User.name.like('%ed%'))
))

query.filter(~User.name.in_(['ed', 'wendy', 'jack']))  # 不包含
query.filter(User.name == None)  # NULL 对应 Python 的 None

from sqlalchemy import or_, and_, in_
query.filter(or_(User.name == 'ed', User.name == 'wendy'))  # OR 语法

query.group_by(User.name)  # 分组
query.distinct()  # 去重

from sqlalchemy import func  # SQL 函数包
session.query(func.count(User.name)).filter_by(xxx=xxx)  # 使用 count 函数

# join 关联
# 默认使用内联(inner),即只取两表的交集
session.query(User, Address).filter(User.id==Address.user_id)  # 方法一

session.query(User).join(Address).\  # 方法二
    filter(Address.email_address=='[email protected]')

# 外联 outer join,将另一表的列联结到主表,没有的行为 NULL
session.query(User).outerjoin(User.addresses) \
    .filter(Address.email_address=='[email protected]')

查询返回 query 对象,但 SQL 还没有被执行,直到你调用下列几个方法:

# 构造 query 对象
query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)

# 1. all 返回所有结果的列表
res_list = query.all()

# 2. first 先在 SQL 中加入限制 `limit 1`,然后执行。
res = query.first()

# 3. one 执行 sql 并获取所有结果
# 如果结果不止一行,抛出 MultipleResultsFound Error!!!
# 如果结果为空,抛出 NoResultFound Error !!!
res = query.one()

4. one_or_none 差别在于结果为空它不抛出异常而是返回 None
res = query.one_or_none()