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。