DoesNotExist vs MultipleObjectsReturned - ORM 예외처리
ORM을 사용해서 개발을 하다 보면 아래 오류가 종종 발생하고는 합니다.
User.DoesNotExist: User matching query does not exist.
처음에는 ORM이 잘 작동했지만 서비스에서 트랙픽이 증가하고, 데이터가 누적되기 시작하면서
예외가 빈번하게 발생했습니다.
먼저 발생원인에 대해 확인하면
get( ) 메서드의 핵심 특징을 알아야합니다.
get( )은 단 하나의 레코드만 존재할 때만 성공합니다.
그 외엔 무조건 예외를 발생시킵니다.
예외 발생 | 조건 | 원인 |
DoesNotExist | 해당 조건에 맞는 결과가 0개 | 삭제되었거나 조건이 잘못됨 |
MultipleObjectsReturned | 결과가 2개 이상일 때 | 중복 데이터 또는 unique 제약 누락 |
예외를 처리하지 않으면 서버에서 500에러가 발생해 사용자 입장에서는 서버 문제처럼 보입니다.
get( )은 취약한 API 엔드포인드를 만들 위험이 있습니다.
데이터 구조 자체의 결함(중복, 제약조건 누락)도 드러나지 않고 서비스만 불안정해줄 수 있습니다.
해결방법
1. 예외를 명시적으로 처리 (try - except)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return JsonResponse({'error': '해당 이메일의 사용자가 없습니다.'}, status=404)
except User.MultipleObjectsReturned:
return JsonResponse({'error': '중복된 이메일이 존재합니다.'}, status=400)
API에서는 무조건 404 or 400으로 분리해서 명확한 응답을 줘야 합니다.
2. filter().first()로 우회 (예외 없이 안전하게)
user = User.objects.filter(email=email).first()
if not user:
return JsonResponse({'error': '사용자를 찾을 수 없습니다.'}, status=404)
결과가 여러 개여도 첫 번째 값만 반환해서 예외가 발생하지 않습니다.
중복 방지 설계가 되어 있어야 사용할 수 있습니다.
3. filter().count()로 명확하게 개수 검사
qs = User.objects.filter(email=email)
count = qs.count()
if count == 1:
user = qs.first()
elif count == 0:
return JsonResponse({'error': '사용자를 찾을 수 없습니다.'}, status=404)
else:
return JsonResponse({'error': '중복된 사용자가 존재합니다.'}, status=400)
느리지만 가장 정확한 개수 기반으로 분기가 가능합니다.
4. 애초에 모델에 제약조건 걸기 (unique=True)
class User(models.Model):
email = models.EmailField(unique=True) # 중복 방지
가장 확실한 방법입니다.
DB 차원에서 중복 자체를 원천 차단한다.
나중에 get()도 안전하게 사용 가능합니다.
상황 | 방식 |
get()로 단일 결과를 기대 | 반드시 try-except로 감싸기 |
중복 발생 가능성 있음 | filter().first()로 우회 |
정확한 결과 필요 | filter().count() 후 분기 |
구조적 중복 방지 | unique 제약조건 설정 |
정리
Django ORM의 get()은 매우 편리하지만,
예외가 발생할 가능성이 매우 높은 메서드입니다.
실무에서는 반드시 DoesNotExist, MultipleObjectsReturned를 고려하고
대응 전략을 사전에 구조적으로 설계해야 합니다.
참고 자료 (Reference)
- Django 공식 문서 – QuerySet API
- Stack Overflow – get() 예외 처리 관련 Q&A
- DRF 공식 문서: https://www.django-rest-framework.org/