本篇文章就是要实现一个简单版本的ORM框架,前面的面向对象写了好几篇,光说不练是不行的,因此用ORM实现,好好巩固一下自己的学习效果,也为以后WEB框架–Django的学习打下基础。废话不多说,开始吧。
#什么是ORM
ORM—Object Relational Mapping 全称对象关系映射。
ORM框架主要是为方便操作数据库设计,数据库的一张表对应ORM框架中的一个类,表中的字段对应类的字段属性,表的增、删、改、查操作,对应类的增、删、改、查方法属性,通过ORM框架,将数据库表操作的需要的SQL语句进行封装,变成对类以及类的对象进行操作,简化程序设计,不用累死累活的写原生的SQL语句了。当然了,原生SQL语句咱也要会写,此事后话。
既然要手撸一个ORM,那就要明确为什么样的需求写一个ORM框架,我不会凭空造一个需求,因此就以老男孩学习视频中关于数据库与多线程操作的作业需求来写。需求如下:
1、仿视频网站实现服务器与客户端,允许多用户登录
2、实现会员充值
3、实现上传与下载视频
4、实现发布公告
5、查看观影记录
6、查看视频与公告
综上所述,需要在数据库中设计几张表:用户表、公告表、电影表。
根据前述说明,需要设计用户表、公告表、电影表
字段名称 | 用途 |
---|---|
id | 用户的索引序号 |
name | 用户名 |
password | 密码 |
is_vip | 是否为VIP充值用户 |
user_type | 用户类型,普通用户或者管理员用户 |
is_locked | 用户是否锁定 |
字段名称 | 用途 |
---|---|
id | 电影的索引序号 |
name | 电影名称 |
path | 电影文件存放路径 |
is_free | 是否免费 |
is_delete | 是否设置为删除状态 |
create_time | 创建时间 |
user_id | 创建用户 |
file_md5 | 电影文件的MD5值 |
字段名称 | 用途 |
---|---|
id | 公告的索引序号 |
name | 公告名称 |
content | 公告内容 |
create_time | 创建时间 |
user_id | 创建用户 |
字段类用于描述表格中的每一列,而一个表包含多中不同属性的列,因此需要多种不同的字段类,因此我们先设计一个字段基类,然后由这个基类派生出其它不同属性的字段类
class Fileld:
def __init__(self,name,column_type,primary_key,default):
self.name=name
self.column_type=column_type
self.primary_key= primary_key
self.default=default
字段基类的基本属性:字段名称(name)、字段类数据类型(colum_type)、是否为主键(primary_key)、默认值(default)
class StringFileld(Fileld):
def __init__(self,name=None,column_type='varchar(200)',primary_key=False,default=None):
super().__init__(name,column_type,primary_key,default)
基本字符串字段类,在继承Filed类的基础上,仅仅是为字段的基本属性设置一个默认参数值
column_type被设置为与数据库数据类型对应的vchar类型,最大长度为200,默认不是主键、默认值为None
class IntegerFileld(Fileld):
def __init__(self,name=None,column_type='int',primary_key=False,default=0):
super().__init__(name,column_type,primary_key,default)
数值字段类,在继承Filed类的基础上,仅仅是为字段的基本属性设置一个默认参数值
column_type被设置为与数据库数据类型对应的int类型,默认不是主键、默认值为None
class User(Modles):
table_name='userinfo'
id=IntegerFileld('id',primary_key=True)
name=StringFileld('name')
password=StringFileld('password')
is_vip = IntegerFileld('is_vip')
locked= IntegerFileld('locked')
user_type=StringFileld('user_type')
可以看到,user用户表类中,除去table_name字段外,其它字段都是StringFileld或者IntegerFileld类的一个对象,根据实际需求,传递字段的属性name、column_type、primary_key、default对应的值
class Movie(Modles):
table_name='movie'
id =IntegerFileld('id',primary_key=True)
name=StringFileld('name')
path=StringFileld('path')
is_free=IntegerFileld('is_free')
is_delete=IntegerFileld('is_delete')
create_time=StringFileld('create_time')
user_id=IntegerFileld('user_id')
file_md5=StringFileld('file_md5')
class Notice(Modles):
table_name='notice'
id =IntegerFileld('id',primary_key=True)
name=StringFileld('name')
content=StringFileld('content')
create_time=StringFileld('create_time')
user_id=IntegerFileld('user_id')
以上三个应用表格对应的类,并没有使用外键关联方式,这里的ORM框架,我们仅考虑单表操作,并且我们只设计了两种字段类型IntegerFileld、StringFileld。
元类的设计分为两步,首先设计Models类,然后指定Models的元类,这样所有继承Models的类,都将继承其元类。
还有一个问题我们要考虑,在前一篇博客中我说道,元类可以拦截类的创建过程,可以拦截对象的创建过程,那么这个ORM框架到底使用哪种方式呢。
想想本片博文一直在描述表格类怎么设计,怎么继承、使用元类;所以应该是使用拦截类的创建过程,这种方式使用元类。
class Modles(dict,metaclass=ModlesMetaclass):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def __setattr__(self, key, value):
self[key]=value
def __getattr__(self, item):
try:
return self[item]
except TypeError:
raise ('没有该属性')
Models类继承自字典类,元类指定为ModelsMetaclass。
__setattr__(self, key, value)
方法,目的是在字典的基础上,实现“.”句点运算符赋值的方式,这样既可以字典的方式进行属性赋值,又可以普通对象使用句点方式进行属性赋值__getattr__(self, item)
,只有在使用‘句点’调用属性且属性不存在的时候才会触发,使此类的对象可以使用句点方式访问属性的值class ModlesMetaclass(type):
def __new__(cls, name,bases,attrs):
if name=='Modles':
return type.__new__(cls,name,bases,attrs)
table_name=attrs.get('table_name',None)
if table_name == None:
table_name = name
# table_name=attrs['table_name']#不使用这种方式获取表名属性,是因为当attrs字典不存在‘table_name’属性时会报错
primary_key=None
mappings=dict()
for k,v in attrs.items():
if isinstance(v,Fileld):#v 是不是Field的对象
mappings[k]=v
if v.primary_key:
#找到主键
if primary_key:
raise TypeError('主键重复:%s'%k)
primary_key=k
for k in mappings.keys():
attrs.pop(k)
if not primary_key:
raise TypeError('没有主键')
attrs['table_name']=table_name
attrs['primary_key']=primary_key
attrs['mappings']=mappings
return type.__new__(cls,name,bases,attrs)
ModelMetaclass主要是重写__new__()
方法,拦截类的创建过程,完成元类的高级操作,此高级操作就是拦截所有基于ModelMetaclass元类的类创建,为类的创建增加table_name、primary_key、mappings三个字段,并将类中基于Field的属性字段进行重塑,放入mappings对应的类属性字典中。详细解析如下:
__setattr__(self, key, value)
函数执行,相当于为对象实例增加一个name属性,我们让name与mappings中的key的name以及key对应的value值的name属性(即Field.name)相对应,名称保持一致才能进行后续关于表的增、删、改、查操作。未经过元类改造的类名称空间 | 经过元类改造的类名称空间 |
---|---|
table_name=‘userinfo’ | table_name |
id=IntegerFileld(‘id’,primary_key=True) | primary_key |
name=StringFileld(‘name’) | mappings,此中存放所有基于Field类的子类实例 |
password=StringFileld(‘password’) | |
is_vip = IntegerFileld(‘is_vip’) | |
locked= IntegerFileld(‘locked’) | |
user_type=StringFileld(‘user_type’) |
以下Models类是在元类基础上,完成数据库中表的查找、更新、保存操作,暂未实现删除操作
class Modles(dict,metaclass=ModlesMetaclass):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def __setattr__(self, key, value):
self[key]=value
def __getattr__(self, item):
try:
return self[item]
except TypeError:
raise ('没有该属性')
@classmethod
def select_one(cls,**kwargs):
#只查一条
key=list(kwargs.keys())[0]
value=kwargs[key]
#select * from user where id=%s
sql='select * from %s where %s=?'%(cls.table_name,key)
#
sql=sql.replace('?','%s')
ms=Mysql_poo.Mysql()
re=ms.select(sql,value)
if re:
#attrs={'name':'123','password':123}
#u=User(**attrs)
#相当于 User(name='123',password=123)
u=cls(**re[0])
return u
else:
return
@classmethod
def select_many(cls,**kwargs):
ms = Mysql_poo.Mysql()
if kwargs:
key=list(kwargs.keys())[0]
value=kwargs[key]
sql = 'select * from %s where %s=?' % (cls.table_name, key)
#
sql = sql.replace('?', '%s')
re = ms.select(sql, value)
else:
sql = 'select * from %s'%(cls.table_name)
re = ms.select(sql)
if re:
lis_obj=[cls(**r) for r in re]
return lis_obj
else:
return
def update(self):
ms = Mysql_poo.Mysql()
#update user set name=?,password=? where id=1
filed=[]
pr=None
args=[]
# mapping={id:inter的对象,name:strfil的对象,password:stringfile 的对象}
# user:1 table_name 2 primary_key 3 mapping
# 4 name='123'5 id=1 6 password=123
for k,v in self.mappings.items():
if v.primary_key:
pr=getattr(self,v.name,None)#v.name = id
else:
filed.append(v.name + '=?')
args.append(getattr(self,v.name,v.default))
getattr(self, v.name, None) # 拿到123
sql = 'update %s set %s where %s =%s'%(self.table_name,','.join(filed),self.primary_key,pr)
#'update user set name=?,password =? where id =1'
sql=sql.replace('?','%s')
ms.execute(sql,args)
def save(self):
ms = Mysql_poo.Mysql()
#insert into user (name,passwword) values (?,?)
filed=[]
values=[]
args=[]
for k,v in self.mappings.items():
if not v.primary_key:
filed.append(v.name)
values.append('?')
args.append(getattr(self,v.name,None))
sql ='insert into %s (%s) VALUES (%s)'%(self.table_name,','.join(filed),','.join(values))
sql= sql.replace('?','%s')
ms.execute(sql,args)
好了,这一篇基于元类的ORM框架介绍到此,此时的我貌似有一点明白元类干了哪些活了,但是为什么要如此操作,还不甚明了,写不出所以然来,待以后理解的更加深刻之后,再来续此篇吧。