Python ORM 完全指南:从基础到高级实践
1. ORM 基础概念
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于在面向对象编程语言中实现与关系型数据库的交互。ORM 将数据库表映射为编程语言中的类,将表中的行映射为对象实例,将表中的列映射为对象属性。
为什么使用 ORM?
-
提高开发效率:减少编写重复的 SQL 语句
-
数据库无关性:可以轻松切换数据库后端
-
安全性:自动防止 SQL 注入攻击
-
面向对象:使用熟悉的面向对象范式操作数据库
2. Python 主流 ORM 框架
2.1 SQLAlchemy
SQLAlchemy 是 Python 社区最流行的 ORM 框架之一,提供了完整的 ORM 功能。
python
from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmakerBase = declarative_base()class User(Base):__tablename__ = 'users'id = Column(Integer, primary_key=True)name = Column(String)age = Column(Integer)# 创建引擎 engine = create_engine('sqlite:///example.db') Base.metadata.create_all(engine)# 创建会话 Session = sessionmaker(bind=engine) session = Session()# 添加新用户 new_user = User(name='Alice', age=25) session.add(new_user) session.commit()# 查询用户 users = session.query(User).filter(User.age > 20).all() for user in users:print(user.name, user.age)
2.2 Django ORM
Django 内置的 ORM 是 Django 框架的一部分,简单易用。
python
from django.db import modelsclass User(models.Model):name = models.CharField(max_length=100)age = models.IntegerField()def __str__(self):return self.name# 创建记录 User.objects.create(name='Bob', age=30)# 查询记录 users = User.objects.filter(age__gt=25) for user in users:print(user.name, user.age)
2.3 Peewee
Peewee 是一个轻量级的 Python ORM,适合小型项目。
python
from peewee import *db = SqliteDatabase('example.db')class User(Model):name = CharField()age = IntegerField()class Meta:database = dbdb.connect() db.create_tables([User])# 创建记录 User.create(name='Charlie', age=35)# 查询记录 users = User.select().where(User.age > 25) for user in users:print(user.name, user.age)
3. ORM 核心功能
3.1 模型定义
python
# SQLAlchemy 示例 class Product(Base):__tablename__ = 'products'id = Column(Integer, primary_key=True)name = Column(String(100), nullable=False)price = Column(Float)category = Column(String(50))# 定义关系reviews = relationship("Review", back_populates="product")class Review(Base):__tablename__ = 'reviews'id = Column(Integer, primary_key=True)content = Column(Text)rating = Column(Integer)product_id = Column(Integer, ForeignKey('products.id'))product = relationship("Product", back_populates="reviews")
3.2 CRUD 操作
python
# 创建 (Create) new_product = Product(name='Laptop', price=999.99, category='Electronics') session.add(new_product) session.commit()# 读取 (Read) # 获取单个对象 product = session.query(Product).get(1)# 获取多个对象 cheap_products = session.query(Product).filter(Product.price < 500).all()# 更新 (Update) product = session.query(Product).get(1) product.price = 899.99 session.commit()# 删除 (Delete) product = session.query(Product).get(1) session.delete(product) session.commit()
3.3 高级查询
python
# 复杂查询 from sqlalchemy import and_, or_# AND 条件 results = session.query(Product).filter(and_(Product.price < 500,Product.category == 'Electronics') ).all()# OR 条件 results = session.query(Product).filter(or_(Product.category == 'Electronics',Product.category == 'Books') ).all()# 排序 results = session.query(Product).order_by(Product.price.desc()).all()# 分页 page_size = 10 page_number = 2 results = session.query(Product).limit(page_size).offset((page_number-1)*page_size).all()# 聚合函数 from sqlalchemy import funcavg_price = session.query(func.avg(Product.price)).scalar() max_price = session.query(func.max(Product.price)).scalar()
4. 关系与关联
4.1 一对多关系
python
# SQLAlchemy 示例 class Author(Base):__tablename__ = 'authors'id = Column(Integer, primary_key=True)name = Column(String)books = relationship("Book", back_populates="author")class Book(Base):__tablename__ = 'books'id = Column(Integer, primary_key=True)title = Column(String)author_id = Column(Integer, ForeignKey('authors.id'))author = relationship("Author", back_populates="books")# 使用示例 author = Author(name='J.K. Rowling') book1 = Book(title='Harry Potter 1', author=author) book2 = Book(title='Harry Potter 2', author=author)session.add_all([author, book1, book2]) session.commit()# 查询作者的所有书籍 author = session.query(Author).first() for book in author.books:print(book.title)
4.2 多对多关系
python
# 需要中间关联表 book_tags = Table('book_tags', Base.metadata,Column('book_id', Integer, ForeignKey('books.id')),Column('tag_id', Integer, ForeignKey('tags.id')) )class Tag(Base):__tablename__ = 'tags'id = Column(Integer, primary_key=True)name = Column(String)books = relationship("Book", secondary=book_tags, back_populates="tags")class Book(Base):__tablename__ = 'books'id = Column(Integer, primary_key=True)title = Column(String)tags = relationship("Tag", secondary=book_tags, back_populates="books")# 使用示例 book = Book(title='Python Cookbook') tag1 = Tag(name='Programming') tag2 = Tag(name='Python')book.tags.extend([tag1, tag2]) session.add(book) session.commit()# 查询书籍的所有标签 book = session.query(Book).first() for tag in book.tags:print(tag.name)
5. ORM 高级特性
5.1 混合属性 (Hybrid Attributes)
python
from sqlalchemy.ext.hybrid import hybrid_propertyclass Product(Base):# ... 其他字段 ...price = Column(Float)tax_rate = Column(Float)@hybrid_propertydef price_with_tax(self):return self.price * (1 + self.tax_rate)@price_with_tax.expressiondef price_with_tax(cls):return cls.price * (1 + cls.tax_rate)# 可以在查询中使用 expensive_products = session.query(Product).filter(Product.price_with_tax > 1000 ).all()
5.2 事件监听
python
from sqlalchemy import event@event.listens_for(Product.price, 'set') def validate_price(target, value, oldvalue, initiator):if value < 0:raise ValueError("Price cannot be negative")# 尝试设置负价格会引发异常 product = Product(name='Test', price=-10) # 引发 ValueError
5.3 多数据库支持
python
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker# 主数据库 master_engine = create_engine('postgresql://master.db') MasterSession = sessionmaker(bind=master_engine)# 从数据库 slave_engine = create_engine('postgresql://slave.db') SlaveSession = sessionmaker(bind=slave_engine)# 根据操作类型选择会话 def get_session(read_only=False):return SlaveSession() if read_only else MasterSession()# 使用示例 with get_session(read_only=True) as session:products = session.query(Product).all() # 从从库读取
6. ORM 性能优化
6.1 批量操作
python
# 低效方式 for i in range(1000):product = Product(name=f'Product {i}', price=i*10)session.add(product)session.commit() # 每次提交# 高效方式 session.bulk_insert_mappings(Product, [{'name': f'Product {i}', 'price': i*10} for i in range(1000) ]) session.commit() # 一次提交
6.2 延迟加载 vs 立即加载
python
# 默认是延迟加载 author = session.query(Author).first() # 访问 books 属性时会触发新的查询 print(author.books) # 执行新的查询# 使用 joinedload 立即加载 from sqlalchemy.orm import joinedloadauthor = session.query(Author).options(joinedload(Author.books)).first() # books 已经被加载 print(author.books) # 不会执行新的查询
6.3 只加载需要的列
python
# 只加载 id 和 name 列 products = session.query(Product.id, Product.name).all()
7. 常见问题与解决方案
7.1 N+1 查询问题
问题:获取一个列表,然后对每个元素访问关联属性,导致大量查询。
解决方案:使用 joinedload
或 subqueryload
。
python
from sqlalchemy.orm import subqueryload# 不好的方式 (N+1 查询) authors = session.query(Author).all() for author in authors:print(author.books) # 每个 author 都会触发一个查询# 好的方式 (2 个查询) authors = session.query(Author).options(subqueryload(Author.books)).all() for author in authors:print(author.books) # 不会触发新查询
7.2 长事务问题
问题:事务打开时间过长,导致数据库连接被占用。
解决方案:合理划分事务范围。
python
# 不好的方式 with session.begin():# 长时间操作process_large_dataset(session)# 其他操作...# 好的方式 for chunk in get_data_chunks():with session.begin():process_chunk(session, chunk)
8. ORM 最佳实践
-
合理使用 ORM:简单查询使用 ORM,复杂查询可以考虑直接使用 SQL
-
注意会话生命周期:避免长时间持有会话
-
批量操作:大量数据操作时使用批量方法
-
选择性加载:只加载需要的列和关联
-
索引优化:确保数据库表有适当的索引
-
监控性能:使用 SQL 日志和性能分析工具
9. 总结
Python ORM 提供了强大的数据库操作能力,可以显著提高开发效率。选择适合项目规模的 ORM 框架,合理使用其特性,并注意性能优化,可以构建出既高效又易于维护的数据库访问层。