下载工作台
Python 数据库实战

SQLAlchemy Core 与 Engine

试读上半部分 · 解锁后可读全文

第 9 章 · SQLAlchemy 2.0 Core 与 Engine

本章目标:掌握 SQLAlchemy 2.0 Core(非 ORM)层:EngineConnectiontext()insert()/update() 构造器;理解 显式事务begin() 语义;在 db-demo 中用 Core 重写 ch08 部分 CRUD;保持 slug + is_published + price(分) 字段约定;为 ch10 ORM 与 ch11 Alembic 铺路。

学时建议:5~6 小时(含 2 小时 Core 跟练)

前置:完成 python-database ch07~ch08;了解 SQL 与 Python 类型注解。


9.1 场景说明:Core 介于 raw SQL 与 ORM 之间

层次特点db-demo 用途
sqlite3 raw最少依赖运维脚本 ch08
SQLAlchemy Core结构化 SQL、方言抽象报表 SQL、批量 ETL
SQLAlchemy ORM对象映射业务服务 ch10
Django ORM框架绑定django-web ch12 对照

Core 适合:复杂 SQL跨数据库迁移不建 Model 的临时任务

db-demo 数据访问栈
  ch08 sqlite3 ──► ch09 Core ──► ch10 ORM ──► ch11 Alembic

9.2 安装与项目布局

cd ~/python-learn/db-demo
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install "sqlalchemy>=2.0,<3"
pip freeze | grep -i sqlalchemy >> requirements.txt
db-demo/
├── core/
│   ├── __init__.py
│   ├── engine.py          # Engine 工厂
│   ├── schema.py          # Table 元数据(Core 反射或声明)
│   └── product_ops.py     # insert/update 示例
├── data/shop_db.sqlite3
└── requirements.txt

连接 URL

# SQLite 同步(本章)
DATABASE_URL = "sqlite:///data/shop_db.sqlite3"

# 生产 MySQL 示例(仅注释)
# mysql+pymysql://user:pass@localhost/shop_db?charset=utf8mb4

9.3 Engine:连接池入口

core/engine.py

from pathlib import Path
from sqlalchemy import create_engine, event
from sqlalchemy.engine import Engine

ROOT = Path(__file__).resolve().parents[1]
DB_FILE = ROOT / "data" / "shop_db.sqlite3"
DATABASE_URL = f"sqlite:///{DB_FILE.as_posix()}"


def make_engine(echo: bool = False) -> Engine:
    eng = create_engine(
        DATABASE_URL,
        echo=echo,                 # True 打印 SQL
        future=True,               # 2.0 风格(2.0 默认 True)
        pool_pre_ping=True,
    )

    @event.listens_for(eng, "connect")
    def _set_sqlite_pragma(dbapi_conn, _connection_record):
        cursor = dbapi_conn.cursor()
        cursor.execute("PRAGMA foreign_keys = ON")
        cursor.close()

    return eng


engine = make_engine()
参数说明
echo=True开发期观察生成 SQL
connect 事件SQLite 外键等同 ch08 db_config
create_engine惰性创建,首次 connect() 才连库

Engine vs Connection

对象生命周期职责
Engine进程级单例管理连接池、方言
Connection请求/事务级执行 SQL、事务边界

9.4 Connection 与 text() 查询

from sqlalchemy import text
from core.engine import engine

with engine.connect() as conn:
    result = conn.execute(
        text("""
            SELECT p.slug, p.title, p.price, p.is_published
            FROM products p
            INNER JOIN categories c ON c.id = p.category_id
            WHERE c.slug = :category_slug AND p.is_published = 1
            ORDER BY p.price ASC
            LIMIT :limit OFFSET :offset
        """),
        {"category_slug": "digital", "limit": 10, "offset": 0},
    )
    rows = result.mappings().all()   # list[RowMapping],类 dict
    for row in rows:
        print(row["slug"], row["price"])  # price 为分
API说明
text(sql)绑定参数的安全 SQL 文本
:name 绑定防注入,等同 sqlite3 命名参数
result.mappings()行作 mapping
result.scalars()单列标量(见 §9.6)
connect() 默认不自动 commit;只读可不带事务;写入需 §9.7 事务。

9.5 Table 元数据(Core 声明表)

与 ch07 DDL 对齐,用 Core 描述(供 insert/update 构造器使用):

core/schema.py

from sqlalchemy import (
    MetaData, Table, Column,
    Integer, Text, ForeignKey, CheckConstraint,
)

metadata = MetaData()

categories = Table(
    "categories", metadata,
    Column("id", Integer, primary_key=True),
    Column("name", Text, nullable=False),
    Column("slug", Text, nullable=False, unique=True),
    Column("sort_order", Integer, nullable=False, server_default="0"),
)

products = Table(
    "products", metadata,
    Column("id", Integer, primary_key=True),
    Column("category_id", Integer, ForeignKey("categories.id"), nullable=False),
    Column("title", Text, nullable=False),
    Column("slug", Text, nullable=False, unique=True),
    Column("description", Text),
    Column("price", Integer, nullable=False),       # 分
    Column("stock", Integer, nullable=False, server_default="0"),
    Column("is_published", Integer, nullable=False, server_default="0"),
    CheckConstraint("price >= 0", name="ck_products_price_nonneg"),
    CheckConstraint("is_published IN (0, 1)", name="ck_products_published"),
)
方式场景
Table(...) 声明与 DDL 同步维护,Core DML
autoload_with=engine 反射已有库反向加载
ORM DeclarativeBase(ch10)推荐业务层

本章用 声明 Table 演示 Core DML,表已由 ch07 SQL 创建。


9.6 insert() 与 returning

SQLAlchemy 2.0 风格:

from sqlalchemy import insert, select
from core.engine import engine
from core.schema import products

def create_product(
    category_id: int,
    title: str,
    slug: str,
    price_cents: int,
    stock: int,
    is_published: bool = False,
) -> int:
    stmt = (
        insert(products)
        .values(
            category_id=category_id,
            title=title,
            slug=slug,
            price=price_cents,
            stock=stock,
            is_published=int(is_published),
        )
        .returning(products.c.id)
    )
    with engine.begin() as conn:          # 自动 commit
        new_id = conn.execute(stmt).scalar_one()
    return new_id
方法说明
insert(table).values(...)构造 INSERT
.returning(col)SQLite 3.35+ / PostgreSQL 支持
engine.begin()事务上下文,成功 commit
.scalar_one()取 RETURNING 单值

批量插入

from sqlalchemy import insert

rows = [
    {"category_id": 1, "title": "A", "slug": "a", "price": 1000, "stock": 1, "is_published": 1},
    {"category_id": 1, "title": "B", "slug": "b", "price": 2000, "stock": 1, "is_published": 1},
]
with engine.begin() as conn:
    conn.execute(insert(products), rows)

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。