SQLAlchemy 中的对象关系映射器
在 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 中持久性操作的主要接口。 它建立并维护我们的程序与数据库之间的所有对话。
建立表
以下程序在内存中创建一个表,然后将数据打印到控制台。
orm_create_table.py
#!/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
表中。
orm_add_car.py
#!/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()
方法用于在查询对象上应用过滤条件。
orm_query_like.py
#!/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
运算符。
orm_query_in.py
#!/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
这是示例的输出。
外键
在最后一个示例中,我们处理了两个表之间的关系。 建立外键。
orm_foreign_key.py
#!/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()
在此查询中,我们获得了列夫·托尔斯泰(Leo Tolstoy)撰写的所有书籍。 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
属性的book
对象。
$ ./orm_foreign_key.py
War and Peace
Anna Karenia
Jane Austen
这是示例的输出。
在 SQLAlchemy 教程的这一部分中,我们使用了 SQLAlchemy 的 ORM。