在Django中正确地和数据库打交道 | 青训营

90 阅读5分钟

在Django中正确地和数据库打交道

1 什么是ORM

ORM(Object Relational Mapping,对象关系映射)主要作用是在编程中把数据跟数据库中表(table)对应起来。ORM将不同类型的数据转换为数据库中的对象,同时提供与对象交互的方法。下面是关于Python的后端框架Django的ORM的介绍。

Django’s Object-Relational Model or ORM provides a great productivity shortcut: not only generating decent SQL queries for common use cases, but providing model access/update functionality that comes complete with validation and security.

The Django ORM, like any ORM, converts data from different types into objects that we can use pretty consistently across supported databases. Then it provides a set of methods for interacting with those objects.

- Two Scoops of Django

得益于ORM,我们可以在不编写复杂的SQL语句的情况下访问数据库。手写SQL代码还有可能存在一个重大安全隐患——SQL注入。为了避免上述问题,还是建议使用像Django这样的成熟的安全的框架操作数据库。

2 捕获查询异常

Django的objects.get()方法用于尝试获取一个符合条件的记录。如果数据库不含或含有多个符合条件的记录,objects.get()均会抛出异常。若不想使用objects.filter()获取QuerySet后使用objects.filter().exists()来判断查询结果,则可以借助Django定义的异常。

首先来定义一个model

# models.py
from django.db import models

class User(models.Model):
   	sex_choices = ("M", "F", "O")
	
    name = models.CharField(max_length=64)
    sex = models.CharField(max_length=1, choices=SEX_CHOICES)
    age = models.PositiveIntegerField()
    email = models.EmailField()
    friend = models.ManyToManyField('self', symmetrical=False)

2.1 数据库中没有记录时

在Django中,get() 函数用于对数据库模型进行查询,并且返回符合查询条件的单个对象(记录)。如果查询条件匹配多个对象,或者没有对象与查询条件匹配,get() 函数将引发异常。为了方便处理查询对象不存在的情况,可以用ObjectDoesNotExistDoesNotExist来抛出异常,然后进行处置。

ObjectDoesNotExist可用于任何一个model,然而DoesNotExist只能用于特定的model

# views.py
from django.core.exceptions import ObjectDoesNotExist
from .models import User

def get_user(name):
	try:
        return User.objects.get(name=name)
    except User.DoesNotExists:
        return None
        
def get_object(model, name):
    try:
        return model.objects.get(name=name)
    except ObjectDoesNotExist:
        return None

2.2 数据库中有多个记录时

Django同样提供了MultipleObjectsReturned异常来应对这种情况。

# views.py
from .models import User

def get_user(name):
	try:
        return User.objects.get(name=name)
    except User.DoesNotExists:
        return None
    # Note here
    except User.MultipleObjectsReturned:
        return User.objects.filter(name=name)

3 复杂查询

Django 支持使用 Python 常量、变量甚至其他表达式对查询表达式进行求反、加法、减法、乘法、除法、模算术和幂运算符。

Django supports negation, addition, subtraction, multiplication, division, modulo arithmetic, and the power operator on query expressions, using Python constants, variables, and even other expressions.

- Django Documentation

在Django中,django.db.models.Q 是一个用于构建复杂查询条件的对象。它可以用来创建多个条件之间的逻辑连接,如ANDOR,从而更灵活地构建数据库查询。Q 对象允许你以编程方式构建复杂的查询表达式,而不是只使用简单的过滤条件。通常,Q 对象用于在 filter()exclude() 函数中创建复杂的查询条件。

逻辑操作表示
AND&
OR|
NOT~
XOR

除了逻辑操作,Django还提供了各种用来进行比较和过滤操作的表达式。下文给出了一些常见的表达式。

比较操作表示
小于(less than)__lt
大于(greater than)__gt
小于等于(less than or equal)__lte
大于等于(greater than or equal)__gte
精确匹配(字符串)__exact
非精确匹配(字符串)__iexact

让我们举例说明。查询十八岁以上的男性的记录。

# views.py
from django.db.models import Q
from .models import User

# male age greater than 18
# query is an instance of object Q
query = Q(age__gt=18) & Q(sex="M")

Q对象作为 filter()的参数来查询,获得一个QuerySet,其是所有符合条件的对象的索引的集合。

# users is QuerySet, which contains references of returned objects
users = User.objects.filter(query)

for user in users:
    print(user.name)

4 Lazy Evaluation

Lazy evaluation(延迟计算)表现为直到需要时再计算,Django的ORM也提供了这种特性。当我们使用一长串的查询语句查询数据库中的记录时,Django直到我们真正操作查询结果之前都不会执行查询操作,我们不必在每次修改查询时都一遍又一遍地访问数据库。延迟计算可以优化性能,通过将所有运算隐式聚集成一个以避免进行多次运算。基于这样的机制,我们也可以把查询语句分成多行,从而增加代码的可读性和可维护性,而不必担心这么做影响查询性能。

让我们在 3 复杂查询 的例子的基础上写一个更复杂的例子。

# views.py, not ok
from django.db.models import Q
from .models import User

users = User.objects.filter(Q(age__gt=18) & Q(sex="M")).include(name__iexact="bob").exclude(name="Bob Dylan")

这样写代码太难维护。为了可读性可以这样写:

# views.py, ok
from django.db.models import Q
from .models import User

users = User
		.objects
		.filter(
			Q(age__gt=18) & 
			Q(sex="M"))
		.include(name__iexact="bob")
		.exclude(name="Bob Dylan")

这么写还是有点丑。又因为延迟计算的特性,我们更可以这么写:

# views.py, better
from django.db.models import Q
from .models import User

users = User.objects
users = filter(Q(age__gt=18) & Q(sex="M"))
users = include(name__iexact="bob")
users = exclude(name="Bob Dylan")

# Django will touch the database at this moment
for user in users:
    print(user.name)

在真正调用数据库内的对象(执行print()语句)时,Django才会执行查询操作。这样我们便在保证代码可维护性和可读性的同时,减少访问数据库的次数。

Reference

更多详细内容,请参考Django文档。

  1. Model Field Reference

  2. Query Expressions