很多網(wǎng)站有多數(shù)據(jù)庫聯(lián)用的文章,如自強學堂http://code.ziqiangxuetang.com/django/django-multi-database.html
大都只講解如何讓不同的app對應(yīng)不同的數(shù)據(jù)庫,而沒有談到如何讓同一個app里的不同class對應(yīng)不同的數(shù)據(jù)庫。
經(jīng)過N多次試驗,歷經(jīng)好幾天時間,終于找出如下的簡便易行的途徑,而不需要自己造輪子,現(xiàn)總結(jié)如下:
方式一:通過數(shù)據(jù)庫路由自動分發(fā)Model,無需手動using
settings配置如下:
1、增加了DATABASE_ROUTERS,用于指定路由路徑
2、增加了DATABASE_APPS_MAPPING,指定app_label對應(yīng)的數(shù)據(jù)庫
DBNAME = 'multi_test'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'db1': {
'ENGINE': 'django.db.backends.mysql',
'NAME': DBNAME,
'USER': 'root',
'PASSWORD': 'xxx',
'HOST': '127.0.0.1',
'PORT': 3306,
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
},
'db2': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': DBNAME,
'USER': 'root',
'PASSWORD': 'xxx',
'HOST': '127.0.0.1',
'PORT': 5432,
},
'db3': {
'ENGINE': 'django.db.backends.dummy',
},
}
connect(DBNAME) #connect to mongodb
SUB_DIR = os.path.basename(os.path.dirname(__file__))
DATABASE_ROUTERS = [
'{}.database_router.DatabaseAppsRouter'.format(SUB_DIR)
]
DATABASE_APPS_MAPPING = {
# example:
#'app_label':'database_name',
'mysql': 'db1',
'postgres': 'db2',
}
# 'mysql', 'postgres'需加到INSTALLED_APPS中,它們是通過startapp創(chuàng)建的兩個空app
在settings同目錄下增加database_router.py 文件,內(nèi)容如下:
(直接從自強學堂拷貝,只注釋掉print語句,無需做其他更改)
# -*- coding: utf-8 -*-
from django.conf import settings
DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING
class DatabaseAppsRouter(object):
"""
A router to control all database operations on models for different
databases.
In case an app is not set in settings.DATABASE_APPS_MAPPING, the router
will fallback to the `default` database.
Settings example:
DATABASE_APPS_MAPPING = {'app1': 'db1', 'app2': 'db2'}
"""
def db_for_read(self, model, **hints):
""""Point all read operations to the specific database."""
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def db_for_write(self, model, **hints):
"""Point all write operations to the specific database."""
if model._meta.app_label in DATABASE_MAPPING:
return DATABASE_MAPPING[model._meta.app_label]
return None
def allow_relation(self, obj1, obj2, **hints):
"""Allow any relation between apps that use the same database."""
db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label)
db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label)
if db_obj1 and db_obj2:
if db_obj1 == db_obj2:
return True
else:
return False
return None
# for Django 1.4 - Django 1.6
def allow_syncdb(self, db, model):
"""Make sure that apps only appear in the related database."""
if db in DATABASE_MAPPING.values():
return DATABASE_MAPPING.get(model._meta.app_label) == db
elif model._meta.app_label in DATABASE_MAPPING:
return False
return None
# Django 1.7 - Django 1.11
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Make sure that apps only appear in the related database.
根據(jù)app_label的值只在相應(yīng)的數(shù)據(jù)庫中創(chuàng)建一個表,如果刪除該def或
不指定過濾條件,則一個Model會在每個數(shù)據(jù)庫里都創(chuàng)建一個表。
"""
if db in DATABASE_MAPPING.values():
return DATABASE_MAPPING.get(app_label) == db
elif app_label in DATABASE_MAPPING:
return False
return None
models里在class Meta中指定app_label就可以指定它要使用的數(shù)據(jù)庫
例如:settings的DATABASE_APPS_MAPPING中指定app1對應(yīng)db1、app2對應(yīng)db2,
那么app1中的Model默認使用db1,而當指定Meta中的app_label為app2時,則改用db2。
注:app_label指定的值須包含在settings的INSTALLED_APPS中,否則makemigrations不會自動創(chuàng)建表。
app1/models.py內(nèi)容如下:
#coding=utf-8
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Animal(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
# 通過app_label來指定要使用的數(shù)據(jù)庫
# 需指定db_table,否則該class的表名會是mysql_animal
# 如果不指定Meta的app_label,會使用默認數(shù)據(jù)庫
app_label = 'mysql'
db_table = 'app1_animal'
@python_2_unicode_compatible
class Plant(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
app_label = 'postgres'
db_table = 'app1_plant'
app2/models.py
#coding=utf-8
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Fruit(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
app_label = 'mysql' #使用mysql數(shù)據(jù)庫
db_table = 'app2_fruit' #指定表名為app2_fruit
@python_2_unicode_compatible
class Nut(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
app_label = 'postgres' #使用postgres數(shù)據(jù)庫
db_table = 'app2_nut' #指定表名為app2_fruit
app1/test.py
from django.test import TestCase
from app1.models import Animal, Plant
from app2.models import Fruit, Nut
class ModelsTestCase(TestCase):
# db1 -- mysql
# db2 -- postgres
def setUp(self):
Animal.objects.create(name='db1')
Plant.objects.create(name='db2')
Fruit.objects.create(name='mysql_fruit')
Nut.objects.create(name='post_nut')
def test_methods_auto_choose_db(self):
self.assertEqual(Animal.objects.all()[0],
Animal.objects.using('db1').all()[0])
self.assertEqual(Plant.objects.all()[0],
Plant.objects.using('db2').all()[0])
self.assertEqual(Fruit.objects.all()[0],
Fruit.objects.using('db1').all()[0])
self.assertEqual(Nut.objects.all()[0],
Nut.objects.using('db2').all()[0])
self.assertNotEqual(Animal.objects.all()[0],
Fruit.objects.using('db1').all()[0])
self.assertNotEqual(Plant.objects.all()[0],
Nut.objects.using('db2').all()[0])
self.assertNotEqual(Fruit.objects.all()[0],
Plant.objects.using('db2').all()[0])
然后在命令行中運行
python manage.py makemigrations
python manage.py migrate --database=db1
python manage.py migrate --database=db2
python manage.py migrate
python manage.py test
完整代碼放在coding上:
git clone https://git.coding.net/zhengwenjie/multi_dbs.git
cd multi_dbs
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
vi multi_dbs/settings.py #修改數(shù)據(jù)庫名和密碼等
alias mg='python manage.py'
mg makemigrations
mg migrate --database=db1
mg migrate --database=db2
mg migrate
mg test
方式二:通過using手動指定數(shù)據(jù)庫
例如settings中的數(shù)據(jù)庫配置如下:
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
import pymysql
pymysql.install_as_MySQLdb()
from mongoengine import connect
DBNAME = 'multi_test'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'mysql': {
'ENGINE': 'django.db.backends.mysql',
'NAME': DBNAME,
'USER': 'root',
'PASSWORD': 'xxx',
'HOST': '127.0.0.1',
'PORT': 3306,
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
},
'postgres': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': DBNAME,
'USER': 'root',
'PASSWORD': 'xxx',
'HOST': '127.0.0.1',
'PORT': 5432,
},
'mongo': {
'ENGINE': 'django.db.backends.dummy',
},
}
則查詢和創(chuàng)建數(shù)據(jù)語句如下(不加using為采用default數(shù)據(jù)庫)
from app1.models import House
House.objects.using('mysql').create(name='Tom')
House.objects.using('mysql').get(name='Tom')
House.objects.using('postgres').get_or_create(name='Jim')