这部分标题比较大,按照之前的分析方法肯定会比较复杂且不够系统,所以从另一个角度出发,我们通过对几个关键问题的追溯来帮助我们了解整个Django ORM的设计思想:
- Django ORM如何做到多数据库支持的;
- Django ORM中的objects是什么;
- Filter方法的查询流程;
1、准备工作
- Python 3.5.2
- Django 2.1.2
- PyCharm 2018.2.1 (Professional Edition)
- 启动项目
1 | [min:] ~/Desktop/python/Demo$ python manage.py runserver 0.0.0.0:8000 |
2、分析流程
现在我们开始根据上述提到的问题进行逐个的分析。
2.1、Django ORM如何做到多数据库支持的
2.1.1、 Django db source tree
1 | ├── __init__.py |
按照科学的推断,如果要做到多数据库的支持,一般的结构肯定是有一个Wrapper保证对外的接口一致,然后在这个Wrapper中,负责加载不同的数据库类型,执行相应的方法。而Django db的源码也和我们的猜想差不多,将后端的集中到backends中,在此结构下差异化不同的数据库;
2.1.2、具体的差异化加载流程
以Django服务启动时的数据库连接检查为例:
在Django源码分析一:服务启动一文中,我们有分析过Django服务的启动流程,在这个过程中间也包含了对数据库连接的检查,具体路径如下:django.core.management.base.BaseCommand#check_migrations,在这个方法中,引用了django.db.connections:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from django.db import DEFAULT_DB_ALIAS, connections # 调用ConnectionHandler的__init__方法
#.............省略...............
def check_migrations(self):
"""
Print a warning if the set of migrations on disk don't match the
migrations in the database.
"""
from django.db.migrations.executor import MigrationExecutor
try:
# python的魔术方法,调用到ConnectionHandler的__getitem__方法
executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
except ImproperlyConfigured:
# No databases are configured (or the dummy one)
return我们发现
connections = ConnectionHandler()
,查看ConnectionHandler类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38class ConnectionHandler:
def __init__(self, databases=None):
"""
databases is an optional dictionary of database definitions (structured
like settings.DATABASES).
"""
self._databases = databases # 调用databases(self)方法
self._connections = local()
def databases(self):
if self._databases is None:
self._databases = settings.DATABASES
if self._databases == {}:
self._databases = {
DEFAULT_DB_ALIAS: {
'ENGINE': 'django.db.backends.dummy',
},
}
if DEFAULT_DB_ALIAS not in self._databases:
raise ImproperlyConfigured("You must define a '%s' database." % DEFAULT_DB_ALIAS)
if self._databases[DEFAULT_DB_ALIAS] == {}:
self._databases[DEFAULT_DB_ALIAS]['ENGINE'] = 'django.db.backends.dummy'
return self._databases
................
def __getitem__(self, alias):
if hasattr(self._connections, alias):
return getattr(self._connections, alias)
self.ensure_defaults(alias)
self.prepare_test_settings(alias)
db = self.databases[alias]
backend = load_backend(db['ENGINE']) # 重要!!根据ENGINE的类型决定使用哪一种数据库
conn = backend.DatabaseWrapper(db, alias)
setattr(self._connections, alias, conn)
return conn从上面代码注释可以了解到在
__init__
方法中通过调用databases完成对_databases
属性的赋值,将settings中的DATABASES赋值给这个变量;之后在check_migrations
方法中调用了ConnectionHandler的__getitem__
方法;django.db.utils.load_backend
1
2
3
4
5
6
7
8
9
10
11
12
13def load_backend(backend_name):
"""
Return a database backend's "base" module given a fully qualified database
backend name, or raise an error if it doesn't exist.
"""
# This backend was renamed in Django 1.9.
if backend_name == 'django.db.backends.postgresql_psycopg2':
backend_name = 'django.db.backends.postgresql'
try:
return import_module('%s.base' % backend_name)
except ImportError as e_user:
#.............省略...............1
2
3
4
5
6
7
8
9
10
11# 示例settings.DATABASES
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config.DATABASES_NAME,
'USER': config.DATABASES_USER,
'PASSWORD': config.DATABASES_PASSWORD,
'HOST': config.DATABASES_HOST,
'PORT': config.DATABASES_PORT,
}
}在这个方法中,根据settings.DATABASES的ENGINE值,完成对不同类型数据库的加载;每个类型的数据库拥有一个DatabaseWrapper作为其代理,作为后续操作的具体对象。
2.2 objects的作用
在分析ORM的filter之前,我们无法绕开objects这个方法,因为我们发现貌似所有的数据库操作都是基于objects,比如最常见的:
1 | ret = models.Book.objects.filter(title="Django"); |
那么这个objects究竟是什么,对整个数据库操作有着怎样的作用呢?
2.2.1 django.db.models.base.ModelBase
1 | def __new__(cls, name, bases, attrs, **kwargs): |
从使用方式上我们可以看到objects是Model的一个属性,那么这个属性是什么时候赋值给Model的呢?Book继承于Model,Model继承于ModelBase,在ModelBase中有如上两个重要方法(见注释)完成对objects的赋值。但是有一个问题我们需要注意就是赋值操作使用的是add_to_class方法而不是常见病的setter方法,那么这个方法的作用是什么呢?
1 | def add_to_class(cls, name, value): |
从上面方法中,我们可以看到最后会调用contribute_to_class
方法,这个方法属于BaseManager。
2.2.1 django.db.models.manager.BaseManager
1 | def contribute_to_class(self, model, name): |
结合上面的分析,可以看到其实objects最后赋值的对象应该是ManagerDescriptor,这个是什么呢??
1 | class ManagerDescriptor: |
可以看到在使用Book.objects的时候其实正在起作用的还是传入进来的Manager示例,为什么要多此一举呢?
django 规定, 只有 Model 类可以使用 objects, Model 类实例不可以. 请注意区分类和类实例之间的区别.
其实是非常有道理的, Book.objects.filter(id=1) 返回的是 QuerySet 对象, 而 QuerySet 对象可以看成是 Model 实例的集合, 也就是 book_set 是 Model 实例的集合。假使Model 类的实例可以使用 objects 属性, 即从一本书中查询书」这在语意上不通过. 只能是从书的集合(Book)中查询书,所以 django 用 ManagerDescriptor 特意为 Manager 做的一层包装来校验。
2.2.3 django.db.models.manager.Manager
1 | class Manager(BaseManager.from_queryset(QuerySet)): |
1 |
|
从这两个方法中,可以看到其实Manager真正继承的应该是QuerySet这个方法,所以后续的filter,get等方法其实都是基于QuerySet的。
2.3 Filter方法的查询流程
这一章节的重点是让大家理解从Object到SQL到转化,了解Django ORM是如果工作的,为了让思路更加聚焦,所以涉及到一些细节的问题,不会再在Code层进行分析,只会提一下,有兴趣的话可以单独深入分析。
2.3.1 django/db/models/query.py
在使用filter的时候,其实调用的是_filter_or_exclude方法
1 | def filter(self, *args, **kwargs): |
1 | def _filter_or_exclude(self, negate, *args, **kwargs): |
2.3.2 django/db/models/sql/query.py
1 | def add_q(self, q_object): |
添加当前的Q对象到已存在的filter中,然后将返回的where对象插入到当前类的where中,且用and连接表示;另外在这个方法中同时处理了Django ORM中的通过__
符号来连接外健的操作。
一般来讲,where语句写完,就应该进行查询操作,但是在重新顺着流程捋了一遍,都没有找到在什么地方有执行sql的操作,最后发现是因为Django的惰性查询关系,做完这些操作之后,并不会马上执行sql,而是等待需要用的Queryset的__iter__
的时候,才去真正的根据QuerySet 已经设置好的各种查询条件,去编译sql语句,执行并返回结果.
以如下语句为例:
1 | ret = models.Book.objects.filter(title="Django"); |
在执行完filter方法之后,使用断点或者日志打印的方式发现ret其实是一个django.db.models.query.QuerySet对象,然后使用如上两种方式才可以打印出具体的Book信息,所以接下来我们就需要看一下QuerySet的__iter__
方法。
2.3.3 django.db.models.query.ModelIterable
1 | def __getitem__(self, k): |
1 | def _fetch_all(self): |
1 | class ModelIterable(BaseIterable): |
2.3.4 django/db/models/sql/compiler.py
1 | def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE): |
此方法中通过self.as_sql()拿到sql语句跟参数,获取cursor游标,执行sql并得到结果,然后根据传入的result_type来从游标中返回正确的结果集;
1 | def as_sql(self, with_limits=True, with_col_aliases=False): |
如果我们忽略掉这过程中的许多细节如:怎么获取select,where,order_by等sql部分,怎么对上面各部分各个连接啊,参数等合法检验等部分,就会发现,其实as_sql的实现方式不外乎就是: 用list一次存储各个部分,然后””.join方式连接这个list成一个字符串,当然,各部分包括(但不限于):
- select部分
- distinct
- where表达式
- group表达式
- having表达式
- 是否加入limit or offset