SQLAlchemy 中的对象关系映射器
最后修改于 2020 年 7 月 6 日
在本 SQLAlchemy 教程中,我们将介绍 SQLAlchemy 的对象关系映射器。
对象关系映射
使用 Python 数据库 API 进行编程,可以使开发人员完全控制对数据库的直接访问。这种直接访问也有一些缺点。它们在较大的项目中尤其明显。我们将两种语言混合在一起:SQL 和 Python。结果是它使 SQL 语句更难测试和维护。在一个典型的 Web 应用程序中,除了 Python 和 SQL(或任何其他服务器端编程语言)之外,我们还有 HTML、CSS、JavaScript。 Python 和 SQL 结合在一起使项目更加复杂。在编程理论中,我们尝试将业务逻辑与数据访问和呈现分开。因此,一个将 Python 代码与 SQL 代码分离的解决方案是理想的。
另一个问题是,我们称之为 *对象关系阻抗失配*。 当面向对象编程语言或风格编写的程序使用关系数据库管理系统时,经常会遇到一组概念和技术困难。 在 Python 中,我们使用放置在对象中的数据。 在数据库系统中,数据存储在表中。 程序员需要在处理数据的两种方式之间进行转换。 这与我们应用程序的核心问题无关。
解决方案之一是 *对象关系映射*。 ORM 工具解决了上述问题。 对于 Python 语言,有几个 ORM 工具。 SQLAlchemy 是最广泛使用的工具之一。
SQLAlchemy ORM
SQLAlchemy 对象关系映射器将 (a) 用户定义的 Python 类映射到数据库表,(b) 表行映射到实例对象,以及 (c) 列映射到实例属性。 SQLAlchemy ORM 建立在 SQLAlchemy 表达式语言之上。
使用 ORM 时,我们首先配置将要使用的数据库表。 然后我们定义将映射到它们的类。 现代 SQLAlchemy 使用 *声明式* 系统来完成这些任务。 创建一个 *声明式基类*,它维护类和表的目录。 使用 declarative_base 函数创建声明式基类。
会话
完成配置后,我们创建一个会话。 *会话* 是 SQLAlchemy ORM 中持久化操作的主要接口。 它建立并维护程序与数据库之间的所有对话。
创建表
以下程序在内存中创建一个表,然后将数据打印到控制台。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
eng = create_engine('sqlite:///:memory:')
Base = declarative_base()
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
Base.metadata.bind = eng
Base.metadata.create_all()
Session = sessionmaker(bind=eng)
ses = Session()
ses.add_all(
[Car(Id=1, Name='Audi', Price=52642),
Car(Id=2, Name='Mercedes', Price=57127),
Car(Id=3, Name='Skoda', Price=9000),
Car(Id=4, Name='Volvo', Price=29000),
Car(Id=5, Name='Bentley', Price=350000),
Car(Id=6, Name='Citroen', Price=21000),
Car(Id=7, Name='Hummer', Price=41400),
Car(Id=8, Name='Volkswagen', Price=21600)])
ses.commit()
rs = ses.query(Car).all()
for car in rs:
print car.Name, car.Price
在 Cars 表中创建了八辆车。
Base = declarative_base()
使用 declarative_base 函数创建声明式基类。
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
用户定义的 Car 类映射到 Cars 表。 该类继承自声明式基类。
Base.metadata.bind = eng
声明式 Base 绑定到数据库引擎。
Base.metadata.create_all()
create_all 方法创建所有已配置的表; 在本例中,只有一个表。
Session = sessionmaker(bind=eng) ses = Session()
创建一个会话对象。
ses.add_all(
[Car(Id=1, Name='Audi', Price=52642),
Car(Id=2, Name='Mercedes', Price=57127),
...
使用 add_all 方法,我们将 Car 类的指定实例添加到会话中。
ses.commit()
更改通过 commit 方法提交到数据库。
rs = ses.query(Car).all()
我们查询 Cars 表中的所有数据。 query 方法加载 Car 类的所有实例,它的 all 方法返回查询表示的所有结果作为列表。
for car in rs:
print car.Name, car.Price
我们遍历结果集,并为所有返回的行打印两列。
$ ./orm_create_table.py Audi 52642 Mercedes 57127 Skoda 9000 Volvo 29000 Bentley 350000 Citroen 21000 Hummer 41400 Volkswagen 21600
这是示例的输出。
添加一辆新车
在下一个示例中,我们将向 Cars 表中添加一辆车。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
eng = create_engine('sqlite:///test.db')
Base = declarative_base()
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
Session = sessionmaker(bind=eng)
ses = Session()
c1 = Car(Name='Oldsmobile', Price=23450)
ses.add(c1)
ses.commit()
rs = ses.query(Car).all()
for car in rs:
print car.Name, car.Price
该脚本连接到 SQLite 数据库并将新行添加到 Cars 表中。
eng = create_engine('sqlite:///test.db')
我们连接到 SQLite test.db 数据库。
Base = declarative_base()
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
执行用户定义类到数据库表的映射。
Session = sessionmaker(bind=eng) ses = Session()
创建会话对象,它是 ORM 到数据库的中介。
c1 = Car(Name='Oldsmobile', Price=23450)
创建一个映射的 Car 类的新实例。
ses.add(c1)
add 方法将新对象添加到会话中。
ses.commit()
更改提交到数据库。
$ ./orm_add_car.py Audi 52642 Mercedes 57127 Skoda 9000 Volvo 29000 Bentley 350000 Citroen 21000 Hummer 41400 Volkswagen 21600 Oldsmobile 23450
我们验证新车是否已成功添加到数据库中。
筛选数据
会话查询的 filter 方法用于对查询对象应用过滤条件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
eng = create_engine('sqlite:///test.db')
Base = declarative_base()
Base.metadata.bind = eng
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
Session = sessionmaker(bind=eng)
ses = Session()
rs = ses.query(Car).filter(Car.Name.like('%en'))
for car in rs:
print car.Name, car.Price
该示例打印名称以 'en' 字符串结尾的汽车。
rs = ses.query(Car).filter(Car.Name.like('%en'))
filter 方法采用过滤条件,这是一个 SQL 表达式对象。 该条件是使用 like 方法创建的。
$ ./orm_query_like.py Citroen 21000 Volkswagen 21600
表中共有两辆车以 'en' 字符串结尾。
in_ 方法实现了 SQL IN 运算符。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
eng = create_engine('sqlite:///test.db')
Base = declarative_base()
class Car(Base):
__tablename__ = "Cars"
Id = Column(Integer, primary_key=True)
Name = Column(String)
Price = Column(Integer)
Session = sessionmaker(bind=eng)
ses = Session()
rs = ses.query(Car).filter(Car.Id.in_([2, 4, 6, 8]))
for car in rs:
print car.Id, car.Name, car.Price
代码示例选择并打印由 SQL IN 运算符选择的具有 Id 的行的列。
rs = ses.query(Car).filter(Car.Id.in_([2, 4, 6, 8]))
过滤条件由 in_ 方法创建。 该方法采用 Id 列表。
$ ./orm_query_in.py 2 Mercedes 57127 4 Volvo 29000 6 Citroen 21000 8 Volkswagen 21600
这是示例的输出。
外键
在最后一个示例中,我们处理两个表之间的关系。 建立了外键。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker, relationship
eng = create_engine('sqlite:///test.db')
Base = declarative_base()
class Author(Base):
__tablename__ = "Authors"
AuthorId = Column(Integer, primary_key=True)
Name = Column(String)
Books = relationship("Book")
class Book(Base):
__tablename__ = "Books"
BookId = Column(Integer, primary_key=True)
Title = Column(String)
AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))
Author = relationship("Author")
Session = sessionmaker(bind=eng)
ses = Session()
res = ses.query(Author).filter(Author.Name=="Leo Tolstoy").first()
for book in res.Books:
print book.Title
res = ses.query(Book).filter(Book.Title=="Emma").first()
print res.Author.Name
我们有 Author 和 Book 类,它们映射到 Authors 和 Books 数据库表。(创建表的 SQL 在第一章中列出)。 在两个表之间实现了外键约束。 外键由 ForeignKey 类型和 relationship 函数定义。
Books = relationship("Book")
在两个类之间建立了一对多的关系。 relationship 函数的第一个参数是我们建立关系的类的名称。 因此,作者对象将具有一个 Books 属性。
AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))
Book 类的 AuthorId 是一个外键。 它由 ForeignKey 类型定义。 它引用 Authors 表中的 AuthorId 列。
Author = relationship("Author")
此行创建 Book 类的 Author 属性。
res = ses.query(Author).filter(Author.Name=="Leo Tolstoy").first()
在此查询中,我们获取了列夫·托尔斯泰写的所有书。 filter 方法对查询应用过滤条件。 first 方法获取作者对象。
for book in res.Books:
print book.Title
我们遍历结果集并打印所有检索到的书籍。 Books 属性是使用 relationship 函数创建的。
res = ses.query(Book).filter(Book.Title=="Emma").first() print res.Author.Name
此查询返回标题为 Emma 的作者。 该查询返回书对象,该对象具有内置的 Author 属性。
$ ./orm_foreign_key.py War and Peace Anna Karenia Jane Austen
这是示例的输出。
在本 SQLAlchemy 教程中,我们使用了 SQLAlchemy 的 ORM。