通过提供用于操作数据库表的类和对象,对象关系映射工具有助于提高生产率。Python 最好的对象关系映射工具是 SQLObject —— 一个开放源码项目,它几乎完成编程数据库所需的所有操作。sqlobject支持数据库类型有sqlite3,mysql,PostgreSQL等等
1、安装和设置 SQLObject
SQLObject 具有一个 setup.py 文件,安装方式与其他任何 Python 包一样。直接python setup.py即可,当然你也可以进一步使用setup tools即可:easyinstall.exe sqlobject即可,呵呵,十分简单方便哦。
2、数据库准备(本文使用mysql为例)
要实际使用 SQLObject,需要设置数据库包以及这种数据库的 Python 接口。SQLObject 连接多种数据库,其中包括三个大的开放源码产品:MySQL、PostgreSQL 和无服务器 SQLite。
——————————-
mysql> use mysql;
Database changed
mysql> create database sqlobject_demo;
Query OK, 1 row affected (0.00 sec)
mysql> grant all privileges on sqlobject_demo to ‘dbuser’@’localhost’
identified by ‘dbpassword’;
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
——————————-
3、连接数据库
如果想让应用程序使用 SQLite 数据库,则需要将数据库文件的路径写入位于sqlobject.sqlite 包的 SQLite 连接构建器中。如果数据库文件不存在,QLObject 将告诉 SQLite 创建一个,代码如下:
——————————-
import sqlobject
from sqlobject.sqlite import builder
conn = builder()(‘sqlobject_demo.db’)
——————————-
如果使用的是 MySQL 或带有服务器的其他数据库,则将数据库连接信息传递到连接构建器中。上面提供了在上一节创建的 MySQL 数据库的示例。
——————————-
import sqlobject
from sqlobject.mysql import builder
conn = builder()(user=’dbuser’, passwd=’dbpassword’,
host=’localhost’, db=’sqlobject_demo’)
——————————-
不管连接哪种数据库,连接代码都应该放置在一个名称类似 Connection.py 的文件中,且该文件存储在一些通常可访问的位置中。这样,可以导入您定义的所有类,并使用已经构建的conn 对象。conn 变量将包含所有与数据库相关的详细信息。
注意:SQLObject 的一些特性不可用于 SQLite 或 MySQL。
4、定义模式
SQLObject 使得操作数据库表变得容易。看第一个简单的例子,考虑一个电话簿应用程序的由单个表组成的数据库模式,如下所示。
字段 类型 说明
id Int 主键
number String “(###) ###-####”字符串格式;应该惟一
owner String 这是谁的号码?
last_call Date 用户最后一次呼叫该号码是什么时候?
该表的 SQL 类似如下:
CREATE TABLE phone_number (
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(14) UNIQUE,
owner VARCHAR(255),
last_call DATETIME,
notes TEXT
)
使用 SQLObject,不需要编写该 SQL 代码。通过定义 Python 类来定义该数据表。该代码将进入名为 PhoneNumber.py 的文件中。
——————————-
import sqlobject
from Connection import conn
class PhoneNumber(sqlobject.SQLObject):
_connection = conn
number = sqlobject.StringCol(length=14, unique=True)
owner = sqlobject.StringCol(length=255)
lastCall = sqlobject.DateTimeCol(default=None)
PhoneNumber.createTable(ifNotExists=True)
——————————-
定义表的类还有一组定义表字段的成员。SQLObject 提供了 StringCol、BoolCol 等等 —— 一个类对应一种数据库字段类型。
createTable() 方法第一次运行时,SQLObject 将创建一个名为 phone_number 的表。然后,它将只使用该表,因为您将 ifNotExists 设置为 True 来调用该方法。
最后注意,无需在 PhoneNumber 中为 id 字段创建字段对象。因为 SQLObject 总是需要该字段对象,所以它总会创建一个。
5、处理CRUD
著名的缩写词 CRUD 代表对数据库行进行的四种操作:Create、Read、Update 和 Delete。定义了与数据库表对应的类之后,SQLObject 将对表行的操作表示为对类及其实例的操作。
创建
要创建数据库行,需创建对应类的实例,代码如下:
——————————-
>>> from PhoneNumber import PhoneNumber
>>> myPhone = PhoneNumber(number=’(415) 555-1212′,
owner=’Leonard Richardson’)
——————————-
现在 phone_number 表有一个存储我的姓名的行。PhoneNumber 构造函数将表列的值作为关键字参数。它使用您提供的数据创建一行 phone_number。如果由于某种原因,数据不能进入数据库,则构造函数抛出一个异常。当电话号码无效时,会发生如下情况:
——————————-
>>> badPhone = PhoneNumber()
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
…
TypeError: PhoneNumber() did not get expected keyword argument number
如果电话号码已经在数据库中,将会看到:
——————————-
>>> duplicatePhone = PhoneNumber(number=”(415) 555-1212″)
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
…
TypeError: PhoneNumber() did not get expected keyword argument owner
读取(和查询)
SQLObject 类的实例的所有字段可用作成员。这与其他一些将数据库行当作字典的数据库映射工具相反。因此,对于数据库行中的每个字段,PhoneNumber 对象都具有一个成员。
——————————-
>>> myPhone.id
1
>>> myPhone.owner
‘Leonard Richardson’
>>> myPhone.number
‘(415) 555-1212′
>>> myPhone.lastCall == None
True
但如何检索数据库中已经存在的 PhoneNumber 对象呢?需要对数据库执行查询来获取。这就是 SQLObject 的查询构造工具包(该软件包最有趣的特性之一)发挥作用的地方。它允许将 SQL 查询表示为 Python 对象链。如果您熟悉 Java™ 编程语言的对象关系包的话,它与可以对 Torque 中 Criteria 对象执行的操作一样。
您定义的每个 SQLObject 类都有一个 select() 方法。该方法接受一个定义查询的对象,返回与该查询匹配的项列表。例如,下面这个方法调用返回包含数据库中第一个电话号码的列表:
——————————-
>>> PhoneNumber.select(PhoneNumber.q.id==1)
<sqlobject.main.SelectResults object at 0xb7b76cac>
>>> PhoneNumber.select(PhoneNumber.q.id==1)[0]
<PhoneNumber 1 number=’(415) 555-1212′ lastCall=None
owner=’Leonard Richardson’>
——————————-
PhoneNumber.q.id 指明想要对 phone_number 表的 id 字段运行查询。SQLObject 过载比较操作符(==、!=、<、>= 等等)来执行除布尔表达式之外的查询。表达式PhoneNumber.q.id==1 是与 id 字段值为 1 的每行相匹配的查询。
——————————-
>>> PhoneNumber.select(PhoneNumber.q.id < 100)[0]
<PhoneNumber 1 number=’(415) 555-1212′ lastCall=None
owner=’Leonard Richardson’>
>>> PhoneNumber.select(PhoneNumber.q.owner==’Leonard Richardson’).count()
1
>>> PhoneNumber.select(PhoneNumber.q.number.startswith(‘(415)’)).count()
1
——————————-
可以使用 SQLObject 的 AND 和 OR 函数来组合查询子句:
——————————-
>>> from sqlobject import AND, OR
>>> PhoneNumber.select(AND(PhoneNumber.q.number.startswith(‘(415)’),
>>> PhoneNumber.q.lastCall==None)).count()
1
——————————-
下列查询获取一年中呼叫过的所有人以及从未呼叫过的所有人:
——————————-
>>> import datetime
>>> oneYearAgo = datetime.datetime.now() – datetime.timedelta(days=365)
>>> PhoneNumber.select(OR(PhoneNumber.q.lastCall==None,
… PhoneNumber.q.lastCall < oneYearAgo)).count()
1
——————————-
更新
如果更改 PhoneNumber 对象的一个成员,则该更改被自动镜像映射至数据库:
——————————-
>>> print myPhone.owner
Leonard Richardson
>>> print myPhone.lastCall
None
>>> myPhone.owner = “Someone else”
>>> myPhone.lastCall = datetime.datetime.now()
>>> #Fetch the object fresh from the database.
>>> newPhone = PhoneNumber.select(PhoneNumber.q.id==1)[0]
>>> print newPhone.owner
Someone else
>>> print newPhone.lastCall
2005-05-22 21:20:24.630120
注意:SQLObject 不允许更改对象的主键。通常最好是让 SQLObject 来管理表的 id 字段,即使是您不使用 SQLObject 时碰巧用另一个字段作为主键。
删除
删除特定行对象的方法是将其 ID 传递到其类的 delete() 方法中:
——————————-
>>> query = PhoneNumber.q.id==1
>>> print “Before:”, PhoneNumber.select(query).count()
Before: 1
>>> PhoneNumber.delete(myPhone.id)
>>> print “After:”, PhoneNumber.select(query).count()
After: 0
——————————-
6、验证和转换数据
SQLObject 通过允许定义验证和转换入站数据的钩子方法来解决这个问题。可以为表中的每个字段定义一个方法。字段的钩子方法命名为 _set_[field name](),不管是作为 create 操作还是 update 操作的一部分,每当要为该字段设置一个值时,都会调用该方法。钩子方法应(可选)将入站值转换为可接受格式,然后设置该值。否则,它应抛出异常。要实际设置一个值,该方法需要调用 SQLObject 方法 _SO_set_(field name)。
【PhoneNumber 的 _set_number() 方法。如果电话号码完全没有格式化,比如 4155551212,则该方法将该数字格式化为 (415) 555-1212。否则,如果数字格式不正确,该方法会抛出 ValueError。正确格式化的电话号码 —— 或者是转换为正确格式的电话号码 —— 被正确传递给 SQLObject 的 _SO_set_number() 方法。】
——————————-
import re
def _set_number(self, value):
if not re.match(‘\([0-9]{3}\) [0-9]{3}-[0-9]{4}’, value):
#It’s not in the format we expect.
if re.match(‘[0-9]{10}’, value):
#It’s totally unformatted; add the formatting.
value = “(%s) %s-%s” % (value[:3], value[3:6], value[6:])
else:
raise ValueError, ‘Not a phone number: %s’ % value
self._SO_set_number(value)
——————————-
7、定义表之间的关系
真正的数据库应用程序通常具有多个相关表,SQLObject 允许将表之间的关系定义为外键。作为演示,我们将一个小的数据库规范化(normalization)应用于上一示例,将 PhoneNumber 的 owner 字段分割到单独的 person 表中。清单 14 所示的代码保存在名为 PhoneNumberII.py 的文件中。
——————————-
import sqlobject
from Connection import conn
class PhoneNumber(sqlobject.SQLObject):
_connection = conn
number = sqlobject.StringCol(length=14, unique=True)
owner = sqlobject.ForeignKey(‘Person’)
lastCall = sqlobject.DateTimeCol(default=None)
class Person(sqlobject.SQLObject):
_idName=’fooID’
_connection = conn
name = sqlobject.StringCol(length=255)
#The SQLObject-defined name for the “owner” field of PhoneNumber
#is “owner_id” since it’s a reference to another table’s primary
#key.
numbers = sqlobject.MultipleJoin(‘PhoneNumber’, joinColumn=’owner_id’)
Person.createTable(ifNotExists=True)
PhoneNumber.createTable(ifNotExists=True)
——————————-
该 PhoneNumber 类具有与旧类相同的成员,但它的 owner 成员是对 person 表的主键的引用,而不是对 phone_number 表中字符串列的引用。这使得表示具有两个电话号码的个人成为可能:
——————————-
>>> from PhoneNumberII import PhoneNumber, Person
>>> me = Person(name=’Leonard Richardson’)
>>> work = PhoneNumber(number=”(650) 555-1212″, owner=me)
>>> cell = PhoneNumber(number=”(415) 555-1212″, owner=me)
——————————-
Person 的 numbers 成员,一个 SQLObject MultipleJoin,使得基于 person 到 phone_number 的连接进行查询变得容易:
——————————-
>>> for phone in me.phoneNumbers:
… print phone.number
…
(650) 555-1212
(415) 555-1212
——————————-
同样,SQLObject 允许使用 MultipleJoin 类进行多对多连接的查询。
8、将 SQLObject 用于现有表
SQLObject 的一个常见用途是为另一个应用程序创建的数据库提供 Python 接口。SQLObject 有多个特性可用于实现这一点。
数据库内省
如果正在使用数据库中已经存在的表,则不需要在 Python 中定义列。SQLObject 可以通过数据库内省来提取它需要的信息。例如,清单 17 中的代码保存在 PhoneNumberIII.py 中。
——————————-
import sqlobject
from Connection import conn
class PhoneNumber(sqlobject.SQLObject):
_connection = conn
_fromDatabase = True
——————————-
该类将使用现有 phone_number 数据表的属性。您可以与它交互,就好像已经手动定义了该类及其所有列一样,如前面的示例所示。使用 SQLObject,只需要编写表定义一次 —— 用 SQL 还是用 Python 编写就取决于您了。
但是,该特性又带来了数据库的选择问题。例如,该特性完全不能用于 SQLite。它基本上能用于 MySQL,但不能提取外键关系。如果使用的是 MySQL,而且想要为表定义外键,则需要在从数据库中加载模式之后,编写代码定义这些字段。
命名约定
上一部分中的代码假设现有表符合 SQLObject 的命名约定(例如,表的主键字段名为 id,且列名中的词用下划线分隔)。表的命名约定在 Style 类中定义。
SQLObject 提供了一些与常见数据库命名约定对应的 Style 类。例如,如果列名类似 likeThis 而非 like_this,则可以使用 MixedCaseStyle:
——————————-
import sqlobject
from sqlobject.styles import MixedCaseStyle
from Connection import conn
class PhoneNumber(sqlobject.SQLObject):
_connection = conn
_fromDatabase = True
_style = MixedCaseStyle
——————————-
如果没有预包装的 Style 类符合您的需要,那么您可以定义 Style 基类的子类,并定义自己的命名约定。在最坏的情况下,如果表的字段名分配得毫无道理,则可以逐个命名每个字段。
关于 SQLObject 限制
SQLObject 想让您用面向对象的方式而非关系方式进行思考。这有利于您的理解和您的编程生产率,但不利于性能。毕竟,数据库仍是关系型的。如何标记呼叫过的每个电话号码?使用 SQL,您将使用单个 UPDATE 命令。使用 SQLObject,您需要迭代通过整个结果集,并修改每个对象的 last_call 成员,这是非常低效的。
SQLObject 为开发人员时间牺牲了处理器时间。这通常是好的交易,但甚至在简单的应用程序中,您也可能需要下降一个级别到达 Python 数据库接口,为一些关键路径的操作编写原始 SQL。