SQLAlchemy 1.3文档中文版 - 对象关系指南
时间:2022-08-10 16:30:00
SQLAlchemy 1.3文档中文版 - 对象关系指南(Object Relational Tutorial)
中文翻译:郭夫子(3744)jetko@sina.com)
英文原文地址:https://docs.sqlalchemy.org/en/13/orm/tutorial.html
SQLAlchemy对象关系映射器(Object Relational Mapper)它提供了一种定义用户的方法Python类和数据库的表以及这些类的实例(对象)与相应表中的行相关的方法。它包括一个名字unit of work该系统在对象及其相关行之间透明同步状态变化。它还包括另一个系统,根据用户定义类与类之间的关系查询数据库。
ORM不同于SQLAlchemy表达式语言(Expression Language),ORM是基于SQLAlchemy表达式语言构建的。SQLAlchemy表达语言(在SQL Expression Language Tutorial解释)提供的机制直接显示关系数据库的原始结构,无需修改ORM它提供了一种高级抽象的使用模式,由表达式语言构建。
尽管ORM和表达语言的使用模式有重叠,乍看起来二者很相似,实际不然。一是用户定义domain model处理数据的结构和内容。domain model底层存储模型storage model持久性和刷新透明度。另一种是文字schema和SQL表达角度处理。这些表达式由数据库逐个组合成新闻。
只能使用成功的应用程序ORM对象关系映射器射器。在高级情况下使用ORM在某些需要特定数据库交互的区域,构建的应用程序偶尔会直接使用表达式语言。
采用以下教程doctest格式,即 >>>
行表示可以在Python命令提示下键入的内容,随后的文本表示预期的返回值。
Version Check版本检查
快速检查,确认我们至少使用它SQLAlchemy1.3版本:
>>> import sqlalchemy >>> sqlalchemy.__version__ 1.3.0
Connecting连接
在本教程中,我们将使用只存在于内存中的一个SQLite数据库。我们用 create_engine()
来连接:
>>> from sqlalchemy import create_engine >>> engine = create_engine('sqlite:///:memory:', echo=True)
echo
标志是设置SQLAlchemy日志的快捷方式, 它是利用Python标准库 logging
模块实现。启用后,我们将看到所有生成SQL显示。如果您正在阅读本教程并希望生成更少的输出,请将其设置为 False
。本教程将格式化SQL把它放在弹窗后面,这样它就不会碍眼了;单击SQL链接可以查看正在生成的内容。
create_engine()
的返回值是 Engine
一个例子, 根据控制数据库细节的方言,它表示数据库的核心接口dialect和在用的DBAPI适配。本例中SQLite方言提供解释指令Python内置的sqlite3
模块。
Lazy Connecting懒连接 第一次被create_engine()返回的Engine, 实际上还没有连接到数据库;它只有在第一次被要求执行数据库的任务时才能连接。
Engine.execute()
或者Engine.connect()
第一次调用这种方法时,Engine
真正建立在数据库中的数据库DBAPI
然后用于发送连接SQL。当使用ORM时, 我们通常不直接使用它来创建它Engine
; 就像我们即将看到的,Engine
是被ORM幕后使用。
参见
Database Urls
- 包含create_engine()链接到各种数据库的示例和更多信息。
Declare a Mapping声明一个映射
使用ORM在配置过程中,首先描述我们要处理的数据库表,然后定义类,类将被映射到这些表中。SQLAlchemy这两个任务都被称为Declarative通常一起执行机制。Declarative这样我们就可以创建包含指令的真实数据库表,这些指令描述该映射到。
Declarative机制映射类参照一个基类来定义。该基类维护与该基类相关的一系列类和表。这个基类是declarative base class。在正常导入的模块中,我们的应用程序通常只有一个基类的例子。我们使用它declarative_base()
如下:
>>> from sqlalchemy.ext.declarative import declarative_base >>> Base = declarative_base()
由于我们已经有了一个基本类别,我们可以根据它来定义任何数量的映射类别。我们将从存储我们应用程序的终端用户的单表开始。我们映射到一个名字User新类别。在这一类中,我们定义表的细节,主要是表名、列名和数据类型:
>>> from sqlalchemy import Column, Integer, String >>> class User(Base): ... __tablename__ = 'users' ... ... id = Column(Integer, primary_key=True) ... name = Column(String) ... fullname = Column(String) ... nickname = Column(String) ... ... def __repr__(self): ... return "" % ( ... self.name, self.fullname, self.nickname)
小提示
User 类定义了一个 __repr__()方法, 注意这是可选的; 我们只在本教程中这样做,为的是我们的示例能漂亮地显示格式化过的User对象。
一个使用Declarative创建的类至少需要一个__tablename__
属性,和至少一列Column
。该列是主键的一部分。SQLAlchemy自身从不对类指向的表做推测,并且它没有内置的对名字、数据类型或约束条件的规范。但这不意味着需要样例;相反,它鼓励你利用helper辅助函数和mixin类去创建自己的自动化规范。这些会在Mixin and Custom Base Classes
中详细介绍。
当类创建时,Declarative把所有的Column对象替换为特殊的Python访问器-描述符。这个过程被称为instrumentaion插桩。被插桩的映射类可提供给我们在一个SQL上下文中引用表以及持久化和从库中加载列中的值的手段。
除开映射过程对我们的类做的这些动作,这个类仍是个正常的Python类,我们可以对它定义任意多的普通属性和方法。
想了解为何需要主键,请看How do I map a table that has no primary key?.
Create a Schema创建schema
对于通过Declarative机制创建的User类,我们已经定义了表的信息,即表的元数据metadata。SQLAlchemy中表示特定表的这些信息的对象叫Table
对象,Declarative已经给我们做了一个。我们可以通过考察__table__
属性来看看这个对象:
>>> User.__table__
Table('users', MetaData(bind=None),
Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
Column('name', String(), table=<users>),
Column('fullname', String(), table=<users>),
Column('nickname', String(), table=<users>), schema=None)
当我们声明类时,Declarative使用了一个Python元类以便在类声明完成后执行额外的动作;在这个阶段,它按照我们的定义创建了Table
对象,并创建了一个Mapper
对象,用Mapper
把Table
对象跟类关联起来。Mapper
对象是个幕后英雄,正常情况下我们不需要直接跟它打交道(尽管它可以在我们需要之时提供丰富的有关映射的信息)。
Table
对象是一个更大的集合Metadata
的一员。当使用Declarative时,使用Declarative基类的.metadata
属性时,这个对象可用。
metadata
是个注册器,它能发送一套有限的schema生成指令到数据库。因为我们的SQLite数据库实际上还没有users
表,我们可以使用Metadata
来发送CREATE TABLE语句到数据库来创建所有还不存在的表。下面,我们调用MetaData.create_all()
方法,传进去Engine
作为数据库连接的源。我们将看到首先发出了特别的指令检查users
表是否存在,跟着才是真正的CREATE TABLE
语句:
>>> Base.metadata.create_all(engine
)
SELECT ...
PRAGMA main.table_info("users")
()
PRAGMA temp.table_info("users")
()
CREATE TABLE users (
id INTEGER NOT NULL, name VARCHAR,
fullname VARCHAR,
nickname VARCHAR,
PRIMARY KEY (id)
)
()
COMMIT
Classical Mappings 经典映射
尽管强烈推荐使用Declarative机制,但它不是使用SQLAchemy ORM所必需的。Declarative之外,直接利用mapper()函数可以将任意纯Python类映射到任意的表;这种不常见的用法在经典映射Classical Mappings中有描述。
Minimal Table Descriptions vs. Full Descriptions最小表描述和完整描述
熟悉CREATE TABLE语法的用户可能会注意到VARCHAR列没有定义length;在SQLite 和PostgreSQL上,这是合法的数据类型,但在其他数据库上不允许。所以,如果是在那些数据库上跑本教程,你想使用SQLAlchemy发出CREATE TABLE的话,要给String
数据类型一个length值,像下面一样:
Column(String(50))
String的length字段,以及类似的Integer,Numeric等的precision/scale字段,除了在创建表时用到,SQLAlchemy不会使用。
另外,Firebird和Oracle需要序列来生成新的主键标识符,而sqlAlchemy在没有得到指示的情况下不会生成或推测这些标识符。为此,您使用 Sequence
结构:
from sqlalchemy import Sequence
Column(Integer, Sequence('user_id_seq'), primary_key=True)
因此一个通过Declarative映射生成的,完整的,万无一失的Table
是这样:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
name = Column(String(50))
fullname = Column(String(50))
nickname = Column(String(50))
def __repr__(self):
return "" % (
self.name, self.fullname, self.nickname)
我们单独列出这个更详细的表定义,是为了突出最小构造和有更严格需求的表构造的区别。最小构造经过调整,主要用于Python程序内;而更严格的表构造用于在特定后端上发送CREATE TABLE语句。
Create an Instance of the Mapped Class创建映射类的实例
映射建好了,我们现在创建和查看User
对象:
>>> ed_user = User(name='ed', fullname='Ed Jones', nickname='edsnickname')
>>> ed_user.name
'ed'
>>> ed_user.nickname
'edsnickname'
>>> str(ed_user.id)
'None'
尽管我们没有在构造器中指定id属性,在我们访问它时,它仍给出了一个值None
(这与Python正常行为不同,正常访问一个未定义属性会抛出AttributeError
异常)。SQLAlchemy的instrumentation插桩正常会在列映射的属性第一次被访问时给它生成这个缺省值。对于我们实际上已经指定了值的属性,instrumentation机制跟踪那些指定值,用在最终的INSERT语句中,发送给数据库。
__init__() 方法
我们用Declarative机制定义的类,已经有了一个构造器(即__init__()方法)。该方法自动接收匹配到列的关键字名称。我们还可自由定义自己喜欢的任意的显式__init__()方法,这将覆盖Declarative提供的缺省方法。
Creating a Session创建会话
我们现在已经准备好跟数据库对话了。ORM与数据库的抓手是Session
。在我们最初创建应用时,在create_engine()
语句同级别,我们定义一个Session
类。该类是创建新Session
对象的工厂:
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=engine)
某些情况下如果你定义模块级别的对象时,你的应用还没有Engine
,只要这样做:
>>> Session = sessionmaker()
然后,当你用create_engine()
创建引擎时,用configure()
把它连接到Session
:
>>> Session.configure(bind=engine) # engine可用时
这个定制的Session类会创建新的Session
对象,新对象会绑定到数据库。调用sessionmaker
时,也可以定义其他的事务性特性。后续章节会讲这些。然后,每当你需要跟数据库对话时,你就实例化一个Session
:
>>> session = Session()
上面的Session
与启用了SQLite的Engine
相关联,但它还未打开任何连接。当第一次用它时,它从Engine
维护的连接池中取出一个连接,保持住,直到我们提交所有变更并/或关闭会话对象。
Session Lifecycle Patterns会话生命期模式
何时创建会话这个问题很大程度上取决于正在创建的应用的类型。记住,Session只是你的对象的工作空间,一个特定数据库连接的本地空间。如果你把一个应用线程当成饭局上的一个来客,Session就是客人的餐盘,它盛的对象就是食物(数据库是厨房?)!关于此话题更多信息,可访问"When do I construct a Session, when do I commit it, and when do I close it?"
Adding and Updating Objects添加和更新对象
为了持久化User
对象,我们add()
它到Session
:
>>> ed_user = User(name='ed', fullname='Ed Jones', nickname='edsnickname')
>>> session.add(ed_user)
现在,我们说实例是挂起(pending)状态;还没有发送SQL,对象也没有变成数据库里的一行数据。如果需要,Session
可以立刻使用flush进程发送SQL来持久化EdJones
。如果查询数据库的Ed Jones
记录,所有的pending信息将先被flush,随后查询会立即发出。
例如,下面我们创建一个新的Query
对象,该对象载入User
实例。我们用name属性为ed来过滤,并说明我们只想要完整记录的第一个结果。返回结果是个User
实例,跟我们刚添加的一样:
>>> our_user = session.query(User).filter_by(name='ed').first()
>>> our_user
<User(name='ed', fullname='Ed Jones', nickname='edsnickname')>
实际上,Session
确认了返回的行记录跟它的内部对象映射中的是同一行,所以我们事实上取回了我们刚刚添加的那同一个实例:
>>> ed_user is our_user
True
此处起作用的ORM机制叫同一性映射(identity map),它保证了在一个Session内对某特定行的所有操作操作的都是同一组数据。一旦带有特定主键的对象存在于Session中,该会话上所有针对该主键的SQL查询会返回同一个Python对象;如果试图在会话中加入第二个主键相同且已持久化的对象,机制会抛出异常。
我们可以马上用add_all()
加入更多的User
对象:
>>> session.add_all([
... User(name='wendy', fullname='Wendy Williams', nickname='windy'),
... User(name='mary', fullname='Mary Contrary', nickname='mary'),
... User(name='fred', fullname='Fred Flintstone', nickname='freddy')])
我们觉得Ed的昵称不太酷,让我们改掉:
>>> ed_user.nickname = 'eddie'
Session
一直在关注着,比如,它知道了Ed Jones
被改了:
>>> session.dirty
IdentitySet([])
也知道三个新User
对象处于pending状态:
>>> session.new
IdentitySet([<User(name='wendy', fullname='Wendy Williams', nickname='windy')>,
<User(name='mary', fullname='Mary Contrary', nickname='mary')>,
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')>])
我们告诉Session
我们想发布对数据库的所有剩余更改,并提交整个过程中一直在进行的事务。我们可以用commit()
。Session
发出UPDATE
语句以反映“Ed”的昵称变化,以及INSERT
语句反映我们添加的三个新User
对象:
>>> session.commit()
commit()
把尚存的变更刷入数据库,并提交事务。会话引用的连接资源归还到连接池。此会话的后续操作将在一个新的事务中进行,新事务会在首次需要时再次申请连接资源。
我们看看Ed的id
属性,先前是None
,现在有值了:
>>> ed_user.id
1
Session
在库中插入新行后,所有新生成的id和数据库产生的缺省值在实例上就可用了,或是立即可用,或是在第一次访问时加载。在本例中,整行在被访问时重新加载了,因为我们发出commit()
后开始了一个新事务。SQLAlchemy缺省在新事务中首次访问前一次事务取得的数据时会进行数据刷新,如此最新的状态才可用。重新加载的级别是可配置的,见Using the Session中所述。
Session Object States 会话对象状态
当User对象在“Session外”、“Session内但无主键”、“真正被插入表中”之间变化时,它对应四个可用的“对象状态”中的三个-暂时(transient)、挂起(pending)、持久(persistent)。搞清楚这些状态和含义很有好处-请一定阅读Quickie Intro to Object States。
Rolling Back回滚
因为Session
工作在事务中,我们也可以回滚所做的变更。让我们做两个要回滚的变更;把ed_user
的用户名改成Edwardo
:
>>> ed_user.name = 'Edwardo'
再添加个错误用户fake_user
:
>>> fake_user = User(name='fakeuser', fullname='Invalid', nickname='12345')
>>> session.add(fake_user)
查询会话,可以看到操作已经刷到当前的事务中了:
>>> session.query(User).filter(User.name.in_(['Edwardo', 'fakeuser'])).all()
[<User(name='Edwardo', fullname='Ed Jones', nickname='eddie')>, <User(name='fakeuser', fullname='Invalid', nickname='12345')>]
回滚,可以看到ed_user的名字已经改回ed,fake_user也从会话中消失:
>>> session.rollback()
>>> ed_user.name
u'ed'
>>> fake_user in session
False
发送一个SELECT来显示数据库的变更:
>>> session.query(User).filter(User.name.in_(['ed', 'fakeuser'])).all()
[<User(name='ed', fullname='Ed Jones', nickname='eddie')>]
Querying查询
Query
对象由Session
的query()
方法生成。这个函数接收不定数量的参数,可以是类和描述符的任意组合。下面,我们查看一个加载了User
实例的Query
。当在迭代上下文中评估时,返回一个User
对象的列表:
>>> for instance in session.query(User).order_by(User.id):
... print(instance.name, instance.fullname)
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
Query
也接受被ORM插桩的描述符作为参数。每当多个类实体或基于列的实体作为query()
函数的参数时,返回值是元组:
>>> for name, fullname in session.query(User.name, User.fullname):
... print(name, fullname)
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
Query
返回的元组是命名元组,由keyedTuple
类提供,可被当做普通Python对象处理。其名称与属性名及类名相同:
>>> for row in session.query(User, User.name).all():
... print(row.User, row.name)
<User(name='ed', fullname='Ed Jones', nickname='eddie')> ed
<User(name='wendy', fullname='Wendy Williams', nickname='windy')> wendy
<User(name='mary', fullname='Mary Contrary', nickname='mary')> mary
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')> fred
你可以使用lable()
控制单个列的名称。lable()
在任何ColumnElement
派生对象以及映射到ColumnElement
派生对象的类属性(如User.name
)上都可用:
>>> for row in session.query(User.name.label('name_label')).all():
... print(row.name_label)
ed
wendy
mary
fred
假设query()
调用返回了多个实体,可以用aliased()
来控制一个完整实体(如User
)的名称:
>>> from sqlalchemy.orm import aliased
>>> user_alias = aliased(User, name='user_alias')
SQL>>> for row in session.query(user_alias, user_alias.name).all():
... print(row.user_alias)
<User(name='ed', fullname='Ed Jones', nickname='eddie')>
<User(name='wendy', fullname='Wendy Williams', nickname='windy')>
<User(name='mary', fullname='Mary Contrary', nickname='mary')>
<User(name='fred', fullname='Fred Flintstone', nickname='freddy')>
Query
的基本操作包括发送LIMIT和OFFSET,利用Python的数组切片可很方便做到,通常还与ORDER BY一起使用:
>>> for u in session.query(User).order_by(User.id)[1:3]:
... print(u)
<User(name='wendy', fullname='Wendy Williams', nickname='windy')>
<User(name='mary', fullname='Mary Contrary', nickname='mary')>
还有过滤结果操作,可以用filter_by()
(加关键字参数)
>>> for name, in session.query(User.name).\
... filter_by(fullname='Ed Jones'):
... print(name)
ed
或者用filter()
来完成。filter()
可以使用更灵活的SQL表达式语言构建。这就让你可以在映射类上使用常规的Python运算符操作类级别的属性。
>>> for name, in session.query(User.name).\
... filter(User.fullname=='Ed Jones'):
... print(name)
ed
Query
对象是完全生成型的,意思是,大多数方法的调用会返回一个新的Query
对象,在新对象上又可以叠加更多条件。例如,用全名“Ed Jones”查询用户“Ed”,你可以调用filter()两次,这跟使用AND条件匹配一样。
>>> for user in session.query(User).\
... filter(User.name=='ed').\
... filter(User.fullname=='Ed Jones'):
... print(user)
<User(name='ed', fullname='Ed Jones', nickname='eddie')>
Common Filter Operators常用筛选器运算符
下面是filter()
中一些最常用的运算符汇总:
-
equals
:`query.filter(User.name == 'ed')`
-
not equals
:query.filter(User.name != 'ed')
-
LIKE
:query.filter(User.name.like('%ed%'))
注意 ColumnOperators.like()生成的LIKE运算符,在某些后端设备上大小写不敏感,在其他设备又是敏感的。对于保证不区分大小写的比较,请使用ColumnOperators.ilike()。
-
ILIKE
(case-insensitive LIKE):`query.filter(User.name.ilike('%ed%'))`
注意 多数后端设备不直接支持ILIKE。对这些设备,ColumnOperators.ilike()运算符生成一个表达式。该表达式是LIKE和SQL函数LOWER(应用到操作数上)的组合。
-
IN
:query.filter(User.name.in_(['ed', 'wendy', 'jack'])) # works with query objects too: query.filter(User.name.in_( session.query(User.name).filter(User.name.like('%ed%')) )) # use tuple_() for composite (multi-column) queries from sqlalchemy import tuple_ query