我曾经做过与第一点最接近的事情,但是使用的不是中间件来设置默认连接,而是使用Django数据库路由器。这样可以让应用程序逻辑在每个请求中使用多个数据库(如果需要)。应用程序逻辑需要为每个查询选择一个合适的数据库,这是这种方法的主要缺点。
使用这种设置,所有数据库都列在settings.DATABASES中,包括可能在客户之间共享的数据库。将每个特定于客户的模型放置在具有特定应用标签的Django应用程序中。
例如,以下类定义了一个存在于所有客户数据库中的模型。
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
数据库路由器被放置在settings.DATABASE_ROUTERS
链中,通过app_label
路由数据库请求,类似于以下示例(不是完整示例):
class AppLabelRouter(object):
def get_customer_db(self, model):
if model._meta.app_label == 'myapp':
return 'shared_db'
if model._meta.app_label == 'customer_records':
customer_db = thread_local_data.current_customer_db()
if customer_db is not None:
return customer_db
raise Exception("No customer database selected")
return None
def db_for_read(self, model, **hints):
return self.get_customer_db(model, **hints)
def db_for_write(self, model, **hints):
return self.get_customer_db(model, **hints)
这个路由器的特殊之处在于
thread_local_data.current_customer_db()
调用。在使用路由器之前,调用者/应用程序必须在
thread_local_data
中设置当前客户数据库。可以使用Python上下文管理器来实现此目的,以推送/弹出当前客户数据库。
配置完所有内容后,应用程序代码看起来像这样,其中
UseCustomerDatabase
是一个上下文管理器,将当前客户数据库名称推入/弹出
thread_local_data
,以便在最终命中路由器时
thread_local_data.current_customer_db()
返回正确的数据库名称:
class MyView(DetailView):
def get_object(self):
db_name = determine_customer_db_to_use(self.request)
with UseCustomerDatabase(db_name):
return MyModel.object.get(pk=1)
这是一个相当复杂的设置。它可以运行,但我会尝试总结一下我看到的优点和缺点:
优点
- 数据库选择灵活。它允许在单个查询中使用多个数据库,可以在请求中使用特定于客户和共享的数据库。
- 数据库选择是明确的(不确定这是优点还是缺点)。如果您尝试运行命中客户端数据库的查询,但应用程序没有选择其中一个,将出现异常,指示编程错误。
- 使用数据库路由器允许不同的数据库存在于不同的主机上,而不是依赖于猜测所有数据库都可以通过单个连接访问的
USE db;
语句。
缺点
- 设置复杂,并涉及很多层来使其正常工作。
- 线程本地数据的需求和使用是模糊的。
- 视图中充斥着数据库选择代码。这可以使用基于类的视图抽象化,以根据请求参数自动选择数据库的方式来解决,就像中间件会选择默认数据库一样。
- 选择数据库的上下文管理器必须在查询集周围进行包装,以便在评估查询时仍然处于活动状态。
建议
如果您想要灵活的数据库访问,我建议使用Django的数据库路由器。使用中间件或视图Mixin自动设置默认数据库以用于基于请求参数的连接。您可能需要诉诸线程本地数据来存储要使用的默认数据库,以便在命中路由器时,它知道要路由到哪个数据库。这允许Django使用其现有的持久连接到数据库(如果需要,可以驻留在不同的主机上),并根据请求中设置的路由选择要使用的数据库。
此方法的另一个优点是,如果需要,可以通过使用QuerySet using()
函数选择默认之外的其他数据库来重写查询的数据库。