ZetCode

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 中持久化操作的主要接口。 它建立并维护程序与数据库之间的所有对话。

创建表

以下程序在内存中创建一个表,然后将数据打印到控制台。

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

我们有 AuthorBook 类,它们映射到 AuthorsBooks 数据库表。(创建表的 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。