ORM框架疏理——廖雪峰实战系列(一)

摘要:
实际上,它实际上创建了一个可以在编程语言中使用的“虚拟对象数据库”。以上是维基百科的解释,但为什么要使用ORM?数据库非常简单。您只需要维护三张表。如果要加强表之间的关系,可以使用外键。数据库表由记录组成,每个记录包含不同的字段。我们编写的ORM框架是为了实现上述想法s'),argsor())51ifsize:52rs=awaitcur.fetchermany53其他:54rs=await cur.fetchall()5556日志记录。info57returnrs5859#包执行函数,可执行INSERT、UPDATE和DELETE命令60异步执行:61global__pool62#从连接池63获取连接,与__pool异步。Acquire()asconn:64#如果MySQL禁止隐式提交,请标记事务的开始65ifnotautocommit:66awaitconn。begin()67try:68#createccursorexecuteMySQL命令69与conn异步。cursorascur:70awaitcur执行(sql.replace('?','%s'),argsor())71afectrow=cur。rowcount72#如果MySQL禁止隐式提交,请手动提交事务73ifnotautocommit:74awaitcur。commit()75#如果事务中发生错误,则76exceptBaseExceptionase:7awaitconn。回滚()78raise7980#返回重复行数81返回错误行8283#使用“?”创建占位符?

ORM(Object Relational Mapping,对象关系映射),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上来说,它其实创建了一个可在编程语言里使用的“虚拟对象数据库”。

上面是维基百科的解释,但是为什么要用ORM这种编程技术呢?

就这个实战作业来看:

  博客——标题、摘要、内容、评论、作者、创作时间

  评论——内容、评论人、评论文章、评论时间

  用户——姓名、邮箱、口令、权限

上述信息,都需要有组织的存储在数据库中。数据库方面很简单,只需要维护三张表,如果想要加强各表间的联系,可以使用外键。但是,Python该如何组织这些信息呢?每篇博客有不同标题、摘要、内容...,每篇评论和用户信息也个不相同。像C这种面向过程的语言必须创建高级数据结构,而Python这种面向对象的语言真是有天然的优势,我们只需要把每篇博客、评论或者用户看作对象,使用属性表示其蕴含的信息。最后,我们还要解决一个问题:Python和数据库如何高效有组织的交换数据呢?数据库表是由一条条记录组成的,每条记录又包含不同字段。记录和字段,对象和属性...看起来两者关系类不类似?这就是我们的思路——

  将数据库表的每条记录映射为对象,每条记录的字段和对象的属性相应;同时透过对象方法执行SQL命令。

我们编写的ORM框架就是实现上述想法。

  1 #!/usr/bin/env python3
  2 # -*- coding: utf-8 -*-
  3 #Program:
  4 #       This is a ORM for MySQL.
  5 #History:
  6 #2017/06/29       smile          First release
  7 
  8 import logging
  9 import asyncio
 10 import aiomysql
 11 
 12 def log(sql,args=()):
 13     logging.info('SQL: %s' % sql)
 14 
 15 #Close pool
 16 async def destory_pool():
 17     global __pool
 18     __pool.close()
 19     await __pool.wait_closed()
 20 
 21 #Create connect pool
 22 #Parameter: host,port,user,password,db,charset,autocommit
 23 #           maxsize,minsize,loop
 24 async def create_pool(loop,**kw):
 25     logging.info('Create database connection pool...')
 26     global __pool
 27     __pool = await aiomysql.create_pool(
 28         host = kw.get('host', 'localhost'),
 29         port = kw.get('port', 3306),
 30         user = kw['user'],
 31         password = kw['password'],
 32         db = kw['db'],
 33         charset = kw.get('charset', 'utf8'),
 34         autocommit = kw.get('autocommit', 'True'),
 35         maxsize = kw.get('maxsize', 10),
 36         minsize = kw.get('minsize', 1),
 37         loop = loop
 38     )
 39 
 40 #Package SELECT function that can execute SELECT command.
 41 #Setup 1:acquire connection from connection pool.
 42 #Setup 2:create a cursor to execute MySQL command.
 43 #Setup 3:execute MySQL command with cursor.
 44 #Setup 4:return query result.
 45 async def select(sql,args,size=None):
 46     log(sql,args)
 47     global __pool
 48     async with __pool.acquire() as conn:
 49         async with conn.cursor(aiomysql.DictCursor) as cur:
 50             await cur.execute(sql.replace('?','%s'),args or ())
 51             if size:
 52                 rs = await cur.fetchmany(size)
 53             else:
 54                 rs = await cur.fetchall()
 55 
 56         logging.info('rows returned: %s' % len(rs))
 57         return rs
 58 
 59 #Package execute function that can execute INSERT,UPDATE and DELETE command
 60 async def execute(sql,args,autocommit=True):
 61     global __pool
 62     #acquire connection from connection pool
 63     async with __pool.acquire() as conn:
 64         #如果MySQL禁止隐式提交,则标记事务开始
 65         if not autocommit:
 66             await conn.begin()
 67         try:
 68             #create cursor to execute MySQL command
 69             async with conn.cursor(aiomysql.DictCursor) as cur:
 70                 await cur.execute(sql.replace('?','%s'),args or ())
 71                 affectrow = cur.rowcount
 72                 #如果MySQL禁止隐式提交,手动提交事务
 73                 if not autocommit:
 74                     await cur.commit()
 75         #如果事务处理出现错误,则回退
 76         except BaseException as e:
 77             await conn.rollback()
 78             raise
 79 
 80         #return number of affected rows
 81         return affectrow
 82 
 83 #Create placeholder with '?'
 84 def create_args_string(num):
 85     L = []
 86     for i in range(num):
 87         L.append('?')
 88     return ', '.join(L)
 89 
 90 #A base class about Field
 91 #描述字段的字段名,数据类型,键信息,默认值
 92 class Field(object):
 93     def __init__(self,name,column_type,primary_key,default):
 94         self.name = name
 95         self.column_type = column_type
 96         self.primary_key = primary_key
 97         self.default = default
 98 
 99     def __str__(self):
100         return '<%s,%s:%s>' % (self.__class__.__name__,self.column_type,self.name)
101 
102 #String Field
103 class StringField(Field):
104     def __init__(self,name=None,ddl='varchar(100)',default=None,primary_key=False):
105         super(StringField,self).__init__(name,ddl,primary_key,default)
106 
107 #Bool Fileed
108 class BooleanField(Field):
109     def __init__(self,name=None,ddl='boolean',default=False,primary_key=False):
110         super(BooleanField,self).__init__(name,ddl,primary_key,default)
111 
112 #Integer Field
113 class IntegerField(Field):
114     def __init__(self,name=None,ddl='bigint',default=None,primary_key=None):
115         super(IntegerField,self).__init__(name,ddl,primary_key,default)
116 
117 #Float Field
118 class FloatField(Field):
119     def __init__(self,name=None,ddl='real',default=None,primary_key=None):
120         super(FloatField,self).__init__(name,ddl,primary_key,default)
121 
122 #Text Field
123 class TextField(Field):
124     def __init__(self,name=None,ddl='text',default=None,primary_key=None):
125         super(TextField,self).__init__(name,ddl,primary_key,default)
126 
127 #Meatclass about ORM
128 #作用:
129 #首先,拦截类的创建
130 #然后,修改类
131 #最后,返回修改后的类
132 class ModelMetaclass(type):
133     #采集应用元类的子类属性信息
134     #将采集的信息作为参数传入__new__方法
135     #应用__new__方法修改类
136     def __new__(cls,name,bases,attrs):
137         #不对Model类应用元类
138         if name == 'Model':
139             return type.__new__(cls,name,bases,attrs)
140 
141         #获取数据库表名。若__table__为None,则取用类名
142         tablename = attrs.get('__table__',None) or name
143         logging.info('Found model: %s (table: %s)' % (name,tablename))
144 
145         #存储映射表类的属性(键-值)
146         mappings = dict()
147         #存储映射表类的非主键属性(仅键)
148         fields = []
149         #主键对应字段
150         primarykey = None
151         for k,v in attrs.items():
152             if isinstance(v,Field):
153                 logging.info('Found mapping: %s ==> %s' % (k,v))
154                 mappings[k] = v
155 
156                 if v.primary_key:
157                     logging.info('Found primary key')
158                     if primarykey:
159                         raise Exception('Duplicate primary key for field:%s' % k)
160                     primarykey = k
161                 else:
162                     fields.append(k)
163 
164         #如果没有主键抛出异常
165         if not primarykey:
166             raise Exception('Primary key not found')
167 
168         #删除映射表类的属性,以便应用新的属性
169         for i in mappings.keys():
170             attrs.pop(i)
171 
172         #使用反单引号" ` "区别MySQL保留字,提高兼容性
173         escaped_fields = list(map(lambda f:'`%s`' % f,fields))
174 
175         #重写属性
176         attrs['__mappings__'] = mappings
177         attrs['__table__'] = tablename
178         attrs['__primary_key__'] = primarykey
179         attrs['__fields__'] = fields
180         attrs['__select__'] = 'SELECT `%s`, %s FROM `%s`' % (primarykey,','.join(escaped_fields),tablename)
181         attrs['__insert__'] = 'INSERT `%s` (%s,`%s`) VALUES (%s)' % (tablename,','.join(escaped_fields),primarykey,create_args_string(len(escaped_fields) + 1))
182         attrs['__update__'] = 'UPDATE `%s` SET %s WHERE `%s` = ?' % (tablename,','.join(map(lambda f:'`%s` = ?' % (mappings.get(f).name or f),fields)),primarykey)
183         attrs['__delete__'] = 'DELETE FROM `%s` WHERE `%s` = ?' % (tablename,primarykey)
184 
185         #返回修改后的类
186         return type.__new__(cls,name,bases,attrs)
187 
188 #A base class about Model
189 #继承dict类特性
190 #附加方法:
191 #       以属性形式获取值
192 #       拦截私设属性
193 class Model(dict,metaclass=ModelMetaclass):
194     def __init__(self,**kw):
195         super(Model,self).__init__(**kw)
196 
197     def __getattr__(self,key):
198         try:
199             return self[key]
200         except KeyError:
201             raise AttributeError(r"'Model' object has no attribute '%s'" % key)
202 
203     def __setattr__(self,key,value):
204         self[key] = value
205 
206     def getValue(self,key):
207         return getattr(self,key,None)
208 
209     def getValueorDefault(self,key):
210         value = getattr(self,key,None)
211         if value is None:
212             field = self.__mappings__[key]
213             if field.default is not None:
214                 value = field.default() if callable(field.default) else field.default
215                 logging.debug('using default value for %s: %s' % (key,str(value)))
216                 setattr(self,key,value)
217 
218         return value
219 
220     #ORM框架下,每条记录作为对象返回
221     #@classmethod定义类方法,类对象cls便可完成某些操作
222     @classmethod
223     async def findAll(cls,where=None,args=None,**kw):
224         sql = [cls.__select__]
225         #添加WHERE子句
226         if where:
227             sql.append('WHERE')
228             sql.append(where)
229 
230         if args is None:
231             args = []
232 
233         orderby = kw.get('orderby',None)
234         #添加ORDER BY子句
235         if orderby:
236             sql.append('ORDER BY')
237             sql.append(orderby)
238 
239         limit = kw.get('limit',None)
240         #添加LIMIT子句
241         if limit:
242             sql.append('LIMIT')
243             if isinstance(limit,int):
244                 sql.append('?')
245                 args.append(limit)
246             elif isinstance(limit,tuple):
247                 sql.append('?, ?')
248                 args.extend(limit)
249             else:
250                 raise ValueError('Invalid limit value: %s' % str(limit))
251 
252         #execute SQL
253         rs = await select(' '.join(sql),args)
254         #将每条记录作为对象返回
255         return [cls(**r) for r in rs]
256 
257     
258     #过滤结果数量
259     @classmethod
260     async def findNumber(cls,selectField,where=None,args=None):
261         sql = ['SELECT %s _num_ from `%s`' % (selectField,cls.__table__)]
262 
263         #添加WHERE子句
264         if where:
265             sql.append('WHERE')
266             sql.append(where)
267 
268         rs = await select(' '.join(sql),args)
269         if len(rs) == 0:
270             return None
271         return rs[0]['_num_']
272 
273     #返回主键的一条记录
274     @classmethod
275     async def find(cls,pk):
276         rs = await select('%s WHERE `%s` = ?' % (cls.__select__,cls.__primary_key__),[pk],1)
277         if len(rs) == 0:
278             return None
279         return cls(**rs[0])
280 
281     #INSERT command
282     async def save(self):
283         args = list(map(self.getValueorDefault,self.__fields__))
284         args.append(self.getValueorDefault(self.__primary_key__))
285         rows = await execute(self.__insert__,args)
286         if rows != 1:
287             logging.warn('Faield to insert record:affected rows: %s' % rows)
288 
289     #UPDATE command
290     async def update(self):
291         args = list(map(self.getValue,self.__fields__))
292         args.append(self.getValue(self.__primary_key__))
293         rows = await execute(self.__update__,args)
294         if rows != 1:
295             logging.warn('Faield to update by primary_key:affectesd rows: %s' % rows)
296 
297     #DELETE command
298     async def remove(self):
299         args = [self.getValue(self.__primary_key__)]
300         rows = await execute(self.__delete__,args)
301         if rows != 1:
302             logging.warn('Faield to remove by primary key:affected: %s' % rows)

免责声明:文章转载自《ORM框架疏理——廖雪峰实战系列(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇STM32学习笔记(1)——搭建库函数工程linux运维、架构之路-分布式存储Ceph下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MyBatis(四)映射文件 之 参数获取详解#{} 与 ${}

一、#{} 与${} 的取值 相同点: #{}:可以获取map中的值或者pojo对象属性的值; ${}:可以获取map中的值或者pojo对象属性的值; 区别: #{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入; ${}:取出的值直接拼装在sql语句中;会有安全问题; 大多情况下,我们去参数的值都应该去使...

Oracle的dual

1.dual 确实是一张表.是一张只有一个字段,一行记录的表(虚拟表). 2.习惯上,我们称之为'伪表'.因为他不存储主题数据.3. 他的存在,是为了操作上的方便.因为select 都是要有特定对象的.如:select * from mytable ;select * from myview;等等. 4.dual 是由 sql.bsq 建立的。每个data...

sql select 如何定义自增列?

今天在项目中遇到一个问题,想在存储过程中将查询出来的数据放到一个临时表中,不想create table(这人懒 ),但是临时表中需要用到一个自增列,怎么办呢?只好想办法啦 结果如下: drop table #tempSELECT id = IDENTITY(int,1,1),* INTO #TEMP FROM myTable select * from #...

sql server中调用c#写的dll里的方法

最近有一项目:  一超市管理系统单机版,运行在WIN2003+SQL2005上,每天超市关门都都会关电脑,现客户要新加功能,每天关门下班后回家可以上网查看超市管理系统的数据库里的相关数据,然后再做一些原系统没有的统计分析等,老系统不能做大改动,像升级到WIN2012+SQL2012等这些操作,改动越小越好。 现在的想法是:阿里云买台服务器,装上SQL,然...

游标(隐式游标与显示游标)

游标的概念:    游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。游标有两种类型:显式游标和隐式游标。在前述程序中用到的SE...

sql查询语句详解

SQL查询语句详解(一) 一、基本语法 Select select_list From table_name Where condition_expression Group by group_columns having condition_expression Order by sort_columns 二、查询实例 查询所有字段...