Python ORM:从入门到实践 – wiki大全

My apologies, it seems I am unable to directly write files to your file system with the tools currently at my disposal.

However, I have generated the article content for “Python ORM:从入门到实践”. I can provide the full article text here in my response, and you can then save it to a file yourself.

Here is the article content:

“`markdown

Python ORM:从入门到实践

引言

在现代软件开发中,数据存储是核心环节之一。关系型数据库因其强大的数据管理能力而被广泛使用。然而,直接使用SQL语句与数据库交互,不仅会增加开发者的负担,还可能导致代码的可读性和维护性下降,并且容易出现SQL注入等安全问题。为了解决这些问题,对象关系映射 (Object-Relational Mapping, ORM) 技术应运而生。

什么是ORM (Object-Relational Mapping)?

ORM是一种编程技术,用于在不兼容的类型系统之间(通常是面向对象的编程语言和关系型数据库)转换数据。简单来说,ORM允许开发者使用面向对象的方式来操作数据库,将数据库中的表、行、列映射成编程语言中的类、对象、属性。开发者无需编写原始SQL,而是通过操作对象来间接操作数据库。

为什么需要ORM?

  1. 提高开发效率: 开发者可以专注于业务逻辑,而无需花费大量时间编写和调试SQL语句。
  2. 代码可读性和可维护性: 使用面向对象的方式操作数据,代码更符合直觉,易于理解和维护。
  3. 数据库独立性: ORM框架通常支持多种数据库后端(如PostgreSQL, MySQL, SQLite等),理论上在不修改业务代码的情况下,可以轻松切换数据库。
  4. 安全性: ORM框架通常会处理SQL注入等常见安全问题。
  5. 减少重复代码: 数据库相关的操作被抽象和封装,减少了重复的SQL或数据访问层代码。

ORM的优势与劣势

优势:
* 抽象化数据库操作,简化开发。
* 增强代码可移植性(数据库无关)。
* 提高开发效率和代码质量。
* 提供面向对象的数据操作接口。

劣势:
* 性能开销: ORM在将对象转换为SQL和将结果转换为对象时会产生一定的性能开销。对于高度优化的复杂查询,手写SQL可能更高效。
* 学习曲线: 掌握一个成熟的ORM框架可能需要一定的时间。
* 过度抽象: 有时ORM的抽象会掩盖底层数据库的细节,导致开发者不清楚实际执行的SQL,从而难以优化。
* 不适合所有场景: 对于极度复杂的查询或需要充分利用特定数据库高级功能的场景,ORM可能力不从心,或需要结合原生SQL使用。

主流Python ORM框架

Python生态中拥有多个功能强大、社区活跃的ORM框架:

  1. SQLAlchemy:

    • 特点: 功能最全面、最灵活的Python ORM。它提供了一整套企业级持久化模式,包括强大的表达式语言和完整的ORM。SQLAlchemy分为Core(SQL表达式语言)和ORM两部分,既可以作为低级SQL工具使用,也可以作为高级ORM使用。
    • 适用场景: 任何需要灵活数据库操作、高性能、或者非Web框架集成的项目。
  2. Django ORM:

    • 特点: Django Web框架的内置ORM,设计哲学是“DRY”(Don’t Repeat Yourself)。它与Django框架紧密集成,模型定义简洁,查询API直观易用。
    • 适用场景: Django项目。如果你正在使用Django,那么Django ORM是首选。
  3. Peewee:

    • 特点: 一个小巧、富有表现力、易于使用的Python ORM。它旨在成为一个轻量级的替代品,具有简单的API和很少的依赖。
    • 适用场景: 小型项目、快速原型开发,或者你不需要SQLAlchemy那么强大的功能,但又想要比Django ORM更独立的选择时。
  4. PonyORM:

    • 特点: 以其独特的“生成器式”查询语法而闻名,允许开发者在Python代码中直接编写类似SQL的查询,然后PonyORM会将其转换为真正的SQL。
    • 适用场景: 喜欢Pythonic查询语法,对性能有较高要求,且愿意接受其特殊查询范式的项目。

在本篇文章中,我们将重点介绍功能强大且广泛使用的SQLAlchemy

SQLAlchemy 入门与实践

SQLAlchemy是一个非常强大和灵活的ORM框架,它提供了多种方式来与数据库交互。我们将使用其ORM部分进行讲解。

安装

首先,你需要安装SQLAlchemy库。通常还会安装一个数据库驱动,例如SQLite是Python内置的,但对于MySQL或PostgreSQL,你需要安装相应的驱动(如psycopg2mysqlclient)。

bash
pip install sqlalchemy
pip install pymysql # 如果使用MySQL
pip install psycopg2-binary # 如果使用PostgreSQL

定义模型

在SQLAlchemy中,我们通过定义Python类来表示数据库表。这些类通常继承自declarative_base创建的基类。

“`python
from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey
from sqlalchemy.orm import sessionmaker, declarative_base, relationship
import os

定义数据库连接字符串

使用SQLite内存数据库作为示例

如果是文件数据库,可以是 ‘sqlite:///./test.db’

DATABASE_URL = “sqlite:///:memory:”

创建Base

Base = declarative_base()

定义User模型

class User(Base):
tablename = ‘users’ # 数据库中的表名

id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(120), unique=True, nullable=False)

# 定义与Address模型的一对多关系
# 'addresses' 是User对象上的一个属性,通过它你可以访问与该用户关联的Address对象列表
# back_populates='user' 确保在Address对象上也有一个 'user' 属性,指向其所属的User对象
addresses = relationship("Address", back_populates="user", cascade="all, delete-orphan")

def __repr__(self):
    return f"<User(id={self.id}, name='{self.name}', email='{self.email}')>"

定义Address模型

class Address(Base):
tablename = ‘addresses’

id = Column(Integer, primary_key=True)
street = Column(String(100), nullable=False)
city = Column(String(50), nullable=False)
zip_code = Column(String(10), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

# 定义与User模型的多对一关系
# 'user' 是Address对象上的一个属性,通过它可以访问该地址所属的User对象
user = relationship("User", back_populates="addresses")

def __repr__(self):
    return f"<Address(id={self.id}, street='{self.street}', city='{self.city}')>"

“`

在上述代码中:
* declarative_base() 创建了一个基类 Base,我们所有的模型类都将继承它。
* __tablename__ 定义了模型对应的数据库表名。
* Column 用于定义表的列,并指定数据类型(Integer, String, Text等)和约束(primary_key, nullable, unique等)。
* ForeignKey 定义了外键关系。
* relationship 用于定义模型之间的关系,例如一对多(UserAddress)和多对一(AddressUser)。cascade="all, delete-orphan" 表示当User被删除时,关联的Address也会被删除;如果一个Address不再关联任何User,它也会被删除。

连接数据库和创建表

在使用模型之前,需要建立数据库连接并创建表结构。

“`python

连接数据库

engine = create_engine(DATABASE_URL, echo=False) # echo=True 会打印所有SQL语句,方便调试

创建所有定义的表

Base.metadata.create_all(engine)

print(“数据库表已创建!”)
“`

会话管理

SQLAlchemy使用会话(Session)来管理与数据库的交互。会话是所有数据库操作(查询、添加、更新、删除)的入口点。它代表了与数据库的临时会话,负责事务管理和对象状态跟踪。

“`python

创建一个Session类

Session = sessionmaker(bind=engine)

创建一个会话实例

session = Session()
“`

通常,我们会将会话管理封装起来,例如使用上下文管理器,以确保会话在使用完毕后被正确关闭。

“`python
from contextlib import contextmanager

@contextmanager
def session_scope():
“””提供一个事务范围的会话”””
session = Session()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()

使用示例:

with session_scope() as session:

# 在这里执行数据库操作

pass

“`

基本CRUD操作 (Create, Read, Update, Delete)

现在我们已经定义了模型并设置了会话,可以开始进行基本的数据库操作了。

“`python

— C: 创建 (Create) —

print(“\n— 创建用户和地址 —“)
with session_scope() as session:
user1 = User(name=’Alice’, email=’[email protected]’)
user2 = User(name=’Bob’, email=’[email protected]’)

# 添加用户到会话
session.add(user1)
session.add(user2)

# 刷新会话以获取user1和user2的id,以便关联Address
# 或者先commit,再重新查询user1和user2
session.flush()

address1 = Address(street='123 Main St', city='Anytown', zip_code='10001', user=user1)
address2 = Address(street='456 Oak Ave', city='Otherville', zip_code='20002', user=user1)
address3 = Address(street='789 Pine Rd', city='Somewhere', zip_code='30003', user=user2)

session.add_all([address1, address2, address3])

print(“用户和地址已成功添加。”)

— R: 读取 (Read) —

print(“\n— 读取所有用户 —“)
with session_scope() as session:
users = session.query(User).all()
for user in users:
print(user)
for address in user.addresses:
print(f” – {address}”)

print(“\n— 通过ID查询用户 —“)
with session_scope() as session:
user = session.query(User).get(1) # get() 只能通过主键查询
if user:
print(f”查询到的用户ID为1: {user}”)
else:
print(“未找到ID为1的用户”)

print(“\n— 通过过滤条件查询用户 —“)
with session_scope() as session:
user_bob = session.query(User).filter_by(name=’Bob’).first() # first() 返回第一个匹配项
if user_bob:
print(f”查询到的用户名为Bob: {user_bob}”)
else:
print(“未找到用户Bob”)

users_by_email_pattern = session.query(User).filter(User.email.like('%example.com%')).all()
print("Email包含'example.com'的用户:")
for user in users_by_email_pattern:
    print(user)

— U: 更新 (Update) —

print(“\n— 更新用户数据 —“)
with session_scope() as session:
user_to_update = session.query(User).filter_by(name=’Alice’).first()
if user_to_update:
print(f”更新前: {user_to_update}”)
user_to_update.email = ‘[email protected]
print(f”更新后: {user_to_update}”)
else:
print(“未找到Alice用户进行更新”)

验证更新

print(“\n— 验证更新后的用户数据 —“)
with session_scope() as session:
alice_updated = session.query(User).filter_by(name=’Alice’).first()
print(f”重新查询Alice: {alice_updated}”)

— D: 删除 (Delete) —

print(“\n— 删除用户 —“)
with session_scope() as session:
user_to_delete = session.query(User).filter_by(name=’Bob’).first()
if user_to_delete:
print(f”准备删除用户: {user_to_delete}”)
# 由于User和Address之间设置了cascade=”all, delete-orphan”,
# 删除User时会自动删除其关联的Address
session.delete(user_to_delete)
print(“用户Bob及其关联地址已删除。”)
else:
print(“未找到Bob用户进行删除”)

验证删除

print(“\n— 验证删除后的数据 —“)
with session_scope() as session:
remaining_users = session.query(User).all()
print(“删除后剩余用户:”)
for user in remaining_users:
print(user)

remaining_addresses = session.query(Address).all()
print("删除后剩余地址:")
for address in remaining_addresses:
    print(address)

“`

查询进阶

SQLAlchemy的查询API非常强大。

  • 过滤、排序、限制:

    “`python
    with session_scope() as session:
    # 过滤 (filter)
    users_in_city = session.query(User).join(Address).filter(Address.city == ‘Anytown’).all()
    print(“\n生活在Anytown的用户:”)
    for user in users_in_city:
    print(user)

    # 排序 (order_by)
    users_ordered = session.query(User).order_by(User.name.asc()).all()
    print("\n按姓名升序排列的用户:")
    for user in users_ordered:
        print(user)
    
    # 限制 (limit) 和 偏移 (offset) - 用于分页
    limited_users = session.query(User).order_by(User.id.asc()).limit(1).offset(0).all()
    print("\n第一页的第一个用户:")
    for user in limited_users:
        print(user)
    

    “`

  • 联结 (Joins):

    在模型中定义了relationship之后,可以很方便地进行联结查询。

    “`python
    with session_scope() as session:
    # 隐式联结 (由于relationship的存在)
    # 查询所有用户和他们的地址,并根据地址的城市过滤
    users_with_anytown_address = session.query(User).filter(User.addresses.any(Address.city == ‘Anytown’)).all()
    print(“\n通过地址城市过滤的用户 (隐式联结):”)
    for user in users_with_anytown_address:
    print(user)
    for address in user.addresses:
    print(f” – {address}”)

    # 显式联结 (join)
    # 查询所有用户和他们的地址,返回User和Address的组合
    user_address_pairs = session.query(User, Address).join(Address).filter(Address.city == 'Anytown').all()
    print("\n用户与Anytown地址的显式联结:")
    for user, address in user_address_pairs:
        print(f"User: {user.name}, Address: {address.street}, {address.city}")
    

    “`

关系映射

前面已经通过relationshipForeignKey展示了一对多/多对一关系。

对于多对多关系,需要引入一个中间表(关联表)。

“`python

假设我们有一个文章系统,文章和标签之间是多对多关系

定义中间表(不需要定义模型类,直接使用Table)

from sqlalchemy import Table, MetaData

metadata = Base.metadata # 使用之前定义的Base的metadata

post_tags_table = Table(
‘post_tags’, metadata,
Column(‘post_id’, Integer, ForeignKey(‘posts.id’), primary_key=True),
Column(‘tag_id’, Integer, ForeignKey(‘tags.id’), primary_key=True)
)

class Post(Base):
tablename = ‘posts’
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
content = Column(Text)

tags = relationship("Tag",
                    secondary=post_tags_table, # 指定中间表
                    back_populates="posts") # 确保Tag模型也有一个posts属性

def __repr__(self):
    return f"<Post(id={self.id}, title='{self.title}')>"

class Tag(Base):
tablename = ‘tags’
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)

posts = relationship("Post",
                     secondary=post_tags_table,
                     back_populates="tags")

def __repr__(self):
    return f"<Tag(id={self.id}, name='{self.name}')>"

重新创建所有表,包括新的Post和Tag表

Base.metadata.drop_all(engine) # 先删除旧表(谨慎操作,生产环境不要随意执行)
Base.metadata.create_all(engine)

print(“\n— 创建多对多关系数据 —“)
with session_scope() as session:
post1 = Post(title=’ORM Basics’, content=’Learning about ORM…’)
post2 = Post(title=’Advanced SQLAlchemy’, content=’Deep dive into SQLAlchemy features.’)
post3 = Post(title=’Python Web Frameworks’, content=’Comparing popular Python web frameworks.’)

tag_python = Tag(name='Python')
tag_database = Tag(name='Database')
tag_orm = Tag(name='ORM')
tag_web = Tag(name='WebDev')

post1.tags.append(tag_python)
post1.tags.append(tag_database)
post1.tags.append(tag_orm)

post2.tags.append(tag_python)
post2.tags.append(tag_orm)

post3.tags.append(tag_python)
post3.tags.append(tag_web)

session.add_all([post1, post2, post3, tag_python, tag_database, tag_orm, tag_web])

print("文章和标签数据已添加。")

print(“\n— 查询多对多关系 —“)
with session_scope() as session:
# 查询所有Python相关的文章
python_tag = session.query(Tag).filter_by(name=’Python’).first()
if python_tag:
print(f”标签 ‘{python_tag.name}’ 关联的文章:”)
for post in python_tag.posts:
print(f” – {post.title}”)

# 查询包含ORM标签的文章
orm_posts = session.query(Post).filter(Post.tags.any(Tag.name == 'ORM')).all()
print("\n包含 'ORM' 标签的文章:")
for post in orm_posts:
    print(post.title)

“`

Django ORM 简介 (简述)

如果你正在使用Django,那么Django ORM是首选。它与Django框架紧密集成,提供了非常便捷的模型定义和查询接口。

特点与集成:
* 开箱即用,无需额外配置。
* 模型定义是django.db.models.Model的子类。
* 自动生成数据库迁移脚本。
* 强大的QuerySet API。

定义模型示例:

“`python

在 Django 应用的 models.py 中

from django.db import models

class User(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True)

def __str__(self):
    return self.name

class Address(models.Model):
street = models.CharField(max_length=100)
city = models.CharField(max_length=50)
zip_code = models.CharField(max_length=10)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name=’addresses’)

def __str__(self):
    return f"{self.street}, {self.city}"

“`

基本CRUD操作示例:

“`python

假设已经配置好Django环境,可以在 manage.py shell 中运行

创建

user1 = User.objects.create(name=’Charlie’, email=’[email protected]’)
address1 = Address.objects.create(street=’101 River St’, city=’Uptown’, zip_code=’40004′, user=user1)

读取

all_users = User.objects.all()
user_by_id = User.objects.get(id=1)
user_filtered = User.objects.filter(name__startswith=’Cha’)

更新

user1.email = ‘[email protected]
user1.save()

删除

user1.delete()
“`

ORM进阶话题

数据库迁移 (Database Migrations)

随着项目的发展,数据库模型会不断变化(添加新字段、修改字段类型等)。手动维护这些变更既耗时又容易出错。数据库迁移工具允许开发者以结构化的方式管理数据库模式的演变。

  • SQLAlchemy: 通常结合 Alembic 使用。Alembic是一个轻量级的数据库迁移工具,可以检测模型变化并自动生成迁移脚本。
  • Django ORM: 内置了强大的迁移系统。通过python manage.py makemigrations生成迁移文件,python manage.py migrate应用迁移。

性能优化

虽然ORM提供了便利,但其性能问题不容忽视。

  1. 惰性加载 (Lazy Loading) 与 即时加载 (Eager Loading):

    • 惰性加载 (默认): 当你访问一个关联对象(如user.addresses)时,ORM才会去数据库中查询。优点是只有需要时才查询,节省资源;缺点是可能导致N+1查询问题(即查询N个用户,每次访问其地址又产生N次额外查询)。
    • 即时加载: 在查询主对象时,同时将关联对象一同加载进来。SQLAlchemy通常使用joinedloadsubqueryload来执行。Django ORM使用select_related(一对一、多对一)和prefetch_related(一对多、多对多)。

    “`python

    SQLAlchemy 即时加载示例

    with session_scope() as session:
    # 使用 joinedload 避免N+1问题,一次查询加载User和其所有Address
    users_eager = session.query(User).options(relationship(User.addresses, lazy=’joined’)).all()
    # 或者更通用的方式:
    # from sqlalchemy.orm import joinedload
    # users_eager = session.query(User).options(joinedload(User.addresses)).all()
    print(“\n即时加载用户及其地址:”)
    for user in users_eager:
    print(f”{user.name}:”)
    for address in user.addresses: # 此时访问 addresses 不会再触发新的数据库查询
    print(f” – {address.city}”)
    “`

  2. 批量操作: 批量插入、更新和删除通常比逐条操作更高效,因为它们可以减少与数据库的往返次数。SQLAlchemy提供了add_all(), bulk_insert_mappings(), bulk_update_mappings()等方法。

    “`python

    SQLAlchemy 批量插入示例

    with session_scope() as session:
    new_users = [
    User(name=’David’, email=’[email protected]’),
    User(name=’Eve’, email=’[email protected]’)
    ]
    session.add_all(new_users) # 也可以批量添加
    “`

  3. 使用原生SQL: 对于特别复杂的查询或性能瓶颈,不要害怕退回到原生SQL。大多数ORM都提供了执行原生SQL的接口。

    “`python

    SQLAlchemy 原生SQL示例

    with session_scope() as session:
    result = session.execute(“SELECT name, email FROM users WHERE id = :user_id”, {‘user_id’: 1}).fetchone()
    if result:
    print(f”\n原生SQL查询结果: Name={result.name}, Email={result.email}”)
    “`

事务管理

事务是数据库操作的基本单位,确保数据库操作的原子性、一致性、隔离性、持久性(ACID)。ORM框架通常提供明确的事务管理机制。

在SQLAlchemy中,session.commit()提交事务,session.rollback()回滚事务。我们前面定义的session_scope上下文管理器就是事务管理的一个良好实践。

总结

Python ORM为开发者提供了一种优雅、高效的方式来与关系型数据库交互,极大地简化了数据访问层的开发。从SQLAlchemy的全面灵活到Django ORM的紧密集成,Python社区提供了丰富的ORM选择,以适应不同项目的需求。

掌握ORM的基本概念、模型定义、CRUD操作以及关系映射是入门的关键。在实践中,关注数据库迁移、性能优化(如即时加载和批量操作)和事务管理,将帮助你构建健壮、高效且易于维护的应用程序。虽然ORM并非银弹,但在大多数Web应用和数据驱动的Python项目中,它无疑是提升开发效率和代码质量的利器。
“`

滚动至顶部