08. ORM——快速开始

一. 什么是ORM?

ORM(Object-Relational Mapping,对象关系映射)是一种用于操作数据库的编程技术,用来在面向对象编程语言关系型数据库之间建立映射关系。

通过 ORM,开发者可以使用 Python 对象的方式操作数据库,而无需直接编写 SQL 语句。

举个例子,如果现在要创建一个名为 book 的表,可以直接在 python 代码中这么写:

python 复制代码
class Book(Base):
__tablename__ = "book"

相当于 SQL 语句:

sql 复制代码
CREATE TABLE book (...);

**使用 ORM 的优势:**减少重复 SQL 代码;代码更简洁、可读性更高;提高开发效率;自动管理数据库连接和事务;使用参数化机制,降低 SQL 注入风险。

Python 中常见的 ORM 工具 有:SQLAlchemy ORM(最常用、最灵活)、Django ORM(适合 Django 框架)和 Tortoise ORM(异步 ORM)。

后续内容使用的是:SQLAlchemy ORM(社区完善、功能强大、应用最广)

二、快速开始

1、安装ORM

可以直接使用以下命令在 terminal 里面安装:

bash 复制代码
pip install sqlalchemy[asyncio] aiomysql

2、创建数据库

在MySQL中创建数据库(MySQL 语句/关键字不区分大小写)。首先保证自己已经安装了MySQL并在环境变量里添加了相关路径。打开 cmd,在命令行中输入:

bash 复制代码
mysql -u root -p

输入密码后进入 MySQL。使用以下命令创建数据库:

sql 复制代码
CREATE DATABASE 数据库名;

使用以下命令查看数据库是否创建成功:

sql 复制代码
show databases;

3、创建数据库引擎

数据库引擎所需的 URL 是根据实际使用的数据库、用户名密码、IP地址、端口和数据库名拼写出来的,数据库 URL 的拼写规则为:

python 复制代码
mysql+aiomysql://用户名:密码@IP地址:端口/数据库名

MySQL数据库url地址写法be like:

python 复制代码
DATABASE_URL = "mysql+aiomysql://用户名:密码@数据库IP:3306/数据库名"

PostgreSQLurl地址写法be like:

python 复制代码
DATABASE_URL = "postgresql+asyncpg://user:password@127.0.0.1:5432/dbname"

SQLite数据库url地址写法be like:

python 复制代码
DATABASE_URL = "sqlite+aiosqlite:///./test.db"

实际代码:

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine

# 创建异步引擎
DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/fastapi01?charset=utf8mb4"
async_engine = create_async_engine(
    DATABASE_URL,      # 数据库URL
    echo=True,         # 输出SQL日志
    pool_size=10,      # 连接池中长期保留的连接数量,相当于一直待命的连接
    max_overflow=20    # 连接池临时多开的连接数量,用完会释放
)

4、创建表

4.1、定义基类(来定义是啥表?有啥字段?是啥类型?)

先有基类,基类放通用的属性和字段(放所有表都需要的属性/字段)。

python 复制代码
import datetime
from sqlalchemy import DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

# 创建表------定义基类
class Base(DeclarativeBase):
    create_time: Mapped[datetime.datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now,            # 注意这里只传递函数
        comment="创建时间",
    )
    update_time: Mapped[datetime.datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now,            # 注意这里只传递函数
        onupdate=func.now(),
        comment="修改时间",
    )

DeclarativeBase 让类具备 被 ORM 映射为数据库表的能力。而在 Base 中定义了两个字段:create_time 和 update_time,这两个字段相当于两个默认字段,凡是 Base 的子类都会具备这两个字段。以 create_time 为例,通过结构:

python 复制代码
字段名:Mapped[python层面的数据类型] = mapped_column(
    数据库层面的数据类型,
    insert_default = 数据库层面要填入的默认值,
    default = python层面的默认值,
    comment = "对这个字段的说明"
    )

来定义一个字段,可以看到其实 ORM 像是一个"翻译",它提供了 Mapped 类型标注和 mapped_column() 函数,既负责 python 层面的注释,也负责数据库层面的注释,相当于同时告诉 python 和数据库这个字段是什么类型的,它的默认值是什么。

但是为什么要同时在 python 层面和数据库层面同时进行注释呢?这是因为如果只在 Python 层定义 default,那么通过 ORM 插入数据是正常的,但如果直接使用 SQL 操作数据库,默认值不会生效,可能导致数据为空;如果只在数据库层定义 insert_default,那么无论 ORM 还是 SQL 插入,数据库中的数据都是完整的,但在 ORM 操作过程中,Python 对象在提交前可能无法获取该字段的值。

而 sqlalchemy 提供了 DateTime 和 func 等数据库层面的数据类型和操作函数。值得注意的一点是:在 default 中只传递了函数 func.now,而非函数运行的结果 func.now(),这是因为如果写成 default = func.now() 的话,default 的值就被固定为程序启动/加载时的时间,所以,以后不管新增多少数据,create_time 或者 update_time 都是是同一个固定值。

4.2、定义模型类(每一个具体表格都是一个模型类)

模型类就是要定义的具体表格(每一个具体表格都是一个模型类)。比如现在创建一个名为book的表格:

python 复制代码
from sqlalchemy import String, Float

# 创建表------定义模型类
class Book(Base):
    __tablename__ = 'book'

    id: Mapped[int] = mapped_column(primary_key=True, comment='书籍id')
    bookname: Mapped[str] = mapped_column(String(255), comment='书名')
    author: Mapped[str] = mapped_column(String(255), comment='作者')
    price: Mapped[float] = mapped_column(Float, comment='价格')
    publisher: Mapped[String] = mapped_column(String(255), comment='出版社')

由上述代码可知,模型类的定义跟基类定义的格式非常相似。在模型类定义中,表名用 tablename 字段定义,参数 primary_key 可设置指定字段为主键,String 是 sqlalchemy 提供的数据库层面的数据类型,String(255) 意味着数据库字段类型是字符串(VARCHAR),最大长度为 255 个字符。

5、启动时自动建表

当所需要的表格已经定义好,最后需要通过 lifespan 机制,在 FastAPI 启动时自动执行建表函数,确保数据库表在接口使用前已经准备好。代码如下:

python 复制代码
# 启动时自动建表

# 定义建表函数
# 注意Base.metadata.create_all后面没有小括号
async def create_tables():                               
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)    

# 定义FastAPI启动时异步执行建表函数create_tables()
@asynccontextmanager
async def lifespan(app: FastAPI):
    await create_tables()
    yield

# 将上面自定义的lifespan函数传递给FastAPI的lifespan函数(FastAPI有一个自己的lifespan函数)
app = FastAPI(lifespan=lifespan)                         

点击run查看有无报错,运行成功会出现如下提示:

如果没报错可以看看 pycharm 里面的 database 里的表格情况:

填写好 user 和 password,点击 ok。就能在右侧边栏的 Database 中看到在 fastapi01 里面创建的 book 表格了:

6、完整代码

python 复制代码
# 1.导包
import datetime
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import DateTime, func, String, Float

# 2.根据自己使用的数据库构造数据库URL
ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/fastapi01?charset=utf8mb4"

# 3.构造数据库引擎,并把构造好的URL传递进去
async_engine = create_async_engine(
    ASYNC_DATABASE_URL,
    echo=True,
    pool_size=10,
    max_overflow=20,
)

# 4.创建表------定义基类
class Base(DeclarativeBase):
    create_time: Mapped[datetime.datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now,
        comment="创建时间",
    )
    update_time: Mapped[datetime.datetime] = mapped_column(
        DateTime,
        insert_default=func.now(),
        default=func.now,
        onupdate=func.now(),
        comment="修改时间",
    )

# 4.创建表------定义模型类
class Book(Base):
    __tablename__ = "book"

    id: Mapped[int] = mapped_column(primary_key=True, comment="书籍id")
    bookname: Mapped[str] = mapped_column(String(255), comment="书名")
    author: Mapped[str] = mapped_column(String(255), comment="作者")
    price: Mapped[float] = mapped_column(Float, comment="价格")
    publisher: Mapped[str] = mapped_column(String(255), comment="出版社")

# 5.启动时自动建表------连接数据库 → 执行ORM的建表操作
async def create_tables():
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

# 5.启动时自动建表------定义FastAPI启动和关闭时执行的代码
@asynccontextmanager
async def lifespan(app: FastAPI):
    await create_tables()
    yield

# 5.启动时自动建表------将上面自定义的lifespan赋给FastAPI中特定的lifespan函数
app = FastAPI(lifespan=lifespan)

@app.get("/")
async def root():
    return {"message": "Hello World"}
相关推荐
捉鸭子1 小时前
某红书X-s X-s-common VMP逆向(算法还原)
python·web安全·网络安全·node.js·网络爬虫
budingxiaomoli1 小时前
SpringBoot快速上手
java·spring boot·后端
qq_366740601 小时前
《多模态大模型——算法、应用与微调》勘误
python
lzhdim1 小时前
SQL 入门 11:日期时间格式化、IF、CASE的使用
数据库·sql
dishugj1 小时前
PSQL常见报错问题以及解决方案
数据库·postgresql
一个天蝎座 白勺 程序猿1 小时前
时序数据库选型从迷茫到清晰:国产DolphinDB凭什么成为大数据场景下的首选?
大数据·数据库·时序数据库
噜噜噜阿鲁~1 小时前
python学习笔记 | 7.2、高级特性-迭代
笔记·python·学习
逝水流痕Summer1 小时前
PG触发器查询
数据库·postgresql
Trouvaille ~1 小时前
零基础入门 LangChain 与 LangGraph(七):真正理解 LangGraph——从工作流、状态图到三个核心案例
python·langchain·agent·workflow·langgraph·ai应用开发·智能体开发