一个web系统可能会用到多个数据库,下面以Mysql、Oracle为例
运行环境为:python==3.7.2,django==1.11.20,cx-oracle==7.12,mysqlClient==1.42,oracle数据库是11版本
默认的SQLite这里不再赘述,我将他去掉了,这里主要说Mysql数据库和Oracle数据库,下图是代码结构:
1. 首先在配置文件里配置Mysql和Oracle,config.ini内容如下:
[Mysql-Database]
host = 111.xx.xx.xx
port = 3306
name = xxx
user = xxx
password = xxx
[Oracle-Database]
host = 118.xxx.xxx.xxx
port = 1521
name = xxx
user = xxx
password = xxx
2. 修改setting文件,添加mysql app和oracle app
"""
Django settings for multiple_database project.
Generated by 'django-admin startproject' using Django 2.2.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import configparser
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'qaplf-ft+g$=89ll-p4d8dxit+y_p)jqfr^^b$6)a-#2xrqlwr'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app_Mysql',
'app_Oracle'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'multiple_database.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'multiple_database.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
config_init = configparser.ConfigParser()
config_init.read(os.path.join(BASE_DIR, 'config/config.ini'))
# config_Json = ''
# with open(os.path.join(BASE_DIR, 'config/config.json'), 'rt') as f:
# config_Json = json.load(f)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': config_init.get("Mysql-Database", "host"),
'PORT': config_init.get("Mysql-Database", "port"),
'NAME': config_init.get("Mysql-Database", "name"),
'USER': config_init.get("Mysql-Database", "user"),
'PASSWORD': config_init.get("Mysql-Database", "password")
},
'db_Oracle': {
'ENGINE': 'django.db.backends.oracle',
'HOST': config_init.get("Oracle-Database", "host"),
'PORT': config_init.get("Oracle-Database", "port"),
'NAME': config_init.get("Oracle-Database", "name"),
'USER': config_init.get("Oracle-Database", "user"),
'PASSWORD': config_init.get("Oracle-Database", "password")
},
}
DATABASE_ROUTERS = ['multiple_database.database_router.DatabaseAppsRouter']
# appName,dbName
DATABASE_APPS_MAPPING = {
'app_Mysql': 'default',
'app_Oracle': 'db_Oracle',
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
USE_I18N = True
USE_L10N = True
USE_TZ = True
TIME_ZONE = 'Asia/Shanghai'
LANGUAGE_CODE = 'zh-hans'
DEFAULT_CHARSET = "utf-8"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
我将setting文件中的关键部分截了下来,放到了下面:
其中:DATABASE_APPS_MAPPING是多数据库配置的核心,这里以map存储
DATABASE_ROUTERS = ['multiple_database.database_router.DatabaseAppsRouter']
# appName,dbName
DATABASE_APPS_MAPPING = {
'app_Mysql': 'default',
'app_Oracle': 'db_Oracle',
}
注意:这里要按需修改,配置不正确就会导致数据库无法同步(models生成表)和映射(表生成models):
multiple_database是你的Django项目名称
app_mysql和app_Oracle是项目中app名称,
db_Mysql和db_Oracle是DATABASES中配置的数据库名称
3. 添加database_router.py文件,这个文件来源于自强学堂
原文地址为:https://code.ziqiangxuetang.com/django/django-multi-database.html
# -*- 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):
if db in DATABASE_MAPPING.values():
return DATABASE_MAPPING.get(app_label) == db
elif app_label in DATABASE_MAPPING:
return False
return None
4 .将mysql数据库中的表结构映射到Model中:
python manage.py inspectdb --database=db_Mysql > app_Mysql/models.py
5. 将Oracle数据库中的表结构映射到Model中:
python manage.py inspectdb --database=db_Oracle > app_Oracle/models.py
上述命令执行完成后,各APP下面将产生对应数据库的models
我这里的oracle是11版本的,所以要使用2.0以下的django版本,如果使用的是oracle12版本的,建议使用Django2.0以上,
上述命令都是在Terminal中执行的,亲测可行
上面的语句执行完成后,会分别在两个app下面生成对应的models内容,这里不再展示!
6. 在mysql app下的Model.py中添加新的表结构:
首先将数据库原有的表结构执行以下命令,即初始化原有model:
python manage.py makemigrations app_Mysql
初始化完成会生成0001_inital.py文件,然后我们在models.py中添加新的表结构
class Demo(models.Model):
l_unit_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
db_table = 'T_Demo'
然后再次执行下面的makemigrations命令,该命令会生成0002_demo.py文件:
python manage.py makemigrations app_Mysql
为了验证makemigrations 命令是否正确执行,在Terminal中输入以下语句,命令将会打印出对应的sql语句:
python manage.py sqlmigrate app_Mysql 0002
然后Terminal会输出:
-- Create model Demo
--
CREATE TABLE `T_Demo` (`l_unit_id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(255) NULL);
COMMIT;
然后再执行migrate命令,我们新建的表就会同步到Mysql了:
python manage.py migrate app_Mysql
Terminal将打印下面的内容:
(venv) C:\Users\X250\Desktop\multiple_database>python manage.py migrate app_Mysql
Operations to perform:
Apply all migrations: app_Mysql
Running migrations:
Applying app_Mysql.0001_initial... OK
Applying app_Mysql.0002_demo... OK
使用Navicat发现,新建的Demo表已经创建到了数据库中:
注意makemigrations和migrate命令后跟的的参数,参数错误,将导致同步失败
7. 在app_Oracle的Model.py中添加新的表结构:
和app_Mysql一样的操作,但是结果却完全不同,真的很恶心。。。。
和Mysql一样,先执行makemigrations命令:
python manage.py makemigrations app_Oracle
然后再执行migrate命令:
python manage.py migrate --database=db_Oracle
再添加Demo表,步骤和mysql一样,但是添加新表时,就会出现下图的问题,如图:
(venv) C:\Users\X250\Desktop\multiple_database>python manage.py makemigrations app_Oracle
Migrations for 'app_Oracle':
app_Oracle\migrations\0002_demo2.py
- Create model Demo2
(venv) C:\Users\X250\Desktop\multiple_database>python manage.py sqlmigrate app_Oracle 0002
BEGIN;
--
-- Create model Demo2
--
COMMIT;
下面是mysql的建表语句:
-- Create model Demo
--
CREATE TABLE `T_Demo` (`l_unit_id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY);
COMMIT;
对比发现,Oracle创建 Demo2时,里面是不含有任何sql语句,而mysql却包含了建表语句。
这个应该是Django1.11和Oracle11两者间的bug。
为了绕过这个问题,只能在Oracle中先建立表,通过反射生成model进行,真的很智障:
如图,
1. 先在oracle创建表,字段名一定要大写:
2. 删除migrations文件夹中的内容,_init_.py不删除
3. 清除数据库中django_migrations中和app_Oracle相关的内容,我这里删除过了
注意:默认的是mysql数据库,所以应该去mysql数据库里进行删除
4. 删除models.py文件中的所以内容
5. 执行反射命令
python manage.py inspectdb --database=db_Oracle > app_Oracle/models.py
然后demo表的models就映射到了models.py文件
6. 执行 makemigrations 命令
python manage.py makemigrations app_Oracle
ok,Oracle无法创建表的问题,我们绕了过去,现在要验证多数据库配置能否被django调用。
8. 多数据库校验
1. 启动管理工具,并创建super用户,注意:super用户在默认的数据库中创建
创建的流程请参考这个链接:https://www.runoob.com/django/django-admin-manage-tool.html
2. 在各app下添加admin对指定models的管理,如图
3. 启动web项目,打开管理页面:
4. 操作app_Mysql下的demo试一下,没有问题:
5. 操作app_Oracle下的demo试一下,没有问题
注意:如果这里报出ID:invalid identifer的错误,请确认Demo表中含有主键,且字段名为大写!!!
注意:如果这里报出ID:invalid identifer的错误,请确认Demo表中含有主键,且字段名为大写!!!
注意:如果这里报出ID:invalid identifer的错误,请确认Demo表中含有主键,且字段名为大写!!!
至此,oracle和mysql这两个数据的配置完成