通过抽象Sql语句来构建流畅的数据访问

如果用数据库做持久化设备的话直接写sql(存储过程)还是用ORM呢?
无疑ORM的出现给了我们一个全新的看待数据操作的视角:在面向对象的语言中,用面向对象的方法访问,操作数据
但是在使用ORM一段时间后,总是会觉得有点别扭的地方,因为关系型数据库对数据的抽象方式是基于关系代数的层次结构,而对象化的数据抽象方式是网状结构,ORM在这两种抽象模式之间映射就会产生Impedance Mismatch(阻抗不匹配)的现象,所以在映射不当的时候就会感觉非常的别扭。
那么我再想,如果抽象数据本身存在阻抗不匹配的问题,那么如果我们抽象对数据的访问呢?无论如何ORM,对关系数据库的访问仍然是通过SQL语句作为访问的界面,那么如果我们抽象SQL语句是否就能摆脱阻抗失调,能够流畅的访问操作数据了呢?我在上一篇文章发布的一个Python的DAL里做了一个小小的尝试,当然我并不是第一个这么做的人,不过我想把这种方法总结出来,也许能够在ORM以外提供一个更新的视角,能够产生出更加轻量化的数据访问组件,也是不错的。用Python实现是因为暂时我还只想到了在动态语言下如何实现,如果需要在.NET下使用,可以用IronPython。如果有同学想到了在C#下如何实现也希望能够拿出来和大家分享一下。
为什么要用动态语言来实现,首先就是不要实体类,因为我们抽象的是访问数据的方式而不是数据本身,在关系型数据库里数据就是数据行,字段的形式,在数据结构上一行数据用字典就能够表示了,而在python下我们可以把字典用下面的形式来表现:
class Row(dict):
    def __getattr__(self,propertyName):
        if self.has_key(propertyName):
            return self[propertyName]
        return None

这样一个Row的对象我们就可以通过列明对应属性来访问其中的键值,就跟一个实体类一样,这样我们就解决了数据抽象的问题。
接下来是重点,我们要抽象SQL,首先要分析SQL的结构,我们对数据的操作有 增、删、改、查四类,分别通过:
INSERT、DELETE、UPDATE、SELECT四种SQL语句来实现,每一种SQL语句又由子句构成,四种SQL语句的子句中有各自独有到,也有共有的子句,比如WHERE子句,SELECT、UPDATE、DELETE 这三种SQL操作都有WHERE子句,且语义相同,均表示待操作的数据筛选到条件,所以我将筛选条件抽象出来定义了conds这个类。
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
这个类用来将语言的关系运算逻辑映射到SQL语句上,所幸的是Python支持操作符重载,所以我们只需要在conds类中加入相应的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 就会映射到sql col1=%s 且增加一个参数,值为5。
同理将其他关系操作符的方法都映射到相应的buildin方法中,两个关系间是and和or,我们用__and__和__or__这两个方法来映射,还有Like找不到相应的操作符,就用like方法来表示这个关系,条件操作的结果还是条件,所以要用对象本身作为返回值。这样 (conds('col1')==5)&(conds('col2')<6) 就映射到了条件 col1=%s and col2<%s这两个条件的并集。由于这么写还需要用字符串来表示列名不方便,所以我们定义一个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)

同理我们在数据连接类上加入getattr,
def __getattr__(self,tablename):
    '''
    return single table queryer for select table
    '''
    return TableQueryer(self,tablename)
    
然后我们就能够通过db.tablename.colname来获取条件对象了,之前的例子就能改成
(db.tablename.col1==5)&(db.tablename.col2<6)
如果预先 tb=db.tablename 的话还可以改写成
(tb.col1==t)&(tb.col2<6)

由于插入数据只需要数据表名不需要查询为基础,所以直接在TableQueryer中加入insert方法
所以插入数据就只需要db.tablename.insert(col1=value1,col2=value2...)

数据的查询、更新、删除都是基于查询条件的,所以抽象出一个操作对象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)

每一个操作抽象一个类出来,并且把查询条件保存到了每一个操作的条件里,最终的Sql生成,以及特定子句的加入,都在具体的类比如Select里完成。
我在这里为了节约一个方法名的层次出来就把TableQueryer做成了可以调用的对象,加入了__call__方法。将查询条件传入__call__方法就返回了一个Operator对象,于是通过
tb=db.tablename
q=tb((tb.col1==5)&(tb.col2<6))
q.select()就能查询出结果,等价sql为 select * from tablename where col1=5 and col2<6
在得到q后,直接调用q.delete()就能把这些数据删掉
q.update(tb.col1==6)就能更新筛选出的数据
q.delete()就能直接删掉符合筛选条件的数据

项目放在 http://bitbucket.org/alexander_lee/flunt-sql-data-access-layer
有兴趣的TX一起研究研究






posted on 2010-08-25 20:42  亚历山大同志  阅读(3665)  评论(25编辑  收藏  举报

导航