본문 바로가기

개발

[ORM] ORM 정리

반응형

ORM?

  • Object Relational Mapping
  • 객체와 관계형 DB를 자동으로 매핑해 주는것.
  • SQL 쿼리문 없어 DB의 데이터를 다룰 수 있게 해줌.
      SLECT * FROM User u WHERE u.first_name='kim'
      User.objects.filter(first_name='kim')

특징

Cahcing(캐싱)

  • 한번 가져온 데이터는 변화가 없다면 계속 사용
         users = User.objects.filter(first_name='kim')
      first_user = users[0].full_name # 데이터 가져옴
      user_list = list(users) # 데이터 가져옴2
      # 총 2번 가져옴
         users = User.objects.filter(first_name='kim')
      user_list = list(users) # 데이터 가져옴
      first_user = user_list[0].full_name # 캐싱된 데이터 이용
      # 총 1번 가져옴
  • 위 예제처럼 캐싱을 활용하면 효율적인 코드 작성이 가능

Lazy-loading(지연로딩)

  • ORM을 사용할 때 데이터가 바로 로딩되지 않음.
         users = User.objects.filter(first_name='kim') # 이때는 데이터를 가져오지 않음
      for user in users:
          print(user.full_name) # 이때 데이터를 가져옴
  • 실제로 데이터가 필요한 시점에 데이터를 가져옴.
  • Django ORM은 기본적으로 Lazy-loading으로 동작.
  • 코드의 순서에 따라 비효율적일 수도 있음. (캐싱 참고)

Eager-loading(즉시로딩)

  • Lazy-loading에 반대되는 개념
  • ORM을 실행한 즉시 데이터를 가져옴
  • Lazy-loading의 대표적인 문제인 N+1 Problem을 해결하기 위해 사용

N+1 Problem

  • 외래키로 연결된 데이터를 가져오는 과정에서 생기는 문제

         # User와 UserProfile은 1:1 관계
      class User(AbstractBaseUser):
          # Data fields
          username = models.CharField(max_length=200, null=False, blank=True, unique=True)
          first_name = models.CharField(max_length=200, null=False, blank=True)
          last_name = models.CharField(max_length=200, null=False, blank=True)
    
      class UserProfile(models.Model):
          user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_profile')
          phone_number = models.CharField(max_length=50, default='')
          zip_code = models.CharField(max_length=20, default='')
          home_address = models.CharField(max_length=200,default='')
         kims_users = User.objects.filter(first_name='kim')
    
      for user in kims_users: # kim_user를 for문에 사용하기 위해 데이터 가져옴
          user_address = user.user_profile.home_address # user와 연결된 userprofile 데이터를 가져옴
          print(f'user address :: {user_address}')
      # 총 1 + N(kims_users 개수)

해결책

selecte_related

   # 첫 줄에서 User, UserProfile까지 join하여 모든 데이터를 가져옴.
kims_users = User.objects.select_related('user_profile').filter(first_name='kim')

for user in kims_users:
    user_address = user.user_profile.home_address
    print(f'user address :: {user_address}')
  • select_related를 사용하면 모든 데이터를 한번에 가져옴
    -> 데이터를 한번만 가져오는 효과
  • 1:1 관계, 정참조 관계에만 사용 가능

prefetch_related

   # 첫 줄에서 User, UserProfile까지 모든 데이터를 가져온 뒤, 장고에서 내부적으로 데이터를 합침.
kims_users = User.objects.prefetch_related('user_profile').filter(first_name='kim')

for user in kims_users:
    user_address = user.user_profile.home_address
    print(f'user address :: {user_address}')
  • prefetch_related역시 모든 데이터를 한번에 가져옴
  • select_related와 달리 join으로 데이터를 합치는게 아닌, 두 테이블의 데이터를 따로 가져와서 Django가 합침
  • select_related보다 추가적인 쿼리 발생
    -> select_related를 사용하지 못하는 상황에서 사용

장점

  1. 생산성 증가
  2. 가독성 증가
  3. 유지보수 용이

단점

  1. ORM을 따로 배워야함
  2. 복잡한 참조일경우 SQL문이 더 좋을수도 있음
  3. 정확한 이해없이 사용하면 성능저하등의 문제 발생
반응형