2.5 使用ORM(对象关系映射)操作数据库
ORM是对象关系映射(object relational mapping,或O/RM)的简称,用于实现面向对象编程语言中不同类型系统的数据之间的转换。从实现效果上来看,ORM其实是创建了一个可以在编程语言里使用的“虚拟对象数据库”。从另外的角度来看,在面向对象编程语言中使用的是对象,而在对象中的数据需要保存到数据库中,或数据库中的数据用来构造对象。在从数据库中提取数据并构造对象或将对象数据存入数据库的过程中,有很多代码是可以重复使用的,如果这些重复的功能完全自己实现那就是“重复造轮子”的低效率工作。在这种情况下就诞生了ORM,它使得从数据库中提取数据来构造对象或将对象数据保存(持久化)到数据库中实现起来更简单。在本节的内容中,将详细讲解在Python语言中使用ORM(对象关系映射)操作数据库的知识。
2.5.1 Python和ORM
在现实应用中有很多不同的数据库工具,并且其中的大部分系统都包含Python 接口,能够使开发者更好地利用它们的功能。但是这些不同数据库工具系统的唯一缺点是需要了解SQL语言。如果你是一个更愿意操纵Python对象而不是SQL 查询的程序员,并且仍然希望使用关系数据库作为程序的数据后端,那么可能会更加倾向于使用ORM。
这些 ORM 系统的创始人将纯SQL语句进行了抽象化处理,将其实现为Python中的对象,这样开发者只要操作这些对象就能完成与生成SQL语句相同的任务。一些软件系统也允许一定的灵活性,可以让我们执行几行SQL语句。但是大多数情况下,都应该避免使用普通的SQL语句。
在ORM系统中,数据库表被转化为Python 类,其中的数据列作为属性,而数据库操作则会作为方法。读者应该会发现,让应用支持ORM与使用标准数据库适配器有些相似。由于ORM 需要代替你执行很多工作,所以一些事情变得更加复杂,或者需要比直接使用适配器更多的代码行。不过,值得大家欣慰的是,这一点额外工作可以获得更高的开发效率。
在开发过程中,最著名的Python ORM是SQLAlchemy(http://www.qlalchemy.org)和SQLObject(http://sqlobject.org)。另外一些常用的Python ORM 还包括:Storm、PyDO/PyDO2、PDO、Dejavu、Durus、QLime和ForgetSQL。基于Web 的大型系统也会包含它们自己的ORM 组件,如WebWare MiddleKit和Django的数据库API。读者需要注意的是,并不是所有知名的ORM都适合于你的应用程序,读者需要根据自己的需要来选择。
2.5.2 使用SQLAlchemy
在Python程序中,SQLAlchemy是一种经典的ORM。在使用之前需要先安装SQLAlchemy,安装命令如下所示。
pip install SQLAlchemy
例如在下面的实例文件SQLAlchemy.py中,演示了在Python程序中使用SQLAlchemy操作两种数据库的过程。
源码路径:codes\2\2-5\SQLAlchemy.py
from distutils.log import warn as printf
from os.path import dirname
from random import randrange as rand
from sqlalchemy import Column, Integer, String, create_engine, exc, orm
from sqlalchemy.ext.declarative import declarative_base
from db import DBNAME, NAMELEN, randName, FIELDS, tformat, cformat, setup
DSNs = {
'mysql': 'mysql://root@localhost/%s' % DBNAME,
'sqlite': 'sqlite:///:memory:',
}
Base = declarative_base()
class Users(Base):
__tablename__ = 'users'
login = Column(String(NAMELEN))
userid = Column(Integer, primary_key=True)
projid = Column(Integer)
def __str__(self):
return ''.join(map(tformat,
(self.login, self.userid, self.projid)))
class SQLAlchemyTest(object):
def __init__(self, dsn):
try:
eng = create_engine(dsn)
except ImportError:
raise RuntimeError()
try:
eng.connect()
except exc.OperationalError:
eng = create_engine(dirname(dsn))
eng.execute('CREATE DATABASE %s' % DBNAME).close()
eng = create_engine(dsn)
Session = orm.sessionmaker(bind=eng)
self.ses = Session()
self.users = Users.__table__
self.eng = self.users.metadata.bind = eng
def insert(self):
self.ses.add_all(
Users(login=who, userid=userid, projid=rand(1,5)) \
for who, userid in randName()
)
self.ses.commit()
def update(self):
fr = rand(1,5)
to = rand(1,5)
i = -1
users = self.ses.query(
Users).filter_by(projid=fr).all()
for i, user in enumerate(users):
user.projid = to
self.ses.commit()
return fr, to, i+1
def delete(self):
rm = rand(1,5)
i = -1
users = self.ses.query(
Users).filter_by(projid=rm).all()
for i, user in enumerate(users):
self.ses.delete(user)
self.ses.commit()
return rm, i+1
def dbDump(self):
printf('\n%s' % ''.join(map(cformat, FIELDS)))
users = self.ses.query(Users).all()
for user in users:
printf(user)
self.ses.commit()
def __getattr__(self, attr): # use for drop/create
return getattr(self.users, attr)
def finish(self):
self.ses.connection().close()
def main():
printf('*** Connect to %r database' % DBNAME)
db = setup()
if db not in DSNs:
printf('\nERROR: %r not supported, exit' % db)
return
try:
orm = SQLAlchemyTest(DSNs[db])
except RuntimeError:
printf('\nERROR: %r not supported, exit' % db)
return
printf('\n*** Create users table (drop old one if appl.)')
orm.drop(checkfirst=True)
orm.create()
printf('\n*** Insert names into table')
orm.insert()
orm.dbDump()
printf('\n*** Move users to a random group')
fr, to, num = orm.update()
printf('\t(%d users moved) from (%d) to (%d)' % (num, fr, to))
orm.dbDump()
printf('\n*** Randomly delete group')
rm, num = orm.delete()
printf('\t(group #%d; %d users removed)' % (rm, num))
orm.dbDump()
printf('\n*** Drop users table')
orm.drop()
printf('\n*** Close cxns')
orm.finish()
if __name__ == '__main__':
main()
q 在上述实例代码中,首先导入了Python标准库中的模块(distutils、os.path、random),然后是第三方或外部模块(sqlalchemy),最后是应用的本地模块(db),该模块会给我们提供主要的常量和工具函数。
q 使用了SQLAlchemy的声明层,在使用前必须先导入sqlalchemy.ext.declarative. declarative_base,然后使用它创建一个Base 类,最后让你的数据子类继承自这个Base类。类定义的下一个部分包含了一个__tablename__属性,它定义了映射的数据库表名。也可以显式地定义一个低级别的sqlalchemy.Table 对象,在这种情况下需要将其写为__table__。在大多数情况下使用对象进行数据行的访问,不过也会使用表级别的行为(创建和删除)保存表。接下来是“列”属性,可以通过查阅文档来获取所有支持的数据类型。最后,有一个__str()__方法定义,用来返回易于阅读的数据行的字符串格式。因为该输出是定制化的(通过tformat()函数的协助),所以不推荐在开发过程中这样使用。
q 通过自定义函数分别实现行的插入、更新和删除操作。插入使用了session.add_all()方法,这将使用迭代的方式产生一系列的插入操作。最后,还可以决定是像我们一样进行提交还是进行回滚。update()和delete()方法都存在会话查询的功能,它们使用query.filter_by()方法进行查找。随机更新会选择一个成员,通过改变ID 的方法,将其从一个项目组(fr)移动到另一个项目组(to)。计数器(i)会记录有多少用户会受到影响。删除操作则是根据ID(rm)随机选择一个项目并假设已将其取消,因此项目中的所有员工都将被解雇。当要执行操作时,需要通过会话对象进行提交。
q 函数dbDump()负责向屏幕上显示正确的输出。该方法从数据库中获取数据行,并按照db.py 中相似的样式输出数据。
本实例执行后会输出:
Choose a database system:
(M)ySQL
(G)adfly
(S)SQLite
Enter choice: S
*** Create users table (drop old one if appl.)
*** Insert names into table
LOGIN USERID PROJID
Faye 6812 4
Serena 7003 1
Amy 7209 2
Dave 7306 3
Larry 7311 3
Mona 7404 3
Ernie 7410 3
Jim 7512 3
Angela 7603 3
Stan 7607 3
Jennifer 7608 1
Pat 7711 1
Leslie 7808 4
Davina 7902 4
Elliot 7911 1
Jess 7912 4
Aaron 8312 3
Melissa 8602 4
*** Move users to a random group
(1 users moved) from (2) to (1)
LOGIN USERID PROJID
Faye 6812 4
Serena 7003 1
Amy 7209 1
Dave 7306 3
Larry 7311 3
Mona 7404 3
Ernie 7410 3
Jim 7512 3
Angela 7603 3
Stan 7607 3
Jennifer 7608 1
Pat 7711 1
Leslie 7808 4
Davina 7902 4
Elliot 7911 1
Jess 7912 4
Aaron 8312 3
Melissa 8602 4
*** Randomly delete group
(group #1; 5 users removed)
LOGIN USERID PROJID
Faye 6812 4
Dave 7306 3
Larry 7311 3
Mona 7404 3
Ernie 7410 3
Jim 7512 3
Angela 7603 3
Stan 7607 3
Leslie 7808 4
Davina 7902 4
Jess 7912 4
Aaron 8312 3
Melissa 8602 4
*** Drop users table
*** Close cxns
2.5.3 使用mongoengine
在Python程序中,MongoDB数据库的ORM框架是mongoengine。在使用mongoengine框架之前需要先安装mongoengine,具体安装命令如下所示。
pip install mongoengine
在运行上述命令之前,必须先确保使用如下命令安装了pymongo框架,在本章前面已经安装了pymongo框架。
pip install pymongo
例如在下面的实例文件orm.py中,演示了在Python程序中使用mongoengine操作数据库数据的过程。
源码路径:codes\2\2-5\orm.py
import random #导入内置模块
from mongoengine import *
connect('test') #连接数据库对象'test'
class Stu(Document): #定义ORM框架类Stu
sid = SequenceField() #“序号”属性表示用户id
name = StringField() #“用户名”属性
passwd = StringField() #“密码”属性
def introduce(self): #定义函数introduce()显示自己的介绍信息
print('序号:',self.sid,end=" ") #打印显示id
print('姓名:',self.name,end=' ') #打印显示姓名
print('密码:',self.passwd) #打印显示密码
def set_pw(self,pw): #定义函数set_pw()用于修改密码
if pw:
self.passwd = pw #修改密码
self.save() #保存修改的密码
…省略部分代码…
if __name__ == '__main__':
print('插入一个文档:')
stu = Stu(name='langchao',passwd='123123')#创建文档类对象实例stu,设置用户名和密码
stu.save() #持久化保存文档
stu = Stu.objects(name='lilei').first() #查询数据并对类进行初始化
if stu:
stu.introduce() #显示文档信息
print('插入多个文档') #打印提示信息
for i in range(3): #遍历操作
Stu(name=get_str(2,4),passwd=get_str(6,8)).save() #插入3个文档
stus = Stu.objects() #文档类对象实例stu
for stu in stus: #遍历所有的文档信息
stu.introduce() #显示所有的遍历文档
print('修改一个文档') #打印提示信息
stu = Stu.objects(name='langchao').first() #查询某个要操作的文档
if stu:
stu.name='daxie' #修改用户名属性
stu.save() #保存修改
stu.set_pw('bbbbbbbb') #修改密码属性
stu.introduce() #显示修改后结果
print('删除一个文档') #打印提示信息
stu = Stu.objects(name='daxie').first() #查询某个要操作的文档
stu.delete() #删除这个文档
stus = Stu.objects()
for stu in stus: #遍历所有的文档
stu.introduce() #显示删除后结果
在上述实例代码中,在导入mongoengine库和连接MongoDB数据库后,定义了一个继承于类Document的子类Stu。在主程序中通过创建类的实例,并调用其方法save()将类持久化到数据库;通过类Stu中的方法objects()来查询数据库并映射为类Stu的实例,并调用其自定义方法introduce()来显示载入的信息。然后插入3个文档信息,并调用方法save()持久化存入数据库,通过调用类中的自定义方法set_pw()修改数据并存入数据库。最后通过调用类中的方法delete()从数据库中删除一个文档。
开始测试程序,在运行本实例程序时,必须在CMD控制台中启动MongoDB服务,并且确保上述控制台界面处于打开状态。下面是开启MongoDB服务的命令:
mongod --dbpath "h:\data"
在上述命令中,“h:\data”是一个保存MongoDB数据库数据的目录。
本实例执行后的效果如图2-22所示。
图2-22 执行效果