在Python的Django框架上部署ORM库的教程
PythonORM概览
作为一个美妙的语言,Python除了SQLAlchemy外还有很多ORM库。在这篇文章里,我们将来看看几个流行的可选ORM库,以此更好地窥探到PythonORM境况。通过写一段脚本来读写2个表,person和address到一个简单的数据库,我们能更好地理解每个ORM库的优缺点。
SQLObject
SQLObject是一个介于SQL数据库和Python之间映射对象的PythonORM。得益于其类似于RubyonRails的ActiveRecord模式,在编程社区变得越来越流行。首个SQLObject在2002年十月发布。它遵循LGPL许可。
在SQLObject中,数据库概念是通过与SLQAlchemy非常类似的的一种方式映射到Python的,表映射成类,行作为实例而字段作为属性。它同时提供一种基于Python对象的查询语言,这使得SQL更加抽象,从而为应用提供了数据库不可知性(译注:应用和数据库分离)
$pipinstallsqlobject Downloading/unpackingsqlobject DownloadingSQLObject-1.5.1.tar.gz(276kB):276kBdownloaded Runningsetup.pyegg_infoforpackagesqlobject warning:nofilesfoundmatching'*.html' warning:nofilesfoundmatching'*.css' warning:nofilesfoundmatching'docs/*.html' warning:nofilesfoundmatching'*.py'underdirectory'tests' Requirementalreadysatisfied(use--upgradetoupgrade):FormEncode>=1.1.1in/Users/xiaonuogantan/python2-workspace/lib/python2.7/site-packages(fromsqlobject) Installingcollectedpackages:sqlobject Runningsetup.pyinstallforsqlobject changingmodeofbuild/scripts-2.7/sqlobject-adminfrom644to755 changingmodeofbuild/scripts-2.7/sqlobject-convertOldURIfrom644to755 warning:nofilesfoundmatching'*.html' warning:nofilesfoundmatching'*.css' warning:nofilesfoundmatching'docs/*.html' warning:nofilesfoundmatching'*.py'underdirectory'tests' changingmodeof/Users/xiaonuogantan/python2-workspace/bin/sqlobject-adminto755 changingmodeof/Users/xiaonuogantan/python2-workspace/bin/sqlobject-convertOldURIto755 Successfullyinstalledsqlobject Cleaningup... >>>fromsqlobjectimportStringCol,SQLObject,ForeignKey,sqlhub,connectionForURI >>>sqlhub.processConnection=connectionForURI('sqlite:/:memory:') >>> >>>classPerson(SQLObject): ...name=StringCol() ... >>>classAddress(SQLObject): ...address=StringCol() ...person=ForeignKey('Person') ... >>>Person.createTable() [] >>>Address.createTable() []
上面的代码创建了2个简单的表:person和address。为了创建和插入记录到这2个表,我们简单实例化一个person实例和一个address实例:
>>>p=Person(name='person') >>>a=Address(address='address',person=p) >>>p >>>a <address>
为了获得或检索新记录,我们用神奇的q对象关联到Person和Address类:
>>>persons=Person.select(Person.q.name=='person') >>>persons >>>list(persons) [] >>>p1=persons[0] >>>p1==p True >>>addresses=Address.select(Address.q.person==p1) >>>addresses >>>list(addresses) [ <address>] >>>a1=addresses[0] >>>a1==a TrueStorm
Storm是一个介于单个或多个数据库与Python之间映射对象的PythonORM。为了支持动态存储和取回对象信息,它允许开发者构建跨数据表的复杂查询。它由Ubuntu背后的公司Canonical公司用Python开发的,用在Launchpad和Landscape应用中,后来在2007年作为自由软件发布。这个项目在LGPL许可下发布,代码贡献者必须受让版权给Canonical公司。
像SQLAlchemy和SQLObject那样,Storm也映射表到类,行到实例和字段到属性。相对另外2个库,Stom中tableclass不需要是框架特定基类的子类。在SQLAlchemy中,每个tableclass是sqlalchemy.ext.declarative.declarative_bas的一个子类。而在SQLOjbect中,每个tableclass是的sqlobject.SQLObject的子类。
类似于SQLAlchemy,Storm的Store对象对于后端数据库就像一个代理人,所有的操作缓存在内存,一当提交方法在store上被调用就提交到数据库。每个store持有自己的Python数据库对象映射集合,就像一个SQLAlchemysession持有不同的Python对象集合。
指定版本的Storm可以从下载页面下载。在这篇文章里,示例代码是使用0.20版本的Storm写的。
>>>fromstorm.localsimportInt,Reference,Unicode,create_database,Store >>> >>> >>>db=create_database('sqlite:') >>>store=Store(db) >>> >>> >>>classPerson(object): ...__storm_table__='person' ...id=Int(primary=True) ...name=Unicode() ... >>> >>>classAddress(object): ...__storm_table__='address' ...id=Int(primary=True) ...address=Unicode() ...person_id=Int() ...person=Reference(person_id,Person.id) ...
上面的代码创建了一个sqlite内存数据库,然后用store来引用该数据库对象。一个Stormstore类似SQLAlchemy的DBSession对象,都管理附属于其的实例对象的生命周期。例如,下面的代码创建了一个person和一个address,然后通过刷新store都插入记录。
>>>store.execute("CREATETABLEperson" ..."(idINTEGERPRIMARYKEY,nameVARCHAR)") >>>store.execute("CREATETABLEaddress" ..."(idINTEGERPRIMARYKEY,addressVARCHAR,person_idINTEGER," ..."FOREIGNKEY(person_id)REFERENCESperson(id))") >>>person=Person() >>>person.name=u'person' >>>printperson >>>print"%r,%r"%(person.id,person.name) None,u'person'#Noticethatperson.idisNonesincethePersoninstanceisnotattachedtoavaliddatabasestoreyet. >>>store.add(person) >>>print"%r,%r"%(person.id,person.name) None,u'person'#Sincethestorehasn'tflushedthePersoninstanceintothesqlitedatabaseyet,person.idisstillNone. >>>store.flush() >>>print"%r,%r"%(person.id,person.name) 1,u'person'#NowthestorehasflushedthePersoninstance,wegotanidvalueforperson. >>>address=Address() >>>address.person=person >>>address.address='address' >>>print"%r,%r,%r"%(address.id,address.person,address.address) None,,'address' >>>address.person==person True >>>store.add(address) >>>store.flush() >>>print"%r,%r,%r"%(address.id,address.person,address.address) 1,,'address'
为了获得或检索已插的Person和Address对象,我们调用store.find()来查询:
>>>person=store.find(Person,Person.name==u'person').one() >>>print"%r,%r"%(person.id,person.name) 1,u'person' >>>store.find(Address,Address.person==person).one() >>>address=store.find(Address,Address.person==person).one() >>>print"%r,%r"%(address.id,address.address) 1,u'address'
Django的ORM
Django是一个免费开源的紧嵌ORM到其系统的web应用框架。在它首次发布后,得益于其易用为Web而备的特点,Django越来越流行。它在2005年七月在BSD许可下发布。因为Django的ORM是紧嵌到web框架的,所以就算可以也不推荐,在一个独立的非Django的Python项目中使用它的ORM。
Django,一个最流行的Pythonweb框架,有它独有的ORM。相比SQLAlchemy,Django的ORM更吻合于直接操作SQL对象,操作暴露了简单直接映射数据表和Python类的SQL对象。
$django-admin.pystartprojectdemo $cddemo $pythonmanage.pysyncdb Creatingtables... Creatingtabledjango_admin_log Creatingtableauth_permission Creatingtableauth_group_permissions Creatingtableauth_group Creatingtableauth_user_groups Creatingtableauth_user_user_permissions Creatingtableauth_user Creatingtabledjango_content_type Creatingtabledjango_session YoujustinstalledDjango'sauthsystem,whichmeansyoudon'thaveanysuperusersdefined. Wouldyouliketocreateonenow?(yes/no):no InstallingcustomSQL... Installingindexes... Installed0object(s)from0fixture(s) $pythonmanage.pyshell
因为我们在没有先建立一个项目时不能够执行Django代码,所以我们在前面的shell创建一个Djangodemo项目,然后进入Djangoshell来测试我们写的ORM例子。
#demo/models.py >>>fromdjango.dbimportmodels >>> >>> >>>classPerson(models.Model): ...name=models.TextField() ...classMeta: ...app_label='demo' ... >>> >>>classAddress(models.Model): ...address=models.TextField() ...person=models.ForeignKey(Person) ...classMeta: ...app_label='demo' ...
上面的代码声明了2个Python类,Person和Address,每一个都映射到数据库表。在执行任意数据库操作代码之前,我们需要先在本地的sqlite数据库创建表。
pythonmanage.pysyncdb Creatingtables... Creatingtabledemo_person Creatingtabledemo_address InstallingcustomSQL... Installingindexes... Installed0object(s)from0fixture(s)
为了插入一个person和一个address到数据库,我们实例化相应对象并调用这些对象的save()方法。
>>>fromdemo.modelsimportPerson,Address >>>p=Person(name='person') >>>p.save() >>>print"%r,%r"%(p.id,p.name) 1,'person' >>>a=Address(person=p,address='address') >>>a.save() >>>print"%r,%r"%(a.id,a.address) 1,'address'
为了获得或检索person和address对象,我们用model类神奇的对象属性从数据库取得对象。
>>>persons=Person.objects.filter(name='person') >>>persons [] >>>p=persons[0] >>>print"%r,%r"%(p.id,p.name) 1,u'person' >>>addresses=Address.objects.filter(person=p) >>>addresses [ <address>] >>>a=addresses[0] >>>print"%r,%r"%(a.id,a.address) 1,u'address'
peewee
peewee是一个小的,表达式的ORM。相比其他的ORM,peewee主要专注于极简主义,其API简单,并且其库容易使用和理解。
pipinstallpeewee Downloading/unpackingpeewee Downloadingpeewee-2.1.7.tar.gz(1.1MB):1.1MBdownloaded Runningsetup.pyegg_infoforpackagepeewee Installingcollectedpackages:peewee Runningsetup.pyinstallforpeewee changingmodeofbuild/scripts-2.7/pwiz.pyfrom644to755 changingmodeof/Users/xiaonuogantan/python2-workspace/bin/pwiz.pyto755 Successfullyinstalledpeewee Cleaningup...
为了创建数据库模型映射,我们实现了一个Person类和一个Address类来映射对应的数据库表。
>>>frompeeweeimportSqliteDatabase,CharField,ForeignKeyField,Model >>> >>>db=SqliteDatabase(':memory:') >>> >>>classPerson(Model): ...name=CharField() ...classMeta: ...database=db ... >>> >>>classAddress(Model): ...address=CharField() ...person=ForeignKeyField(Person) ...classMeta: ...database=db ... >>>Person.create_table() >>>Address.create_table()
为了插入对象到数据库,我们实例化对象并调用了它们的save()方法。从视图的对象创建这点来看,peewee类似于Django。
>>>p=Person(name='person') >>>p.save() >>>a=Address(address='address',person=p) >>>a.save()
为了从数据库获得或检索对象,我们select了类各自的对象。
>>>person=Person.select().where(Person.name=='person').get() >>>person >>>print'%r,%r'%(person.id,person.name) 1,u'person' >>>address=Address.select().where(Address.person==person).get() >>>print'%r,%r'%(address.id,address.address) 1,u'address'
SQLAlchemy
SQLAlchemy是Python编程语言里,一个在MIT许可下发布的开源工具和SQLORM。它首次发布于2006年二月,由MichaelBayer写的。它提供了“一个知名企业级的持久化模式的,专为高效率和高性能的数据库访问设计的,改编成一个简单的Python域语言的完整套件”。它采用了数据映射模式(像Java中的Hibernate)而不是ActiveRecord模式(像RubyonRails的ORM)。
SQLAlchemy的工作单元主要使得有必要限制所有的数据库操作代码到一个特定的数据库session,在该session中控制每个对象的生命周期。类似于其他的ORM,我们开始于定义declarative_base()的子类,以映射表到Python类。
>>>fromsqlalchemyimportColumn,String,Integer,ForeignKey >>>fromsqlalchemy.ormimportrelationship >>>fromsqlalchemy.ext.declarativeimportdeclarative_base >>> >>> >>>Base=declarative_base() >>> >>> >>>classPerson(Base): ...__tablename__='person' ...id=Column(Integer,primary_key=True) ...name=Column(String) ... >>> >>>classAddress(Base): ...__tablename__='address' ...id=Column(Integer,primary_key=True) ...address=Column(String) ...person_id=Column(Integer,ForeignKey(Person.id)) ...person=relationship(Person) ...
在我们写任何数据库代码前,我们需要为数据库session创建一个数据库引擎。
>>>fromsqlalchemyimportcreate_engine >>>engine=create_engine('sqlite:///')
一当我们创建了数据库引擎,可以继续创建一个数据库会话,并为所有之前定义的Person和Address类创建数据库表。
>>>fromsqlalchemy.ormimportsessionmaker >>>session=sessionmaker() >>>session.configure(bind=engine) >>>Base.metadata.create_all(engine)
现在,session对象对象变成了我们工作单元的构造函数,将和所有后续数据库操作代码和对象关联到一个通过调用它的__init__()方法构建的数据库session上。
>>>s=session() >>>p=Person(name='person') >>>s.add(p) >>>a=Address(address='address',person=p) >>>s.add(a)
为了获得或检索数据库中的对象,我们在数据库session对象上调用query()和filter()方法。
>>>p=s.query(Person).filter(Person.name=='person').one() >>>p >>>print"%r,%r"%(p.id,p.name) 1,'person' >>>a=s.query(Address).filter(Address.person==p).one() >>>print"%r,%r"%(a.id,a.address) 1,'address'
请留意到目前为止,我们还没有提交任何对数据库的更改,所以新的person和address对象实际上还没存储在数据库中。调用s.commit()将会提交更改,比如,插入一个新的person和一个新的address到数据库中。
>>>s.commit() >>>s.close()
PythonORM之间对比
对于在文章里提到的每一种PythonORM,我们来列一下他们的优缺点:
SQLObject
优点:
- 采用了易懂的ActiveRecord模式
- 一个相对较小的代码库
缺点:
- 方法和类的命名遵循了Java的小驼峰风格
- 不支持数据库session隔离工作单元
Storm
优点:
- 清爽轻量的API,短学习曲线和长期可维护性
- 不需要特殊的类构造函数,也没有必要的基类
缺点:
- 迫使程序员手工写表格创建的DDL语句,而不是从模型类自动派生
- Storm的贡献者必须把他们的贡献的版权给Canonical公司
Django'sORM
优点:
- 易用,学习曲线短
- 和Django紧密集合,用Django时使用约定俗成的方法去操作数据库
缺点:
- 不好处理复杂的查询,强制开发者回到原生SQL
- 紧密和Django集成,使得在Django环境外很难使用
peewee
优点:
- Django式的API,使其易用
- 轻量实现,很容易和任意web框架集成
缺点:
- 不支持自动化schema迁移
- 多对多查询写起来不直观
SQLAlchemy
优点:
- 企业级API,使得代码有健壮性和适应性
- 灵活的设计,使得能轻松写复杂查询
缺点:
- 工作单元概念不常见
- 重量级API,导致长学习曲线
总结和提示
相比其他的ORM,SQLAlchemy意味着,无论你何时写SQLAlchemy代码,都专注于工作单元的前沿概念。DBSession的概念可能最初很难理解和正确使用,但是后来你会欣赏这额外的复杂性,这让意外的时序提交相关的数据库bug减少到0。在SQLAlchemy中处理多数据库是棘手的,因为每个DBsession都限定了一个数据库连接。但是,这种类型的限制实际上是好事,因为这样强制你绞尽脑汁去想在多个数据库之间的交互,从而使得数据库交互代码很容易调试。
在未来的文章中,我们将会完整地披露更高阶的SQLAlchemy用例,真正领会无限强大的API。