如何编写DRF序列化器生成指定输出并规避N+1查询问题
实现方案
首先先修正现有模型定义的两处语法/逻辑问题:一是CharField未指定必填的max_length参数,二是ForeignKey的related_name和字段同名会导致反向关联冲突,修正后的可正常运行的模型如下:
from django.db import models class User(models.Model): name = models.CharField(max_length=100) class Task(models.Model): task_name = models.CharField(max_length=100) class UserTask(models.Model): user = models.ForeignKey(User, related_name='user_tasks', on_delete=models.CASCADE) task = models.ForeignKey(Task, related_name='user_tasks', on_delete=models.CASCADE)
规避N+1查询的实现方式
select_related仅适用于正向多对一、一对一关联的连表查询,当前场景是User通过中间表UserTask关联Task的多对多层级关系,需要用prefetch_related配合Prefetch对象做预加载,配合select_related拉取中间表关联的Task数据,全程只会产生2次数据库查询,完全没有额外的单条记录查询:
- 第一次查询全量User记录
- 第二次查询这些User关联的所有UserTask记录,同时通过JOIN把关联的Task数据一并取出
视图层使用的查询集写法如下:
from django.db.models import Prefetch from .models import User, UserTask # 预加载逻辑直接写在查询集里 user_queryset = User.objects.prefetch_related( Prefetch( lookup='user_tasks', queryset=UserTask.objects.select_related('task') ) ).all()
DRF序列化器编写
分层定义序列化器,自定义字段匹配要求的输出结构,取值时直接使用预加载好的关联数据,不会触发额外数据库查询:
from rest_framework import serializers from .models import User, Task class TaskSerializer(serializers.ModelSerializer): class Meta: model = Task fields = ['id', 'task_name'] class UserSerializer(serializers.ModelSerializer): # 匹配输出要求的Task字段名 Task = serializers.SerializerMethodField() class Meta: model = User fields = ['id', 'name', 'Task'] def get_Task(self, obj): # 直接遍历预加载的中间表记录取关联task,无额外查询 task_list = [user_task.task for user_task in obj.user_tasks.all()] return TaskSerializer(task_list, many=True).data
外层结构包装
如果需要最外层包裹"User"键匹配要求的输出格式,在视图返回时直接包装序列化结果即可:
from rest_framework.views import APIView from rest_framework.response import Response class UserTaskListAPI(APIView): def get(self, request): serializer = UserSerializer(user_queryset, many=True) return Response({ "User": serializer.data })
内容的提问来源于stack exchange,提问作者user9914165
相关产品推荐
相关产品推荐

