研究Python的ORM框架中的SQLAlchemy库的映射关系


Posted in Python onApril 25, 2015

前面介绍了关于用户账户的User表,但是现实生活中随着问题的复杂化数据库存储的数据不可能这么简单,让我们设想有另外一张表,这张表和User有联系,也能够被映射和查询,那么这张表可以存储关联某一账户的任意数量的电子邮件地址。这种联系在数据库理论中是典型的1-N (一对多)关系,用户表某一用户对应N条电子邮件记录。

之前我们的用户表称为users,现在我们再建立一张被称为addresses的表用于存储电子邮件地址,通过Declarative系统,我们可以直接用映射类Address来定义这张表:

>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy.orm import relationship, backref
 
>>> class Address(Base):
...   __tablename__ = 'addresses'
...   id = Column(Integer, primary_key=True)
...   email_address = Column(String, nullable=False)
...   user_id = Column(Integer, ForeignKey('users.id'))
...
...   user = relationship("User", backref=backref('addresses', order_by=id))
...
...   def __init__(self, email_address):
...     self.email_address = email_address
...
...   def __repr__(self):
...     return "〈Address('%s')〉" % self.email_address

让我们注意一下新出现的东东,首先就是user_id的ForeignKey结构,学过数据库的同学都知道ForeignKey意味着外键,这是关系型数据库的核心理论之一,即该列user_id与其外键引用的列users.id存在引用约束(constrained)关系,在数据库层面上来讲,就是表users的user_id列被表users的id列约束,值得注意的是,外键关联的必定是另外一张表的主键。

其次新出现的就是relationship()函数,这个将会告知ORM通过Address.userAddress类自身必须链接到User类。relationship()使用两个表的外键约束来判定这种链接的性质,比如说判定Address.user将会是多对一(many-to-one)关系。

另外在relationship()内还有另外一个函数称为backref(),它将提供一种用于反向查询的细节,比如说在对象User上的Address对象集是通过User.addresses属性引用,那么多对一的关系(many-to-one)反向总会是一对多关系(one-to-many)。还有对于Address.user和User.addresses的关系来说总是双向的。

假设使用了Declarative系统,那么relationship()的关系到远端类(remote class)的参数能够被指定为字符串。一旦所有的映射都被成功加载,那么这些字符串将会被计算出Python的表达式,再产生实际的参数(上文中User类的情况)。这些可以使用的字符串名字必须通过定义的基类创建好然后才被计算为实际的类参数,说白了,你字符串引用的类必须是ORM映射管理的类,然后这些类被映射完毕后,这些字符串才能被真正翻译为相应类的引用。

接下来我们举个例子同样创建用User取代Address的”addresses/user”双向关系:

class User(Base):
  # ....
  addresses = relationship("Address", order_by="Address.id", backref="user")

好吧,刚才多是直接翻译的官方文档,比较生硬,接下来我们来了解几个关于外键(Foreign Key)的小知识:

1. FOREIGN KEY 约束是大多数(但不是所有)的关系型数据库中可以链接到主键列,或者拥有UNIQUE约束的列。

2. FOREIGN KEY 能够引用多重列主键,并且其自身拥有多重列,被称为“复合外键”(composite foreign key)。其也能够引用这些列的子集(subset)。(注:这地方不太明白)

3. FOREIGN KEY 列作为对于其引用的列或者行的变化的响应能够自动更新其自身,比如CASCADE引用操作,这些都是内置于关系型数据库的功能之一。

4. FOREIGN KEY 能够引用其自身的表,这个就涉及到“自引用”(self-referential)的外键了。

5. 更多关于外键的资料可以参考Foreign Key ? Wikipedia。

最后我们需要在数据库中创建addresses表,所以我们需要通过元数据(metadata)执行我们的CREATE语句,当然会跳过我们已经创建的表(比如users):

>>> Base.metadata.create_all(engine) 
PRAGMA table_info("users")
()
PRAGMA table_info("addresses")
()
CREATE TABLE addresses (
  id INTEGER NOT NULL,
  email_address VARCHAR NOT NULL,
  user_id INTEGER,
  PRIMARY KEY (id),
   FOREIGN KEY(user_id) REFERENCES users (id)
)
()
COMMIT

到这里我们的ORM关系算是建立完成了,接下来开始新的一部分,就是如何查询关联的对象。

现在如果我们创建一个User,一个空的addresses集合将会被创建,在这里默认情况下addresses集合将会是列表类型。

>>> jack = User('jack', 'Jack Bean', 'gjffdd')
>>> jack.addresses
[]

接下来我们可以自由的添加Address对象到我们的User对象里了,在这里我们直接赋予addresses属性一个完整的列表。

>>> jack.addresses = [
...         Address(email_address='jack@google.com'),
...         Address(email_address='j25@yahoo.com')]

当我们使用双向关系时,有一点需要注意的是:在任意一端添加的元素将会自动在另外一端可见,属性的获取和改变将不通过任何SQL语句和Python对象使用一样:

>>> jack.addresses[1]
<Address('j25@yahoo.com')>
 
>>> jack.addresses[1].user
<User('jack','Jack Bean', 'gjffdd')>

让我们添加并提交Jack Bean到数据库中,现在jack对象的addresses集合拥有了两个Address成员,它们将立即被加入会话中:

>>> session.add(jack)
>>> session.commit()
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
('jack', 'Jack Bean', 'gjffdd')
INSERT INTO addresses (email_address, user_id) VALUES (?, ?)
('jack@google.com', 5)
INSERT INTO addresses (email_address, user_id) VALUES (?, ?)
('j25@yahoo.com', 5)
COMMIT

我们来查询关于Jack的信息,但是奇怪的是没有任何关于addresses的SQL语句执行:

>>> jack = session.query(User).\
... filter_by(name='jack').one() 
BEGIN (implicit)
SELECT users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.password AS users_password
FROM users
WHERE users.name = ?
('jack',)
>>> jack
<User('jack','Jack Bean', 'gjffdd')>

让我们直接来查询addresses集合吧,这里大家看到有关addresses的SQL语句执行了:

>>> jack.addresses 
SELECT addresses.id AS addresses_id,
    addresses.email_address AS
    addresses_email_address,
    addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id ORDER BY addresses.id
(5,)
[<Address('jack@google.com')>, <Address('j25@yahoo.com')>]

由上可知,当我们访问addresses集合的时候,相关SQL语句才被执行,这也是延迟加载关系(惰性加载关系, lazy loading relationship)的例子,至此addresses集合方被作为普通列表加载了。

Python 相关文章推荐
Python中的闭包实例详解
Aug 29 Python
给Python中的MySQLdb模块添加超时功能的教程
May 05 Python
Python中的深拷贝和浅拷贝详解
Jun 03 Python
Python常用的内置序列结构(列表、元组、字典)学习笔记
Jul 08 Python
深入flask之异步非堵塞实现代码示例
Jul 31 Python
Python读取指定日期邮件的实例
Feb 01 Python
详解Python数据可视化编程 - 词云生成并保存(jieba+WordCloud)
Mar 26 Python
Python实现最大子序和的方法示例
Jul 05 Python
实例详解Python装饰器与闭包
Jul 29 Python
Python实现直播推流效果
Nov 26 Python
python如何将两张图片生成为全景图片
Mar 05 Python
python3中sys.argv的实例用法
Apr 24 Python
Python的ORM框架中SQLAlchemy库的查询操作的教程
Apr 25 #Python
Python实现单词拼写检查
Apr 25 #Python
在Debian下配置Python+Django+Nginx+uWSGI+MySQL的教程
Apr 25 #Python
使用PDB简单调试Python程序简明指南
Apr 25 #Python
Python脚本判断 Linux 是否运行在虚拟机上
Apr 25 #Python
在Python中使用cookielib和urllib2配合PyQuery抓取网页信息
Apr 25 #Python
使用Python的Tornado框架实现一个一对一聊天的程序
Apr 25 #Python
You might like
在数据量大(超过10万)的情况下
2007/01/15 PHP
用来解析.htgroup文件的PHP类
2012/09/05 PHP
PHP实现基于mysqli的Model基类完整实例
2016/04/08 PHP
PHP filesize函数用法浅析
2019/02/15 PHP
jQuery学习笔记之Helloworld
2010/12/22 Javascript
asp.net 30分钟掌握无刷新 Repeater
2011/09/16 Javascript
jquery 操作iframe的几种方法总结
2013/12/13 Javascript
鼠标拖拽移动子窗体的JS实现
2014/02/25 Javascript
Javascript学习笔记之 函数篇(一) : 函数声明和函数表达式
2014/06/24 Javascript
jQuery根据ID获取input、checkbox、radio、select的示例
2014/08/11 Javascript
jQuery异步上传文件插件ajaxFileUpload详细介绍
2015/05/19 Javascript
JavaScript实现非常简单实用的下拉菜单效果
2015/08/27 Javascript
jQuery+HTML5+CSS3制作支持响应式布局时间轴插件
2016/08/10 Javascript
js前端实现图片懒加载(lazyload)的两种方式
2017/04/24 Javascript
微信小程序网络请求wx.request详解及实例
2017/05/18 Javascript
JavaScript实现简单评论功能
2017/08/17 Javascript
使用Electron构建React+Webpack桌面应用的方法
2017/12/15 Javascript
JavaScript基于对象方法实现数组去重及排序操作示例
2018/07/10 Javascript
移动端如何用下拉刷新的方式实现上拉加载
2018/12/10 Javascript
3分钟读懂移动端rem使用方法(推荐)
2019/05/06 Javascript
js实现旋转木马轮播图效果
2020/01/10 Javascript
Vue $attrs &amp; inheritAttr实现button禁用效果案例
2020/12/07 Vue.js
Python使用Matplotlib实现雨点图动画效果的方法
2017/12/23 Python
python实现zabbix发送短信脚本
2018/09/17 Python
Python中的支持向量机SVM的使用(附实例代码)
2019/06/26 Python
Python argparse模块使用方法解析
2020/02/20 Python
Python实现多线程下载脚本的示例代码
2020/04/03 Python
Myprotein蛋白粉美国官网:欧洲畅销运动营养品牌
2016/11/15 全球购物
农药学硕士毕业生自荐信
2013/09/25 职场文书
广播电视新闻学专业应届生求职信
2013/10/08 职场文书
个人现实表现材料
2014/02/04 职场文书
元旦晚会感言
2014/03/12 职场文书
小学生竞选班干部演讲稿(5篇)
2014/09/12 职场文书
奖学金发言稿(范文)
2019/08/21 职场文书
vue+iview实现手机号分段输入框
2022/03/25 Vue.js
oracle设置密码复杂度及设置超时退出的功能
2022/06/28 Oracle