SQLAlchemy 101 -如何使用炼金术操作你的SQL

SQLAlchemy 介绍

经常编程的朋友应该知道,一般在开发软件,都会使用到数据库,就是我们常说的RDBMS。如果涉及到数据库,那么必不可少的就会使用SQL来操作数据库,但是大多数程序员朋友对于SQL这种DSL,并不太喜欢,那么除了SQL,我们可以通过编程语言中开发出的对象关系映射(ORM)来完成对于数据库的操作。比较有名的框架,包括Java里面的JPA, django框架里面自带的ORM等等,今天我们要介绍的主角,是Python世界里面的另外一个ORM框架-SQLAlchemy。

SQL Alchemy 是当前Python世界里最受欢迎的一款ORM框架,那么最主要的功能,一定是将我们的程序代码里的模型,映射到数据里的表和字段,相当把程序里的对象,映射到了数据库里的关系,就是对象关系映射。

架构图

图一 整体架构

其中虚线部分就是SQLAlchemy的整个结构,包括了ORM和Core两个模块,Core模块是和ORM独立开的,主要功能是通过对象构建SQL表达式,并且可以在目标数据库在一个事务里执行,并获得结果集。而ORM就是将我们的Python语言中的对象转换成相关表的模块。那么我们就来看看使用SQLAlchemy来操作数据库吧,但是操作之前,我们先了解几个基本概念。

Engine(引擎)

在进行和任意数据库进行交互前,SQLAlchemy需要建立一个Engine作为连接的中心来源,并且在其中配制好连接池的信息。通过架构图可得知,这个Engine需要和底层具体的连接池和方言关联,并且使用DBAPI作为底层实现。DBAPI实际上是一个数据库连接的接口,不同的数据库可以有不同的实现,来完成数据库的相关操作。一般来说创建引擎,就是指定一个连接协议,并且配置好相关的连接池。你可以认为Engine就是数据库的一个配置,示例如下:

python 复制代码
from sqlalchemy import create_engine
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

Connection(连接)

为了使程序能够和数据库对话,我们必须得创建一个Connection(连接),很自然的,我们可以使用一个Engine来创建Connection,由于是Connection是一个开放的资源,所以我们在使用的时候,必须使用受限的语法,一般可以使用Python的上下文管理器来进行创建。如下是一个简单示例

python 复制代码
from sqlalchemy import text
 
with engine.connect() as conn:

    result = conn.execute(text("select 'hello world'"))

    print(result.all())

使用如上的命令,实际上在内部,是使用DBAPI创建了一个事务,并且执行了相关的SQL语句,但是这个事务,是没有提交的,在执行完后,会默认自动的回滚。如果需要在执行完提交,需要显式的在最后写一个conn.commit(),来完成这个事务。

Result (结果)

当我们使用SQLAlchemy来进行查询操作后,我们就会得到一个Result对象,例如如下代码:

python 复制代码
with engine.connect() as conn:

    result = conn.execute(text("SELECT x, y FROM some_table"))

    for row in result:

        print(f"x: {row.x}  y: {row.y}")

  


x: 1  y: 1

x: 2  y: 4

x: 6  y: 8

x: 9  y: 10

ROLLBACK

这个result 就是一个Result对象, 对于它一般支持以下几种操作。

  1. 把他赋值给一个Tuple

  2. index来找到第几列

  3. 使用列名来找到对应的列

  4. 使用映射来访问

python 复制代码
# assign to tuple

result = conn.execute(text("select x, y from some_table"))

  


for x, y in result:

    ...

  


# Integer index

result = conn.execute(text("select x, y from some_table"))

  


for row in result:

    x = row[0]

# Integer index

result = conn.execute(text("select x, y from some_table"))

for row in result:

    y = row.y


    # illustrate use with Python f-strings

    print(f"Row: {row.x} {y}")

  
# Mapping access

result = conn.execute(text("select x, y from some_table"))


for dict_row in result.mappings():

    x = dict_row["x"]

    y = dict_row["y"]

Metadata(元数据)

Metadata是指保存一组Table对象的容器, 并且他可以保存和Engine或者Connection的关系。具体是如何使用的呢,首先我们会构造一个Metadata对象, 然后在创建我们的Table对象

python 复制代码
from sqlalchemy import MetaData

metadata_obj = MetaData()

  


from sqlalchemy import Table, Column, Integer, String

user_table = Table(

    "user_account",

    metadata_obj,

    Column("id", Integer, primary_key=True),

    Column("name", String(30)),

    Column("fullname", String),

)

可以看到,我们在创建表的时候,是要指定将相关的元数据加到了指定的metadata对象上面。以上是使用命令方式来创建表的,除此之外,我们还可以使用ORM来进行表的创建。而Table对象,就是和数据库的表对应,可以看到对应的字段名:

python 复制代码
user_table.c.name

user_table.c.keys()

还可以查看和指定约束:

python 复制代码
# find the primary key

user_table.primary_key

  


# add Foreign Key to table

from sqlalchemy import ForeignKey

address_table = Table(

    "address",

    metadata_obj,

    Column("id", Integer, primary_key=True),

    Column("user_id", ForeignKey("user_account.id"), nullable=False),

    Column("email_address", String, nullable=False),

)

当我们把表都信息都放入metadata对象中,加上数据库的连接信息,相当于整个数据库的结构,通过metadata都已经保存下来了,所以我们可以通过metadata对象来生成我们的DDL了。

python 复制代码
metadata_obj.create_all(engine)

Metadata不仅能够使用命令方式创建,还可以使用声明的方式创建。首先我们需要使用一个Base类,它需要继承与DeclarativeBase, 可以看到在Base对象里面带有一个metadata对象

python 复制代码
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):

    pass

Base.metadata

有了这个Base对象,我们就可以通过声明式的方式来创建表了。

python 复制代码
from typing import List

from typing import Optional

from sqlalchemy.orm import Mapped

from sqlalchemy.orm import mapped_column

from sqlalchemy.orm import relationship

  


class User(Base):

    __tablename__ = "user_account"

    id: Mapped[int] = mapped_column(primary_key=True)

    name: Mapped[str] = mapped_column(String(30))

    fullname: Mapped[Optional[str]]

    addresses: Mapped[List["Address"]] = relationship(back_populates="user")

    def __repr__(self) -> str:

        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

  


class Address(Base):

    __tablename__ = "address"

    id: Mapped[int] = mapped_column(primary_key=True)

    email_address: Mapped[str]

    user_id = mapped_column(ForeignKey("user_account.id"))

    user: Mapped[User] = relationship(back_populates="addresses")

    def __repr__(self) -> str:

        return f"Address(id={self.id!r}, email_address={self.email_address!r})"

  

当创建完这几个类后,我们会将相关信息,加入到DelarativeBase的__table__ 对象中, 我们使用Mapped类型,来表示一个列,而在后面使用mapped_column()方法来映射数据库的列。而我们创建的Base类里面,也是包含metadata的,所以创建表的DDL,也可以通过以下的方法来生成。

python 复制代码
Base.metadata.create_all(engine)

动态创建

SQLAlchemy还有一个厉害的地方,是我们可以不需要定义metadata,也就是不知道表的定义,而是通过读取当前数据库的状态,来自动生成表的模型,这个特性叫做table reflection(表反射),一般是使用如下的方式:

some_table = Table("some_table", metadata_obj, autoload_with=engine)

其中some_table是数据库中的表名,SQLAlchemy会去数据库中搜索相关的表名, metadata_obj是你的表将要放入的metadata对象,engine就是你的数据库Engine对象。

上面就是如何使用SQLAlchemy来和数据库建立连接,操作数据库,以及ORM的基本概念,如果你对SQLAlchemy感兴趣, 后续的文章中我会展开更多细节,带你继续了解这个强大的工具。

参考文档: SQLAlchemy 官方文档

相关推荐
费弗里2 小时前
Python全栈应用开发利器Dash 3.x新版本介绍(1)
python·dash
李少兄9 天前
解决OSS存储桶未创建导致的XML错误
xml·开发语言·python
就叫飞六吧9 天前
基于keepalived、vip实现高可用nginx (centos)
python·nginx·centos
Vertira9 天前
PyTorch中的permute, transpose, view, reshape和flatten函数详解(已解决)
人工智能·pytorch·python
学Linux的语莫9 天前
python基础语法
开发语言·python
匿名的魔术师9 天前
实验问题记录:PyTorch Tensor 也会出现 a = b 赋值后,修改 a 会影响 b 的情况
人工智能·pytorch·python
Ven%9 天前
PyTorch 张量(Tensors)全面指南:从基础到实战
人工智能·pytorch·python
mahuifa9 天前
PySide环境配置及工具使用
python·qt·环境配置·开发经验·pyside
大熊猫侯佩9 天前
ruby、Python 以及 Swift 语言关于 “Finally” 实现的趣谈
python·ruby·swift
19899 天前
【Dify精讲】第19章:开源贡献指南
运维·人工智能·python·架构·flask·开源·devops