在看django源码的时候, django.db.__init__.py里面有这样一行代码
connections = ConnectionHandler()
connection = ConnectionProxy(connections, DEFAULT_DB_ALIAS)
从其他各处都能看到各种导入这个connection, 然后就能直接连数据库了。
而点进去ConnectionProxy中去看, 发现了如下的代码:
class ConnectionProxy:
"""Proxy for accessing a connection object's attributes."""
def __init__(self, connections, alias):
self.__dict__["_connections"] = connections
self.__dict__["_alias"] = alias
def __getattr__(self, item):
return getattr(self._connections[self._alias], item)
def __setattr__(self, name, value):
return setattr(self._connections[self._alias], name, value)
def __delattr__(self, name):
return delattr(self._connections[self._alias], name)
def __contains__(self, key):
return key in self._connections[self._alias]
def __eq__(self, other):
return self._connections[self._alias] == other
好像拿到这个东西, 也没办法直接连数据库, 那django是怎么连接数据库的呢?
首先, 上面传入的connections, 是一个Connectionhandler, 而在Connectionhandler的父类BaseConnectionHandler中, 有针对属性做了特殊处理
def __getitem__(self, alias):
try:
return getattr(self._connections, alias)
except AttributeError:
if alias not in self.settings:
raise self.exception_class(f"The connection '{alias}' doesn't exist.")
conn = self.create_connection(alias)
setattr(self._connections, alias, conn)
return conn
也就是获取一个alias例如default的连接的时候, 如果之前没有建立起这个连接, 就会根据配置, 从django.db.backend中依据配置中的引擎去加载对应的连接类, 然后建立起来连接实例。
def create_connection(self, alias):
db = self.settings[alias]
backend = load_backend(db["ENGINE"])
return backend.DatabaseWrapper(db, alias)
需要注意此处的settings是BaseConnectionHandler中申明的一个类属性
@cached_property
def settings(self):
self._settings = self.configure_settings(self._settings)
return self._settings
也就是说, 在建立连接之前, 会先调用configure_settings方法, 而ConnectionHandler中又对configure_settings方法进行了重载, 也就是在重载中, 完成了对组件的加载
def configure_settings(self, databases):
databases = super().configure_settings(databases)
if databases == {}:
databases[DEFAULT_DB_ALIAS] = {"ENGINE": "django.db.backends.dummy"}
elif DEFAULT_DB_ALIAS not in databases:
raise ImproperlyConfigured(
f"You must define a '{DEFAULT_DB_ALIAS}' database."
)
elif databases[DEFAULT_DB_ALIAS] == {}:
databases[DEFAULT_DB_ALIAS]["ENGINE"] = "django.db.backends.dummy"
# Configure default settings.
for conn in databases.values():
conn.setdefault("ATOMIC_REQUESTS", False)
conn.setdefault("AUTOCOMMIT", True)
conn.setdefault("ENGINE", "django.db.backends.dummy")
if conn["ENGINE"] == "django.db.backends." or not conn["ENGINE"]:
conn["ENGINE"] = "django.db.backends.dummy"
conn.setdefault("CONN_MAX_AGE", 0)
conn.setdefault("CONN_HEALTH_CHECKS", False)
conn.setdefault("OPTIONS", {})
conn.setdefault("TIME_ZONE", None)
for setting in ["NAME", "USER", "PASSWORD", "HOST", "PORT"]:
conn.setdefault(setting, "")
test_settings = conn.setdefault("TEST", {})
default_test_settings = [
("CHARSET", None),
("COLLATION", None),
("MIGRATE", True),
("MIRROR", None),
("NAME", None),
]
for key, value in default_test_settings:
test_settings.setdefault(key, value)
return databases
也就是说对上面所有的connection调用的方法,都会先去hander里面去找, 有没有对应的连接, 没有的话, 则会新建连接, 供ConnectionProxy的__getattr__方法使用, 然后对ConnectionProxy实例也就是connection调用的任何属性或者方法, 都会去上面Connectionhandler中建立的连接实例的相关方法。
以上即为django通过proxy代理模式, 对数据库多组件连接的处理, 通过这种巧妙的方式, 在我看来好处有二,一为懒加载, 不需要的话, 就不会真的去建立连接, 只引入connection的话, 其实引入的是proxy对象, 直到调用方法的时候, 才真正的建立了连接, 并调用连接实例的方法, 其二是通过这种方式, 把对不同的组件的加载抽离成了配置, 而且针对不同的数据库特性和业务之间做到了组件间的分离, 在业务中不需要太过关心数据库组件操作的相关事宜。
其实django源码中海油很多很不错的东西, 比如LazySettings懒加载, 还有数据库迁移中的基于双向链表的算法, 还是很值得一看的。
如果大家对django源码感兴趣, 而功力又不到的话, 可以上B站搜索沈奇才的django源码教程, 相对来说,讲得很细, 可供大家参考。