Django : 정참조/역참조, select_related, prefetch_related

Joey Lee·2020년 5월 29일
7

Django

목록 보기
12/23

1. 정참조와 역참조의 개념

1) 정참조

사용하려면 해당 객체가 다른 객체의 ForeignKey를 가지고 있거나 1대1 관계로 있는 상황에서 참조를 하는 경우

[Product -> sub_category 참조시]

a = Product.objects.get(id=1)
a.sub_category.name

2) 역참조

사용하려면 해당 객체를 참조하고 있는 다른 객체를 참조하려고 하는 경우 (다른 객체가 ForeignKey를 가지고 있거나 다대다 관계인 경우)

[main_category -> sub_category 참조시]

a = MainCategory.objects.get(id=1)
a.subcategory_set.all()[0].name

2. 참조 시 자주하는 오류

a = Product.objects.get(id=1)

a.main_category.name : O
MainCategory.objects.get(id=1).name : X

사용하려는 객체가 정참조(다른 객체의 ForeignKey를 가지고 있음) 혹은 ForeignKey가 없더라도 1:1 관계에 있는 객체의 데이터를 캐싱해 오는 메소드이다.

select_related를 쓰면 해당 객체 외 해당객체가 참조하는 데이터들이 한 꺼번에 불러와줘 매번 DB에 접속해서 해당 데이터를 불러오지 않아도 된다. Debugger도구로 확인해 보면 다른 객체에 연결되어 있는 ForeignKey(id값)으로 조인으로 연결되어 있음을 할 수 있다.

즉, select_related로 3개의 객체를 추가로 불러오면 해당 객체의 1개 row 외에 연결된 3개 row의 데이터가 더 불러져 온다고 보면 된다.

> a = Product.objects.select_related('main_category', 'sub_category', 'nutrient').get(id=1)

> a.main_category.name
DB query 날리지 않고 값을 리턴함.

> sub_category__main_category
1차 연결 뿐만 아니라 2차 연결도 가능함. 정참조에서만 가능

Select_related와는 반대로 해당 객체가 역참조될 경우에 해당 객체를 참조하고 있는 객체들의 데이터를 캐싱해 오는 메소드이다.

> a = import inspect
> for i in inspect.getmembers(b):
	print(i)

...
('main_category', <mainCategory..>
('main_category_id', 1)
...

스타벅스 사례를 통해 예시를 들어보자. Product 객체를 기준으로 ManyToMany 관계인 Allergen 객체를 가져오려면 크게 2가지 방법이 있다.

1) ManyToMany 정참조

Product는 Allergen과 연결된 ManyToMany 관리매니저를 정참조하고 있고, 이 관리매니저를 통해 다대다 관계인 Allergen에 접근 가능하다.

> b.allergen # 관리매니저 호출 (바로 객체가 나오지 않음)
> b.allergen.all() # 쿼리셋 리턴

2) 역참조 : allergen_set 활용

Product는 Allergen 모델에 역참조되고 있으므로 역참조 방법인 _set으로 접근할 수도 있다.

> b.productallergen_set.all()  # 정참조 했을 때와 결과값 차이 없음

> c = Product.objects.prefetch_related('allergen').get(id=1) 
# Product와 역참조 관계인 Allergen 객체를 함께 캐싱해서 가져옴

> c.allergen.all() 
# DB에 쿼리 날리지 않음. prefetch를 했기 때문임
> d = [{'id': i.id, 'name' : i.name} for in c.allergen.all()]
# 이 코드 역시 실행시 쿼리 날리지 않음 
  • prefetch 선언시, related_name를 명시하면 역참조 시 "_set" 대신에 해당값으로 변경해서 사용가능함. 단, 필수입력 값은 아님.

prefetch해서 캐싱 먼저하고, 그 다음 filter 걸면 됨

profile
안녕하세요!

0개의 댓글