掘金 后端 ( ) • 2024-03-22 15:23

SQLAlchemy 介绍

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

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

架构图

image.png

图一  整体架构

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

Engine(引擎)

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

from sqlalchemy import create_engine
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

Connection(连接)

为了使程序能够和数据库对话,我们必须得创建一个Connection(连接),很自然的,我们可以使用一个Engine来创建Connection,由于是Connection是一个开放的资源,所以我们在使用的时候,必须使用受限的语法,一般可以使用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对象,例如如下代码:

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. 使用映射来访问


# 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对象


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对象,就是和数据库的表对应,可以看到对应的字段名:


user_table.c.name

user_table.c.keys()

还可以查看和指定约束:


# 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了。

metadata_obj.create_all(engine)

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

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):

    pass

Base.metadata

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


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,也可以通过以下的方法来生成。

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 官方文档