1. FastAPI ORM 操作入门:从零开始添加表记录
作为一名长期使用FastAPI进行后端开发的工程师,我经常遇到新手询问如何在FastAPI中通过ORM操作数据库。今天我们就来深入探讨这个看似基础但极其重要的操作——添加表记录。这不仅是CRUD中最常用的Create操作,更是理解ORM工作流程的最佳切入点。
在FastAPI生态中,SQLAlchemy和Tortoise-ORM是最主流的两个ORM选择。本文将以SQLAlchemy为例,因为它的使用范围更广,与FastAPI的集成也更为成熟。不过无论选择哪种ORM,添加记录的核心思想都是相通的:先建立模型(Model),再创建会话(Session),最后通过add和commit完成持久化。
提示:虽然代码示例使用SQLAlchemy,但Tortoise-ORM的用户同样能从中获益,因为ORM的核心概念和工作流程是跨框架通用的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与模型定义
2.1 基础依赖安装
在开始之前,确保你的开发环境已经安装了必要的包。除了fastapi本身,我们还需要:
bash复制pip install sqlalchemy fastapi uvicorn
这里特别提醒一点:很多教程会推荐安装python-dotenv来管理环境变量,但在生产环境中,我强烈建议使用专门的配置管理工具(如Dynaconf),因为随着项目规模扩大,单纯的.env文件会变得难以维护。
2.2 数据库连接配置
创建一个database.py文件来存放数据库相关配置:
python复制from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# 生产环境建议使用PostgreSQL:
# postgresql://user:password@postgresserver/db
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
这里有几个关键点需要注意:
check_same_thread=False仅用于SQLite开发环境,生产环境不需要autocommit=False确保我们显式控制事务边界autoflush=False防止自动刷新干扰我们的操作流程
2.3 定义数据模型
假设我们要创建一个简单的用户管理系统,首先定义User模型:
python复制from sqlalchemy import Column, Integer, String
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), unique=True, nullable=False)
hashed_password = Column(String(100), nullable=False)
def __repr__(self):
return f"<User {self.username}>"
模型定义时常见的坑:
- 忘记设置
__tablename__会导致表名不符合预期 - 字段长度设置不合理(如String未指定长度)在某些数据库会报错
- 没有正确设置nullable约束可能导致数据不一致
3. 添加记录的核心方法
3.1 基础添加操作
最直接的添加记录方式是通过Session的add方法:
python复制from sqlalchemy.orm import Session
from models import User
def create_user(db: Session, username: str, email: str, password: str):
# 密码应该经过哈希处理,这里简化示例
db_user = User(
username=username,
email=email,
hashed_password=password
)
db.add(db_user)
db.commit()
db.refresh(db_user) # 刷新以获取数据库生成的ID等字段
return db_user
这里有几个关键操作:
db.add()将对象加入会话,此时对象处于"待持久化"状态db.commit()真正执行INSERT语句db.refresh()从数据库重新加载对象,获取自增ID等数据库生成的值
3.2 批量添加记录
当需要添加多条记录时,使用add_all比循环add更高效:
python复制def create_users(db: Session, users_data: list[dict]):
users = [
User(
username=data["username"],
email=data["email"],
hashed_password=data["password"]
)
for data in users_data
]
db.add_all(users)
db.commit()
# 注意:批量操作后refresh需要逐个进行
for user in users:
db.refresh(user)
return users
批量操作的优势:
- 减少数据库往返次数
- 在同一个事务中完成所有操作
- 对于大量数据(数千条以上),性能提升非常明显
4. 高级技巧与最佳实践
4.1 事务管理与错误处理
在实际项目中,数据库操作必须考虑事务和错误处理:
python复制from sqlalchemy.exc import SQLAlchemyError
def safe_create_user(db: Session, user_data: dict):
try:
user = User(**user_data)
db.add(user)
db.commit()
db.refresh(user)
return user
except SQLAlchemyError as e:
db.rollback() # 必须回滚以避免脏会话
raise ValueError(f"数据库操作失败: {str(e)}")
finally:
db.close() # 确保会话被正确关闭
关键点:
- 使用try-except捕获特定异常而非笼统的Exception
- 出错时必须rollback,否则后续操作可能失败
- 使用finally确保资源释放
4.2 性能优化技巧
- 批量提交:对于大量插入,每1000条左右提交一次
- 关闭自动刷新:在Session创建时设置autoflush=False
- 使用核心插入:对于纯插入操作,可以考虑使用SQLAlchemy Core的bulk_insert_mappings
python复制from sqlalchemy.sql import insert
def bulk_insert_users(db: Session, users_data: list[dict]):
stmt = insert(User.__table__).values(users_data)
db.execute(stmt)
db.commit()
这种方法比ORM方式快5-10倍,但会绕过一些ORM的特性(如事件触发器等)。
4.3 与FastAPI路由集成
最后,我们来看如何将ORM操作集成到FastAPI路由中:
python复制from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from models import User
from database import get_db # 假设这是获取Session的依赖项
router = APIRouter()
@router.post("/users/", response_model=User)
def create_user_endpoint(
username: str,
email: str,
password: str,
db: Session = Depends(get_db)
):
try:
return create_user(db, username, email, password)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
集成时的注意事项:
- 每个请求应该使用独立的Session
- 通过Depends注入Session,便于测试和重用
- 将业务逻辑与路由处理分离,保持代码整洁
5. 常见问题排查
5.1 记录添加成功但查询不到
现象:代码执行没有报错,但数据库中查不到新增记录。
可能原因:
- 忘记调用commit() - 这是最常见的错误
- 在其他地方调用了rollback()
- 使用了不同的数据库连接或会话
解决方案:
python复制# 在开发过程中可以添加调试输出
print(db.new) # 查看待持久化的对象
print(db.is_active) # 检查会话状态
5.2 唯一约束冲突
现象:插入重复的唯一键值时报错。
处理方案:
python复制from sqlalchemy.exc import IntegrityError
def create_user_safe(db: Session, user_data: dict):
try:
user = User(**user_data)
db.add(user)
db.commit()
db.refresh(user)
return user
except IntegrityError:
db.rollback()
# 检查是哪个字段冲突
existing = db.query(User).filter(
(User.username == user_data["username"]) |
(User.email == user_data["email"])
).first()
conflict_field = "username" if existing.username == user_data["username"] else "email"
raise ValueError(f"{conflict_field}已存在")
5.3 性能问题排查
当批量插入性能不佳时,可以:
- 使用
echo=True开启SQL日志 - 检查是否不必要地频繁调用commit
- 考虑使用更高效的插入方法(如前文提到的bulk_insert_mappings)
6. 测试策略
6.1 单元测试示例
使用pytest测试添加记录的功能:
python复制import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, User
@pytest.fixture
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_create_user(db_session):
user_data = {
"username": "testuser",
"email": "test@example.com",
"hashed_password": "secret"
}
user = create_user(db_session, **user_data)
assert user.id is not None
assert db_session.query(User).count() == 1
测试要点:
- 使用内存数据库加速测试
- 验证返回对象是否包含数据库生成的ID
- 检查数据库中的记录数是否正确
6.2 集成测试建议
对于与FastAPI集成的端点测试:
python复制from fastapi.testclient import TestClient
from main import app # 你的FastAPI应用
client = TestClient(app)
def test_create_user_endpoint():
response = client.post(
"/users/",
json={
"username": "testuser",
"email": "test@example.com",
"password": "secret"
}
)
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["username"] == "testuser"
7. 生产环境进阶建议
7.1 连接池配置
在生产环境中,合理的连接池配置至关重要:
python复制from sqlalchemy.pool import QueuePool
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
poolclass=QueuePool,
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=3600
)
参数说明:
- pool_size:保持的连接数
- max_overflow:允许超过pool_size的连接数
- pool_recycle:连接自动回收时间(秒),防止数据库断开闲置连接
7.2 异步支持
FastAPI天生支持异步,SQLAlchemy也提供了异步版本:
python复制from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
async_engine = create_async_engine(
"postgresql+asyncpg://user:password@localhost/db"
)
AsyncSessionLocal = sessionmaker(
async_engine, class_=AsyncSession, expire_on_commit=False
)
async def async_create_user(username: str, email: str, password: str):
async with AsyncSessionLocal() as session:
user = User(username=username, email=email, hashed_password=password)
session.add(user)
await session.commit()
await session.refresh(user)
return user
异步操作注意事项:
- 所有数据库操作都需要await
- 会话管理使用async with
- 需要特定的异步数据库驱动(如asyncpg)
7.3 监控与日志
添加适当的监控和日志可以帮助发现问题:
python复制import logging
from sqlalchemy import event
logging.basicConfig()
logger = logging.getLogger("sqlalchemy.engine")
logger.setLevel(logging.INFO)
@event.listens_for(engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
context._query_start_time = time.time()
@event.listens_for(engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
duration = time.time() - context._query_start_time
if duration > 0.5: # 记录慢查询
logger.warning(f"Slow query: {statement} took {duration:.2f}s")
在实际项目中,ORM添加记录看似简单,但要做到高效、可靠却需要深入理解其工作原理。我见过太多项目因为不规范的ORM使用而导致性能问题或数据不一致。记住,每次add和commit都应该是有意识的决定,而不是机械的重复。
