ORM(Object Relational Mapping)对象关系映射
很多web框架中都有ORM,它的核心作用有两点
将对象映射成SQL语句,一个对象对应数据库中的一张表,对象中的属性对应表中的字段
通过对象的形式让框架内部进行相应SQL语句的转换,一定程度上避免的SQL注入攻击
当然如果web使用了ORM,那么它就会比使用原始的SQL语句慢,因为多了一层将对象转换未SQL的过程
我们可以使用Python中的元类来实现一个简单的ORM框架,简单看一下Django ORM的源码,发现它也是通过python元类来实现的
自顶向下设计
在程序设计中,有种设计思想叫自顶向下进行设计,从顶部开始思考ORM框架如何实现。那么一个框架的最顶部就是用户如何使用,所以我们编写一段用户使用ORM框架的代码,假定用户这样使用我们的编写的ORM框架,ORM框架中要实现什么内容。
假定用户通过上面的代码使用我们的ORM框架
他定义了一个类User,继承了Model类(Model类是ORM框架提供的基类),在User类中,嵌套了Meta类,表明User类对应到数据库中表的表名
User类定义完后,他希望实例化User类后,可以直接调用save()方法对实例化时传入的数据进行保存
接着就来逐步实现ORM框架,让上面的代码可以正常使用数据库
定义IntegerField类和StringField类
先来定义上面代码中使用的IntegerField类和StringField类,看回上妈的使用代码
IntegerField类表示在数据库中创建bigint类型的字段,字段名使用变量名,这里就是idStringField类表示在数据库中创建varchar类型的字段,因为varchar类型的字段在创建时需要指明字段长度,所有我们希望可以使用max_length参数来表示varchar字段的长度,我们还希望可以通过defalue来设置varchar字段的默认值
下面开写,先定义出所有xxxxField类的基类Field,在Field类中定义出columntype属性表示数据库中字段的类型,maxlength属性表示数据库中字段的长度,default表示字段的默认值
定义好了基类后再定义出StringField类和IntegerField类就比较简单了,其实就是多态,不同处在于columntype属性和maxlength不同
定义ModelMetaclass类和Model类
在上面使用代码中,User类继承了Model类
按照逻辑,接下来,就要实现Model类了,但在实现Model类前,我们先要实现ModelMetaclass这个元类,如果你忘记了元类相关的知识,可以看一下python元类
ModelMetaclass元类的作用就是定义了Model类或Model子类(User类)的创建行为
当Model类要创建时,触发了if name=='Model'下面的代码
当User类(Model子类)要创建时,就会执行除if name=='Model'外的代码
ModelMetaclass类的具体代码如下:
上面代码的逻辑比较简单,从attrs中获得相应的属性,将这些属性存到mappings这个dict中
然后通过pop方法将attrs中已经加到mappings这个dict中的值删除,元类的作用是创建类,从上图也可以看出dict中id、email这些key对应的值其实都是内存地址,删除attrs中值的目的是避免后面使用getattrs()方法出现意想不到的错误
实现好了ModelMetaclass元类,接着就可以写Model类了
上面代码的大致逻辑就是从mappings中取出相应的值,通过getattr获得类中key对应的值,然后构建出一个sql语句
简单说一下getattr()方法:
当 u = User(id=234, name='ayuliao',email='123',password='123456') 这行代码被执行时,因为User是Model的子类,而Model类又绑定了ModelMetaclass元类,所用User在实例化时,ModelMetaclass元类中的_new_方法先与Model类的_init_方法被调用。
在ModelMetaclass元类中,使用了attrs.pop()方法删除attrs中相应的值,这是因为
1.当u.save()被执行时,在save()方法中getattr()会先从类的属性或父类的属性中找相应的值
2.当在类的属性或父类的属性中找不到相应的值时,才会通过_gettattrs_方法来查找
3.如果没用使用attrs.pop()方法将id、name、email、password这些关键字删除,getattr()方法可以直接从类中获得相应的值,这些值在上面也提及了,都是内存地址,而不是我们需要的具体数值
4.所以使用attrs.pop()删除那些关键字,这样就会调用_gettattrs_方法来查找,在_gettattrs_方法中,使用了self[item],因为类的作用是创建实例对象,所以这里的self其实就是User类的实例u,通过实例就可以获得 u = User(id=234, name='ayuliao',email='123',password='123456')这行代码中传入的值
到这里就将一个简单的ORM编写完成了,将上面的代码都放在一个python文件中,运行起来,效果如下
获得具体的SQL后,就可以调用Python链接数据库的库,将这段SQL作为输入,在数据库中创建相应的表,插入相应的字段,而这个过程对用户来说都是透明的,它只见到的定义了一个User类
为什么要使用元类?
仔细观察上面ModelMetaclass类和Model类的代码,感觉ModelMetaclass类的作用似乎就是将User类中定义的元素存到mappings这个dict中,然后赋值给attrs['mappings'],接着就让Model类来使用了。
为什么要让User类中的元素过一遍mappings这个dict呢?
删除元类,似乎也可以实现需求,代码如下:
运行结果:
删去了元类,依旧达到了效果,似乎还简单了很多
如果使用上面的代码来实现ORM框架就失去了ORM框架的意义了,代码中User类完全就是一个壳,完全可以将User类中的内容删除,改成pass,代码照样正常运行,其实也就说明,SQL语句的实现完全取决于实例化User类时传入的参数,这份代码中的Field类其实没啥用处,而一个正常的ORM框架一般都会使用Field类做参数检查、非法参数过滤
小结
可以去翻看一下Django ORM源码相关的博客、文章,理解真正实用的ORM框架在编写时要考虑哪些细节,本章只是实习了一个非常简易的ORM,要让ORM可以在真实项目中稳定运行,还是要多学点,共勉!
领取专属 10元无门槛券
私享最新 技术干货