如果用數(shù)據(jù)庫做持久化設(shè)備的話直接寫sql(存儲(chǔ)過程)還是用ORM呢?
無疑ORM的出現(xiàn)給了我們一個(gè)全新的看待數(shù)據(jù)操作的視角:在面向?qū)ο蟮恼Z言中,用面向?qū)ο蟮姆椒ㄔL問,操作數(shù)據(jù)
但是在使用ORM一段時(shí)間后,總是會(huì)覺得有點(diǎn)別扭的地方,因?yàn)殛P(guān)系型數(shù)據(jù)庫對(duì)數(shù)據(jù)的抽象方式是基于關(guān)系代數(shù)的層次結(jié)構(gòu),而對(duì)象化的數(shù)據(jù)抽象方式是網(wǎng)狀結(jié)構(gòu),ORM在這兩種抽象模式之間映射就會(huì)產(chǎn)生Impedance Mismatch(阻抗不匹配)的現(xiàn)象,所以在映射不當(dāng)?shù)臅r(shí)候就會(huì)感覺非常的別扭。
那么我再想,如果抽象數(shù)據(jù)本身存在阻抗不匹配的問題,那么如果我們抽象對(duì)數(shù)據(jù)的訪問呢?無論如何ORM,對(duì)關(guān)系數(shù)據(jù)庫的訪問仍然是通過SQL語句作為訪問的界面,那么如果我們抽象SQL語句是否就能擺脫阻抗失調(diào),能夠流暢的訪問操作數(shù)據(jù)了呢?我在上一篇文章發(fā)布的一個(gè)Python的DAL里做了一個(gè)小小的嘗試,當(dāng)然我并不是第一個(gè)這么做的人,不過我想把這種方法總結(jié)出來,也許能夠在ORM以外提供一個(gè)更新的視角,能夠產(chǎn)生出更加輕量化的數(shù)據(jù)訪問組件,也是不錯(cuò)的。用Python實(shí)現(xiàn)是因?yàn)闀簳r(shí)我還只想到了在動(dòng)態(tài)語言下如何實(shí)現(xiàn),如果需要在.NET下使用,可以用IronPython。如果有同學(xué)想到了在C#下如何實(shí)現(xiàn)也希望能夠拿出來和大家分享一下。
為什么要用動(dòng)態(tài)語言來實(shí)現(xiàn),首先就是不要實(shí)體類,因?yàn)槲覀兂橄蟮氖窃L問數(shù)據(jù)的方式而不是數(shù)據(jù)本身,在關(guān)系型數(shù)據(jù)庫里數(shù)據(jù)就是數(shù)據(jù)行,字段的形式,在數(shù)據(jù)結(jié)構(gòu)上一行數(shù)據(jù)用字典就能夠表示了,而在python下我們可以把字典用下面的形式來表現(xiàn):
class Row(dict):
def __getattr__(self,propertyName):
if self.has_key(propertyName):
return self[propertyName]
return None
這樣一個(gè)Row的對(duì)象我們就可以通過列明對(duì)應(yīng)屬性來訪問其中的鍵值,就跟一個(gè)實(shí)體類一樣,這樣我們就解決了數(shù)據(jù)抽象的問題。
接下來是重點(diǎn),我們要抽象SQL,首先要分析SQL的結(jié)構(gòu),我們對(duì)數(shù)據(jù)的操作有 增、刪、改、查四類,分別通過:
INSERT、DELETE、UPDATE、SELECT四種SQL語句來實(shí)現(xiàn),每一種SQL語句又由子句構(gòu)成,四種SQL語句的子句中有各自獨(dú)有到,也有共有的子句,比如WHERE子句,SELECT、UPDATE、DELETE 這三種SQL操作都有WHERE子句,且語義相同,均表示待操作的數(shù)據(jù)篩選到條件,所以我將篩選條件抽象出來定義了conds這個(gè)類。
class conds:
def __init__(self,field):
self.field_name=field
self._sql=""
self._params=[]
self._has_value=False
self._sub_conds=[]
self._no_value=False
這個(gè)類用來將語言的關(guān)系運(yùn)算邏輯映射到SQL語句上,所幸的是Python支持操作符重載,所以我們只需要在conds類中加入相應(yīng)的buildin方法就行了。比如:
def __eq__(self,value):
return self._prepare("".join(["`",self.field_name,"`=%s"]),value)
def _prepare(self,sql,value):
if not self._has_value:
self._sql=sql
self._params.append(value)
self._has_value=True
return self
raise OperationalError,"Multiple Operate conditions"
上面代碼就定義了如果 conds('col1')==5 就會(huì)映射到sql col1=%s 且增加一個(gè)參數(shù),值為5。
同理將其他關(guān)系操作符的方法都映射到相應(yīng)的buildin方法中,兩個(gè)關(guān)系間是and和or,我們用__and__和__or__這兩個(gè)方法來映射,還有Like找不到相應(yīng)的操作符,就用like方法來表示這個(gè)關(guān)系,條件操作的結(jié)果還是條件,所以要用對(duì)象本身作為返回值。這樣 (conds('col1')==5)&(conds('col2')<6) 就映射到了條件 col1=%s and col2<%s這兩個(gè)條件的并集。由于這么寫還需要用字符串來表示列名不方便,所以我們定義一個(gè)table類來獲取conds:
class TableQueryer:
'''
Support for single table simple querys
'''
def __init__(self,db,tablename):
self.tablename=tablename
self.db=db
def __call__(self,query=None):
return Operater(self.db,self.tablename,query)
def __getattr__(self,field_name):
return conds(field_name)
同理我們?cè)跀?shù)據(jù)連接類上加入getattr,
def __getattr__(self,tablename):
'''
return single table queryer for select table
'''
return TableQueryer(self,tablename)
然后我們就能夠通過db.tablename.colname來獲取條件對(duì)象了,之前的例子就能改成
(db.tablename.col1==5)&(db.tablename.col2<6)
如果預(yù)先 tb=db.tablename 的話還可以改寫成
(tb.col1==t)&(tb.col2<6)
由于插入數(shù)據(jù)只需要數(shù)據(jù)表名不需要查詢?yōu)榛A(chǔ),所以直接在TableQueryer中加入insert方法
所以插入數(shù)據(jù)就只需要db.tablename.insert(col1=value1,col2=value2...)
數(shù)據(jù)的查詢、更新、刪除都是基于查詢條件的,所以抽象出一個(gè)操作對(duì)象Operate出來:
class Operater:
def __init__(self,db,tablename,query):
self.count=Count(db,tablename,query)
self.select=Select(db,tablename,query)
self.update=Update(db,tablename,query)
self.delete=Delete(db,tablename,query)
每一個(gè)操作抽象一個(gè)類出來,并且把查詢條件保存到了每一個(gè)操作的條件里,最終的Sql生成,以及特定子句的加入,都在具體的類比如Select里完成。
我在這里為了節(jié)約一個(gè)方法名的層次出來就把TableQueryer做成了可以調(diào)用的對(duì)象,加入了__call__方法。將查詢條件傳入__call__方法就返回了一個(gè)Operator對(duì)象,于是通過
tb=db.tablename
q=tb((tb.col1==5)&(tb.col2<6))
q.select()就能查詢出結(jié)果,等價(jià)sql為 select * from tablename where col1=5 and col2<6
在得到q后,直接調(diào)用q.delete()就能把這些數(shù)據(jù)刪掉
q.update(tb.col1==6)就能更新篩選出的數(shù)據(jù)
q.delete()就能直接刪掉符合篩選條件的數(shù)據(jù)
項(xiàng)目放在 http://bitbucket.org/alexander_lee/flunt-sql-data-access-layer