save()메서드 인자(force_insert/force_update/commit/using) 그리고 다중데이터베이스

권수민·2023년 10월 1일
1

save()는 Model에서 제공된느 장고의 메서드로서 객체를 데이터베이스에 저장 또는 업데이트 할때 사용.

다양한 인자를 받게되는데,

force_insert (기본값: False):

만약 True로 설정될 시 이 객체를 데이터베이스에 새로운 레코드로 삽입.
즉, 기존에 있는 데이터레코드인지 확인을 해서 존재한다하여도 그 존재하는 레코드의 갱신은 시도되지않고 새로운 id를 가지는 데이터로 생성됩니다.
마지막 id가 16이였으면 17로 새로 생성되겠죠?

new_user = User(username="new_user", email="new_user@example.com")
new_user.save(force_insert=True)

반면에

force_update (기본값: False):

만약 True로 설정되면, 장고는 이 객체를 데이터베이스의 존재하는 레코드로 갱신하려 할 것입니다. 즉, 존재하는 데이터 중에서 업데이트를 시도하는데, 해당객체의 기존레코드가 없으면 오류가 발생하며, 새 레코드의 삽입은 시도하지 않습니다.

existing_user = User.objects.get(username="existing_user")
existing_user.email = "updated_email@example.com"
existing_user.save(force_update=True)

여기서 commit=False의 경우는 뭐가 다른가?

일단 commit=False는 모델의 save() 메서드나, 모델 폼의 save() 메서드에서도 사용되지만 force_update와는 다르게 실제 데이베이스에 저장되지 않고 객체를 메모리에만 저장한다. 그렇게 사용하여 따로 실제 데이터베이스에 저장하지 않고 필요한 추가적인 사항을 적용한 후에 객체를 수동으로 저장하여 사용한다.

using:

여러 데이터베이스가 있는 경우 특정 데이터베이스에 연결하여 연산을 실행할 수 있습니다.
예를 들어, 프로젝트에 default와 archive 라는 두 개의 데이터베이스 설정이 있다고 가정합니다. 다음은 archive 데이터베이스에 객체를 저장하는 방법입니다.

user = User(username="archived_user", email="archived_user@example.com")
user.save(using="archive")

해당 데이터베이스의 alias가 self._db에 할당되어 user.save(using=self._db)를 호출하면, self._db에 저장된 데이터베이스 alias를 사용하여 해당 사용자 객체를 저장된다.

장고에서 다중데이터베이스 설정

1. 데이터베이스 설정을 추가

settings.py 파일 내에서 DATABASE설정에 새로운 데이터베이스를 추가

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / "db.sqlite3",
    },
    'archive': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / "archive_db.sqlite3",
    }
}

setting.py에 만들어놓은 라우터 파일을 연결시켜줘야한다.

DATABASE_ROUTERS = ['app이름.파일이름.ArchiveRouter']

여기서는 archive라는 이름을 가진 새로운 데이터를 생성하고, ENGINE을 SQLite로 설정했지만 엔진 부분에는 다른 데이터 베이스 백엔드 (예: PostgreSQL, MySQL)로도 설정할 수 있다.

2. 선택사항으로 데이터베이스 라우팅로직 세팅

특정앱의 모델을 archive 데이터베이스에만 저장되어야 하는 경우를 예를 들어 설명하면, 이 로직은 routers.py 라는 별도의 파일에 저장할 수 있고, 프로젝트의 구조나 개인의 선호에 따라 models.py 또는 database_routers.py 등의 다른 파일에도 저장할 수 있다.

먼저 라우팅이 무엇인가?

라우팅이란 네트워킹에서 데이터 패킷이 소스에서 목적지까지 어떻게 이동할지 결정하는 프로세스인데, 한마디로 데이터의 길찾기? 라고 보면된다.

데이터 패킷: 큰 데이터나 메세지를 여러개의 작은데이터 조각으로 나누어 보내는데 이것이 데이터 패킷이다. 출발지 목적지주소, 순서 정보등 여러정보가 포함되어있다.

소스 : 데이터 패킷의 시작지점으로 웹 서버가 브라우서의 웹페이지요청에 응답하기 위해 데이터 패킷을 보내기 시작하는 지점이 소스

목적지: 패킷의 최종 도착지점으로 브라우저의 웹페이지 요청의 데이터를 받는 목적지가 된다.

데이터 패킷이 소스에서 목적지까지 전송될 때는 다양한 네트워크 장비들(라우터, 스위치, 게이트웨이 등)을 거치게 되며, 각 장비는 패킷의 헤더 정보를 기반으로 다음 목적지로 패킷을 전송하고, 이 과정에서 라우팅 로직이 패킷을 어디로 전송할지 결정하는 역할을 한다.

웹개발의 맥락에서 라우팅을 바라보면, 라우팅은 URL을 특정 처리 로직이나 함수에 매핑하는 메커니즘을 지칭한다.

예를 들어 사용자가 웹 브라우저의 주소창에 특정 URL을 입력하면 그 URL과 연관된 함수나 뷰를 호출하는 역할을 하는것으로 보면된다.

flask를 예를 들어보면 @app.route('/')를 통해 매핑시키는 역할을 볼수있다.

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return "Home Page"

@app.route('/about')
def about():
    return "About Page"

라우팅로직은?

다중 데이터베이스를 사용할때 어떤 데이터베이스에 쿼리를 보내고 어느 데이터베이스에 데이트를 저장할 것인지 결정하는 방법이 필요한데, 이것을 위해 사용되는게 라우팅 로직이다.

라우팅 로직을 사용하면 어플레케이션의 복잡한 db구조와 요구사항 및 데이터를 여러 db에 분산 시키며 성능 향상, 데이터 분리, 백업전략 다양한 목적으로 다중 데이터 베이스 구성을 활용 할 수 있다.

장고의 데이터베이스 라우터는 여러 메서드를 통해 라우팅로직을 제공한다.


**db_for_read(model, hints): 주어진 모델에 대한 읽기 쿼리를 수행할 데이터베이스를 결정합니다.

**db_for_write(model, hints): 주어진 모델에 대한 쓰기 쿼리를 수행할 데이터베이스를 결정합니다.

**allow_relation(obj1, obj2, hints): 두 개의 객체 (또는 모델 인스턴스) 간의 관계 (예: ForeignKey)가 허용되는지를 결정합니다.

**allow_migrate(db, app_label, model_name=None, hints): 특정 데이터베이스에 마이그레이션을 적용할 수 있는지 결정합니다.

예를 하나씩 들어볼게요!

db_for_read와 db_for_write:

read 와 write같은경우는 어디더 읽고 저장할 것인지를 판단하는 과정이라고 보면된다.

class Database1Router: 

클라스

def db_for_read(self, model, **hints):
    if model._meta.app_label == 'myapp':
        return 'database1'
    return 'default'

def db_for_write(self, model, **hints):
    if model._meta.app_label == 'myapp':
        return 'database1'
    return 'default'

myapp에서 읽어온거면 database1에 보내주고, 그 myapp에서 잃어들어온걸 database1에다 써줘, 그외에는 기본 SQLite에 저장할게

라는 의미이다.

allow_relation:

두 모델 간의 관계 (예: ForeignKey 또는 ManyToManyField)가 가능한지를 결정하는 데 사용되는데, 구현되어 있지 않는다면 모든 모델 간의 관계를 설정하는 것을 허용한다. 즉, 어떤 앱의 모델이든 다른 앱의 모델을 참조하거나 연관 짓는 것이 가능하게 된다는 것이다.

예를 들어서
'app1'이라는 앱의 모델이 'db1'에,
'app2'라는 앱의 모델이 'db2'에 저장된다고 가정하면,

'app1'의 모델이 'app2'의 모델을 ForeignKey로 참조하려고 할 때, 이 참조가 실제로 가능한지를 결정하는 것이 allow_relation의 역할이다.

해주고 싶으면 TRUE 싫다면 FALSE를 해주면 된다.

True: 관계 설정 허용
False: 관계 설정 금지
None: 라우터의 결정이 없는 경우 (다음 라우터나 기본 설정 따름)

여기서 obj는 모델 인스턴스로 생각하면 된다.

def allow_relation(self, obj1, obj2, **hints):
    if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp':
        return True
    return None

위에서는 특정 앱에서만 관계를 제한해주고 싶을때, 즉 myapp이란 같은 모델에서만 가능케 꼭 명시해 주고 싶을때는 필요한 메소드이지만 굳이 안해줘도 기본적으로 모든 앱은 fk가 가능한 상태라 제한이 필요한 상황에서 써주면 될것 같다.

def allow_relation(self, obj1, obj2, **hints):
    if obj1._meta.app_label == 'app1' or obj2._meta.app_label == 'app2':
        return True
    return None

여기서는 다른 앱 app1 app2의 모델이 관련된 관계를 맺을때 허용하겠다는 메서드라고 보면 되겠다.

hints는 딕셔너리 형태의 추가적인 정보를 제공하는 역할이라고 생각하면된다. 선택적인 정보여서 필요할때만 넣으면 되고 제공되는 hints딕셔너리 키가 모든상황에서 같은게 아니라서 어떤 키가 있는지 확인하는 로직을 추가하는게 좋다.

기본적으로 model,instance라는 키가 들어가있고,
model: 현재 작업 중인 모델 클래스입니다.
instance: 현재 작업 중인 모델 인스턴스입니다. (일부 메서드에서만 사용 가능)

def allow_relation(self, obj1, obj2, **hints):
  if 'instance' in hints:
      instance = hints['instance']
      if isinstance(instance, MyModel):
          # 로직...
          pass
  return True

hints 딕셔너리 내에 'instance' 키가 있는지 확인
만약 'instance' 키가 있다면 그 값을 instance 변수에 할당하고,
해당 인스턴스가 MyModel(모델클라스이름)의 인스턴스인지 확인해주는 것.

allow_migrate:

특정 데이터베이스에 대한 마이그레이션을 허용하거나 금지하려는 경우 allow_migrate를 사용한다.

이것도 기본적으로 별도의 라우팅 로직을 구현하지 않으면 모든 앱의 모델은 DATABASES 설정의 default 데이터베이스에 마이그레이션된다.

'myapp' 앱의 모델에 대한 마이그레이션을 'database1'에서만 허용하도록 설정해주는 예의 코드를 작성해보겠다.

def allow_migrate(self, db, app_label, model_name=None, **hints):
    if app_label == 'myapp':
        return db == 'database1'
    return None

마이그래이션 연산은 테이블 생성, 삭제 및 특정모델과 직접적인 연관이 있는 것도 있지만, 데이터베이스 인덱스 추가라던지, sql 구문실행과 같이 특정 모델과 직접적인 연관이 없을 수 있기 때문에 그냥 기본 인자값으로 model_name=None처리 해주는것이다.

makemigration이나 migrate 관련 장고 명령 사용시,
장고에서는 내부적으로 이 함수를 호출하면 필요한 인자값을 전달하기 때문에,
model_name=None이어도 model_name은 None값이 아니라
해당 마이그레이션과 관련된 모델의 이름이 문자열로 전달되어
함수 내부에서 특정 모델에 대한 마이그레이션 허용 여부 결정을 해주기 위해 model_name을 활용할 수 있다.

def allow_migrate(self, db, app_label, model_name=None, **hints):
    if app_label == 'myapp' and model_name == 'mymodel':
        return db == 'database1'
    return None

3. 데이터베이스 마이그레이션

새로운 데이터베이스에 마이그레이션을 수행한다!

특정 앱의 모델 변경을 기반으로 마이그레이션 파일을 생성

python manage.py makemigrations app_name

특정 데이터베이스에 마이그레이션을 적용

python manage.py migrate --database=archive
profile
초보개발자

0개의 댓글