Django で複数 DB の実装をします。
複数 DB を使いたいケースとしては、主に以下のケースがあると思います。
- Django の app 毎に DB を使用したい
- マスタースレーブ構成
ここでは、Django の app 毎に DB を使用するケースを実装して行きたいと思います。
完成イメージ
バージョン
- Python 3.6.4
- Django 2.0.3
- MySQL 5.7.21
- mysqlclient 1.3.12
インストール
Python, Django
Python と Django のインストールは、Python Web フレームワーク Django の環境を構築するの記事を参考にしてください。
MySQL, mysqlclient
MySQL と mysqlclient のインストールは、Djangoでマイグレーションを行う + MySQLの記事を参考にしてください。
実装
settings.py
ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ ... 'app1.apps.App1Config', 'app2.apps.App2Config', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db1', 'USER': 'root', 'PASSWORD': 'Eba7|B33veK+', 'OPTIONS': { 'charset': 'utf8mb4', } }, 'db2': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db2', 'USER': 'root', 'PASSWORD': 'Eba7|B33veK+', 'OPTIONS': { 'charset': 'utf8mb4', } }, } DATABASE_ROUTERS = ['multidb_app.db_router.DbRouter']
settings.py の DATABASES に DB を2つ定義します。今回は、db1 を default とし、app1 用と Django の認証やセッション管理などを兼務しています。
さらに、DATABASE_ROUTERS を定義します。詳細は後述しますが、この DATABASE_ROUTERS で app と db のマッピングを定義します。
db_router.py
class DbRouter: def db_for_read(self, model, **hints): if model._meta.app_label == 'app1': return 'default' if model._meta.app_label == 'app2': return 'db2' return None def db_for_write(self, model, **hints): if model._meta.app_label == 'app1': return 'default' if model._meta.app_label == 'app2': return 'db2' return None def allow_relation(self, obj1, obj2, **hints): return True def allow_migrate(self, db, app_label, model=None, **hints): if app_label == 'auth' or app_label == 'contenttypes' or app_label == 'sessions' or app_label == 'admin': return db == 'default' if app_label == 'app1': return db == 'default' if app_label == 'app2': return db == 'db2' return None
DB ルータです。
DB ルータでは、4つの関数を定義します。詳細は、マニュアルを確認してください。
ここの定義が大変で、私はいつも db や app_label を print 文で出力しながらいい感じに仕上げていきます。print 文で出力すると、同じ db 名や app_label 名が何回も飛んでくるのがわかると思います。
ここでは、Read と Write、migrate を定義しています。認証やセッションについては、考慮していないので、それらを実装する場合は、見直しが必要だと思います。
models.py
app1/models.py
from django.db import models class Db1Table1(models.Model): column1 = models.CharField(max_length=10) def __str__(self): return self.column1
db1 用の models.py です。テーブル1つにカラムを1つ定義しています。
app2/models.py
from django.db import models class Db2Table1(models.Model): column1 = models.CharField(max_length=10) def __str__(self): return self.column1
db2 用の models.py です。こちらもテーブル1つにカラムを1つ定義しています。
urls.py
from django.urls import path from app1.views import app1 from app2.views import app2 urlpatterns = [ path('app1/', app1), path('app2/', app2), ]
views.py
app1/views.py
from django.http import HttpResponse from app1.models import Db1Table1 def app1(request): Db1Table1.objects.create(column1='app1') return HttpResponse('app1')
動作確認用に、/app1 にアクセスすると、db1 にテーブルを作成するようにします。
app2/views.py
from django.http import HttpResponse from app2.models import Db2Table1 def app2(request): Db2Table1.objects.create(column1='app2') return HttpResponse('app2')
こちらも、動作確認用に、/app2 にアクセスすると、db2 にテーブルを作成するようにします。
これで仕込みを完了です。
DB 作成
create database
$ mysql -u root -p -e 'create database db1 default charset utf8mb4' $ mysql -u root -p -e 'create database db2 default charset utf8mb4'
Django のマイグレーションは、データベースを作成しない(私が知らないだけ)ので、先にデータベースを作成します。
makemigrations
$ python3.6 manage.py makemigrations Migrations for 'app1': app1/migrations/0001_initial.py - Create model Db1Table1 Migrations for 'app2': app2/migrations/0001_initial.py - Create model Db2Table1 $
makemigrations コマンドで、models.py からマイグレーションファイルを生成します。
migrate
$ python3.6 manage.py migrate $ python3.6 manage.py migrate --database=db2
マイグレーションファイルからテーブルを作成します。
makemigrations コマンドは、1回の実行でしたが、こちらは、データベース毎の2回実行します。
show tables
mysql> show tables from db1; +----------------------------+ | Tables_in_db1 | +----------------------------+ | app1_db1table1 | | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | django_admin_log | | django_content_type | | django_migrations | | django_session | +----------------------------+ 11 rows in set (0.00 sec) mysql> show tables from db2; +-------------------+ | Tables_in_db2 | +-------------------+ | app2_db2table1 | | django_migrations | +-------------------+ 2 rows in set (0.00 sec) mysql>
DB ルータで定義したような構成になっている確認します。うまくテーブルを作れていない場合は、DB ルータの migrate 関数を調整し、データベースの作成からやり直します。
tree
$ tree . ├── app1 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── app2 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── manage.py └── multidb_app ├── __init__.py ├── db_router.py ├── settings.py ├── urls.py └── wsgi.py 5 directories, 22 files
ファイル構成はこのようになりました。
動作確認
runserver
$ python3.6 manage.py runserver 0.0.0.0:8000
まずは runserver を実行します。
http://192.168.33.10:8000/app1/
runserver を実行し、http://192.168.33.10:8000/app1/ にアクセスすると、「app1」と出力し、db_router を通って、db1 にレコードを作成します。
http://192.168.33.10:8000/app2/
http://192.168.33.10:8000/app2/ は、「app2」と出力し、db_router を通って、db2 にレコードを作成します。
select * from db1.app1_db1table1
mysql> select * from db1.app1_db1table1; +----+---------+ | id | column1 | +----+---------+ | 1 | app1 | +----+---------+ 1 row in set (0.00 sec) mysql>
select * from db2.app2_db2table1
mysql> select * from db2.app2_db2table1; +----+---------+ | id | column1 | +----+---------+ | 1 | app2 | +----+---------+ 1 row in set (0.00 sec) mysql>
まとめ
Django で複数 app 毎に db を接続しました。
settings.py の DATABASES に、複数の DB を定義し、DATABASE_ROUTERS に DB ルータを定義します。
この DB ルータはなかなか厄介ですが、ここをうまく定義できれば、views.py などの app 下では、複数 DB を全く意識することなく実装することができます。
次回は、この DB ルータを使って、マスタースレーブ 構成を実装して見たいと思います。