各位观众老爷们,大家好!今天咱们来聊聊Python ORM框架SQLAlchemy
里的两种主要映射方式:声明式(Declarative)和经典式(Classic)。别害怕,这俩家伙虽然听起来像魔法咒语,但其实就是把Python类跟数据库表关联起来的不同方法。咱们争取用最接地气的方式,把它们扒个底朝天,让大家以后写代码的时候,不再迷茫。
开场白:为什么要映射?
在开始之前,咱先得搞清楚一个问题:为什么要映射?想象一下,你写了一个Python程序,需要从数据库里读取数据,或者往数据库里写入数据。如果没有ORM,你就得手写SQL语句,像这样:
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# 查询数据
cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
result = cursor.fetchone()
print(result)
# 插入数据
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ('Alice', '[email protected]'))
conn.commit()
conn.close()
手写SQL语句当然可以,但有几个问题:
- 繁琐: 每一次操作都要拼SQL,代码量巨大。
- 容易出错: 手残党一不小心就拼错SQL,导致程序崩溃。
- 不安全: 容易受到SQL注入攻击。
- 不Pythonic: 看起来不像Python代码,更像SQL代码。
所以,我们需要ORM (Object-Relational Mapping),它就像一个翻译器,把Python对象和数据库表之间的数据进行转换。你只需要操作Python对象,ORM会自动帮你生成SQL语句,执行数据库操作。
SQLAlchemy
就是Python世界里最流行的ORM框架之一。它提供了两种主要的映射方式,让我们可以轻松地把Python类映射到数据库表:声明式和经典式。
第一幕:经典式映射(Classic Mapping)
经典式映射是SQLAlchemy
的老牌映射方式,也是最底层、最灵活的一种。它允许你完全控制映射过程,但也意味着你需要写更多的代码。
- 定义Table对象:
首先,你需要创建一个Table
对象,用来描述数据库表的结构。Table
对象包含表的名称、列的定义、约束等信息。
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:') # 使用内存数据库,方便演示
metadata = MetaData()
users_table = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(50)),
Column('email', String(50))
)
addresses_table = Table('addresses', metadata,
Column('id', Integer, primary_key=True),
Column('email_address', String(50), nullable=False),
Column('user_id', Integer, ForeignKey('users.id'))
)
metadata.create_all(engine) # 创建表
这段代码定义了两个表:users
和addresses
。users
表有id
、name
和email
三个列,addresses
表有id
、email_address
和user_id
三个列,其中user_id
是外键,指向users
表的id
列。
- 定义Python类:
接下来,你需要定义Python类,用来表示数据库表中的数据。
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def __repr__(self):
return f"<User(name='{self.name}', email='{self.email}')>"
class Address:
def __init__(self, email_address, user_id):
self.email_address = email_address
self.user_id = user_id
def __repr__(self):
return f"<Address(email_address='{self.email_address}')>"
这两个类分别对应users
和addresses
表。
- 进行映射:
现在,我们需要使用SQLAlchemy
的mapper()
函数,把Python类和Table
对象关联起来。
from sqlalchemy.orm import mapper
mapper(User, users_table)
mapper(Address, addresses_table)
mapper()
函数把User
类映射到users_table
,Address
类映射到addresses_table
。
- 使用Session:
最后,我们需要使用SQLAlchemy
的Session
对象,来执行数据库操作。
Session = sessionmaker(bind=engine)
session = Session()
# 创建User对象
user1 = User(name='Alice', email='[email protected]')
user2 = User(name='Bob', email='[email protected]')
# 添加到Session
session.add_all([user1, user2])
# 提交事务
session.commit()
# 查询数据
users = session.query(User).all()
print(users)
# 创建Address对象
address1 = Address(email_address='[email protected]', user_id=user1.id)
session.add(address1)
session.commit()
# 查询Address数据
addresses = session.query(Address).all()
print(addresses)
# 关闭Session
session.close()
这段代码创建了一个Session
对象,然后创建了两个User
对象,把它们添加到Session
中,并提交了事务。最后,查询了数据库中的所有User
对象,并打印出来。
经典式映射的优缺点:
-
优点:
- 灵活: 可以完全控制映射过程,适用于复杂的场景。
- 底层: 可以更好地理解
SQLAlchemy
的工作原理。
-
缺点:
- 冗长: 需要写更多的代码。
- 复杂: 学习曲线陡峭,容易出错。
第二幕:声明式映射(Declarative Mapping)
声明式映射是SQLAlchemy
推荐的映射方式,它使用一种更简洁、更Pythonic的方式来定义映射关系。
- 定义Base:
首先,你需要创建一个Base
类,它是所有声明式类的基类。
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
- 定义声明式类:
接下来,你需要定义声明式类,用来表示数据库表中的数据。声明式类继承自Base
类,并使用__tablename__
属性来指定表的名称,使用Column
对象来定义列。
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
email = Column(String(50))
addresses = relationship("Address", back_populates="user")
def __repr__(self):
return f"<User(name='{self.name}', email='{self.email}')>"
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email_address = Column(String(50), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="addresses")
def __repr__(self):
return f"<Address(email_address='{self.email_address}')>"
这段代码定义了两个声明式类:User
和Address
。User
类的__tablename__
属性指定表名为users
,id
、name
和email
是三个列。Address
类的__tablename__
属性指定表名为addresses
,id
、email_address
和user_id
是三个列,其中user_id
是外键,指向users
表的id
列。
relationship
定义了表之间的关系。 在 User
类中,addresses = relationship("Address", back_populates="user")
定义了 User 和 Address 之间的一对多关系(一个用户可以有多个地址)。back_populates="user"
表示在 Address
类中有一个名为 user
的属性,用于反向引用关联的 User
对象。
在 Address
类中,user = relationship("User", back_populates="addresses")
定义了 Address 和 User 之间的一对一关系(一个地址属于一个用户)。back_populates="addresses"
表示在 User
类中有一个名为 addresses
的属性,用于反向引用关联的 Address
对象。
- 创建表:
接下来,你需要使用Base
类的metadata.create_all()
方法,创建数据库表。
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
- 使用Session:
最后,我们需要使用SQLAlchemy
的Session
对象,来执行数据库操作。
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
# 创建User对象
user1 = User(name='Alice', email='[email protected]')
user2 = User(name='Bob', email='[email protected]')
# 添加到Session
session.add_all([user1, user2])
# 提交事务
session.commit()
# 查询数据
users = session.query(User).all()
print(users)
# 创建Address对象
address1 = Address(email_address='[email protected]', user_id=user1.id)
session.add(address1)
session.commit()
# 查询Address数据
addresses = session.query(Address).all()
print(addresses)
# 关闭Session
session.close()
这段代码和经典式映射的代码基本一样,只是创建User
和Address
对象的方式略有不同。
声明式映射的优缺点:
-
优点:
- 简洁: 代码更简洁,更易于阅读和维护。
- Pythonic: 更符合Python的编程风格。
- 方便: 自动生成表结构,减少了手动编写SQL语句的工作量。
-
缺点:
- 灵活性稍差: 对于一些复杂的场景,可能需要使用更底层的API。
- 抽象程度较高: 可能会隐藏一些
SQLAlchemy
的细节。
第三幕:总结与对比
特性 | 经典式映射 | 声明式映射 |
---|---|---|
代码量 | 较多 | 较少 |
灵活性 | 高 | 较低 |
学习曲线 | 陡峭 | 较平缓 |
易用性 | 较低 | 较高 |
推荐使用程度 | 复杂场景或需要完全控制映射过程的情况 | 大部分场景,尤其是新手入门 |
定义方式 | 先定义Table 对象,再用mapper() 函数映射 |
定义继承自Base 的声明式类,自动生成表结构 |
经典式 vs 声明式: 选哪个?
说了这么多,那么问题来了,我们应该选择哪种映射方式呢?
- 如果你是新手,或者项目比较简单,建议使用声明式映射。 声明式映射更易于学习和使用,可以快速上手。
- 如果你需要完全控制映射过程,或者项目非常复杂,可以考虑使用经典式映射。 经典式映射提供了更高的灵活性,可以满足一些特殊的需求。
- 如果项目已经使用了经典式映射,并且运行良好,没有必要迁移到声明式映射。
第四幕:一些高级用法
- 混合式属性 (Hybrid Attributes):
混合式属性允许你在类级别定义一个属性,它既可以像普通Python属性一样访问,也可以在SQLAlchemy查询中使用。这对于定义一些复杂的计算属性非常有用。
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
firstname = Column(String(50))
lastname = Column(String(50))
@hybrid_property
def fullname(self):
return self.firstname + " " + self.lastname
@fullname.setter
def fullname(self, fullname):
self.firstname, self.lastname = fullname.split(" ")
@fullname.expression
def fullname(cls):
return cls.firstname + " " + cls.lastname
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
user1 = User(firstname='Alice', lastname='Smith')
session.add(user1)
session.commit()
print(user1.fullname) # 输出: Alice Smith
user1.fullname = "Bob Johnson"
session.commit()
print(user1.firstname) # 输出: Bob
print(user1.lastname) # 输出: Johnson
# 在查询中使用fullname
users = session.query(User).filter(User.fullname == "Bob Johnson").all()
print(users)
- 事件监听 (Events):
SQLAlchemy允许你监听各种数据库事件,例如插入、更新、删除等。这对于实现一些自定义的业务逻辑非常有用。
from sqlalchemy import event
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
@event.listens_for(User, 'before_insert')
def before_insert_listener(mapper, connection, target):
print(f"Before inserting user: {target.name}")
target.name = target.name.upper() # 将用户名转换为大写
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
user1 = User(name='Alice')
session.add(user1)
session.commit()
print(user1.name) # 输出: ALICE
- 使用反射 (Reflection):
如果你已经有一个现有的数据库,可以使用反射来自动生成Table
对象或声明式类。
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///existing.db') # 假设有一个现有的数据库
metadata = MetaData()
metadata.reflect(bind=engine)
users_table = metadata.tables['users'] # 获取 users 表的 Table 对象
# 使用经典式映射
from sqlalchemy.orm import mapper
class User:
pass
mapper(User, users_table)
Session = sessionmaker(bind=engine)
session = Session()
# 现在你可以像使用经典式映射一样使用 User 类
对于声明式映射,可以用 automap
扩展:
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
engine = create_engine("sqlite:///existing.db")
Base = automap_base()
# reflect the tables
Base.prepare(engine, reflect=True)
# mapped classes are now created with names by default
# matching that of the table name.
User = Base.classes.users
session = Session(engine)
# 同样可以像声明式映射一样使用 User 类
总结:
SQLAlchemy
的声明式和经典式映射是两种强大的工具,可以帮助你轻松地把Python类映射到数据库表。选择哪种映射方式取决于你的具体需求和偏好。希望通过今天的讲解,大家能对这两种映射方式有更深入的了解,并在实际项目中灵活运用。
好了,今天的讲座就到这里,希望大家有所收获!下次再见!