MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python Django中的ORM系统详解

2024-01-015.8k 阅读

什么是ORM

在Python Django的生态体系中,ORM(Object - Relational Mapping,对象关系映射)是一项核心技术。它允许开发者使用Python代码来与数据库进行交互,而无需编写原始的SQL语句。简单来说,ORM就是在面向对象编程的世界和关系型数据库的世界之间搭建了一座桥梁。

在传统的数据库交互中,开发者需要针对不同的数据库(如MySQL、PostgreSQL等)编写特定的SQL语句来执行诸如创建表、插入数据、查询数据、更新数据和删除数据等操作。而使用ORM,开发者可以基于Python的类和对象来完成这些操作,Django的ORM会自动将这些Python代码转换为对应的SQL语句,并适配不同的数据库后端。

例如,假设我们有一个简单的博客应用,其中有一个Post模型代表博客文章。使用ORM,我们可以这样定义这个模型:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

在上述代码中,我们定义了一个Post类,它继承自models.Model。这个类的属性titlecontentcreated_at分别对应数据库表中的列。通过这样的Python代码定义,Django的ORM就能创建出对应的数据库表结构。

ORM的优势

  1. 提高开发效率:开发者无需花费大量时间编写和调试SQL语句,尤其是复杂的查询语句。例如,在一个多表关联查询场景下,使用SQL编写查询语句可能会非常冗长且容易出错。而使用ORM,通过简单的Python代码就可以完成同样的操作。假设我们有AuthorBookPublisher三个模型,并且Book模型与AuthorPublisher存在关联关系。如果要查询某个出版社出版的所有书籍及其作者信息,使用ORM可能只需几行代码:
books = Book.objects.filter(publisher__name='Some Publisher').select_related('author')
for book in books:
    print(book.title, book.author.name)
  1. 数据库独立性:Django的ORM支持多种数据库后端,如MySQL、PostgreSQL、SQLite等。通过简单地修改配置文件,就可以切换数据库,而无需大量修改代码。例如,在开发阶段,我们可能使用SQLite进行快速开发和测试,而在生产环境中切换到性能更好的PostgreSQL。只需要修改settings.py文件中的数据库配置部分:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
# 切换到PostgreSQL
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'your_database_name',
        'USER': 'your_username',
        'PASSWORD': 'your_password',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}
  1. 代码可读性和可维护性:使用ORM,数据库操作代码与Python代码紧密结合,符合面向对象编程的习惯。代码结构更加清晰,易于理解和维护。例如,在传统的SQL方式中,数据库操作语句通常是字符串形式,在代码中分散存在,难以追踪和修改。而ORM将数据库操作封装在模型类和管理器方法中,使得代码结构更加紧凑和易于管理。

Django ORM的核心组件

  1. 模型(Model):模型是Django ORM的基础,它是一个Python类,继承自models.Model。模型类的每个属性代表数据库表中的一列,并且定义了该列的数据类型、约束等信息。例如,我们之前定义的Post模型:
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

这里的title属性使用models.CharField定义,表明这是一个字符串类型的列,并且最大长度为200。content使用models.TextField定义,适用于存储较长的文本内容。created_at使用models.DateTimeField并设置auto_now_add=True,表示在创建Post对象时,会自动记录当前的日期和时间。

  1. 字段类型(Field Types):Django提供了丰富的字段类型,以满足不同的数据存储需求。
    • 字符型字段CharField用于存储较短的字符串,必须指定max_length参数。例如,用于存储用户名、标题等。
    • 文本型字段TextField用于存储较长的文本,如文章内容、评论等。
    • 数值型字段IntegerField用于存储整数,FloatField用于存储浮点数,DecimalField用于存储高精度的十进制数,常用于金融相关的数值存储,需要指定max_digits(总位数)和decimal_places(小数位数)。例如:
price = models.DecimalField(max_digits=10, decimal_places=2)
- **日期和时间型字段**:`DateField`用于存储日期,`TimeField`用于存储时间,`DateTimeField`用于存储日期和时间。`auto_now`参数设置为`True`时,每次对象保存时会自动更新为当前时间;`auto_now_add`设置为`True`时,在对象首次创建时会自动设置为当前时间。
- **布尔型字段**:`BooleanField`用于存储布尔值(`True`或`False`),例如用于表示是否激活、是否已读等状态。
- **关系型字段**:这是非常重要的一类字段,用于建立模型之间的关联关系,包括`ForeignKey`(外键,用于一对多关系)、`ManyToManyField`(多对多关系)和`OneToOneField`(一对一关系)。

3. 管理器(Manager):每个模型类都有一个默认的管理器,名为objects。管理器提供了一系列方法来执行数据库查询操作,如all()用于获取所有对象,filter()用于过滤对象,get()用于获取单个对象等。例如:

# 获取所有的Post对象
all_posts = Post.objects.all()
# 过滤出标题包含特定字符串的Post对象
filtered_posts = Post.objects.filter(title__contains='Django')
# 获取特定主键的Post对象
single_post = Post.objects.get(pk=1)

开发者也可以自定义管理器,通过继承models.Manager类并添加自定义的方法来实现。例如,假设我们希望在Post模型中添加一个方法来获取最近发布的文章,可以这样定义自定义管理器:

from django.db import models

class PostManager(models.Manager):
    def get_recent_posts(self):
        return self.order_by('-created_at')[:5]

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    objects = PostManager()

然后可以使用自定义管理器方法:

recent_posts = Post.objects.get_recent_posts()
  1. 查询集(QuerySet):查询集是Django ORM中一个非常强大的概念,它代表从数据库中获取的对象集合。查询集是惰性的,即只有在实际需要数据时(如迭代、切片、获取单个对象等操作)才会真正执行数据库查询。查询集支持链式调用,这使得复杂的查询可以通过简洁的代码实现。例如:
posts = Post.objects.filter(title__contains='Python').exclude(content__contains='deprecated').order_by('-created_at')[:10]

在上述代码中,首先通过filter方法筛选出标题包含Python的文章,然后使用exclude方法排除内容中包含deprecated的文章,接着按照created_at字段降序排序,最后只获取前10条记录。

模型定义与数据库表创建

  1. 定义模型类:在Django应用的models.py文件中定义模型类。除了基本的字段定义,还可以定义模型的元数据(Meta类),用于设置表名、排序方式、权限等信息。例如:
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    publication_date = models.DateField()

    class Meta:
        db_table = 'books'
        ordering = ['-publication_date']

在上述代码中,通过Meta类的db_table属性指定了数据库表名为booksordering属性指定了默认按照publication_date字段降序排序。

  1. 数据库迁移(Migrations):在定义好模型类后,需要通过数据库迁移来创建实际的数据库表。Django提供了一套强大的迁移系统,它可以跟踪模型的变化并生成相应的SQL语句来更新数据库结构。
    • 生成迁移文件:在项目根目录下执行命令python manage.py makemigrations,这个命令会检测models.py文件中的变化,并生成迁移文件。例如,当我们创建了上述Book模型后,执行该命令会在应用的migrations目录下生成一个新的迁移文件,记录了创建Book模型表的操作。
    • 应用迁移:执行命令python manage.py migrate,该命令会将生成的迁移文件应用到数据库中,实际创建表结构。如果模型发生了变化,如添加新字段、修改字段类型等,再次执行makemigrationsmigrations命令就可以更新数据库结构。

数据库操作 - 创建对象

  1. 使用save()方法:创建模型对象最常见的方式是先实例化模型类,然后调用save()方法将对象保存到数据库中。例如:
from myapp.models import Post

new_post = Post(title='My First Post', content='This is the content of my first post.')
new_post.save()

在上述代码中,首先创建了一个Post对象,设置了titlecontent属性,然后调用save()方法将该对象保存到数据库中。此时,数据库中会插入一条新记录,并且对象会被赋予一个自动生成的主键值。

  1. 使用create()方法:管理器提供了create()方法,它可以在创建对象的同时将其保存到数据库中,一步完成对象的创建和保存操作。例如:
from myapp.models import Post

new_post = Post.objects.create(title='Another Post', content='This is another post.')

这种方式更加简洁,适用于简单的对象创建场景。

数据库操作 - 查询对象

  1. 基本查询
    • 获取所有对象:使用all()方法获取模型的所有对象。例如:
from myapp.models import Post

all_posts = Post.objects.all()
for post in all_posts:
    print(post.title)
- **获取单个对象**:使用`get()`方法根据特定条件获取单个对象。例如:
from myapp.models import Post

try:
    post = Post.objects.get(pk=1)
    print(post.content)
except Post.DoesNotExist:
    print('Post not found')

需要注意的是,get()方法如果找不到符合条件的对象会抛出DoesNotExist异常,所以通常需要使用try - except块来处理这种情况。

  1. 过滤查询:使用filter()方法根据特定条件过滤对象。过滤条件使用字段名和比较运算符组成,例如:
from myapp.models import Post

filtered_posts = Post.objects.filter(title__contains='Django')
for post in filtered_posts:
    print(post.title)

在上述代码中,title__contains='Django'表示筛选出标题中包含DjangoPost对象。常用的比较运算符有__exact(精确匹配)、__contains(包含)、__startswith(以...开头)、__endswith(以...结尾)、__gt(大于)、__lt(小于)等。

  1. 复杂查询:可以通过链式调用多个filter()方法或者结合Q对象来构建复杂的查询条件。Q对象允许使用逻辑运算符(如&表示AND|表示OR)组合条件。例如:
from django.db.models import Q
from myapp.models import Post

complex_query = Post.objects.filter(Q(title__contains='Python') | Q(content__contains='Django')).exclude(pk=1)
for post in complex_query:
    print(post.title)

在上述代码中,首先使用Q对象组合了两个条件,筛选出标题包含Python或者内容包含DjangoPost对象,然后使用exclude()方法排除主键为1的对象。

数据库操作 - 更新对象

  1. 获取对象并修改属性:先通过查询获取对象,然后修改其属性,最后调用save()方法保存修改。例如:
from myapp.models import Post

post = Post.objects.get(pk=1)
post.title = 'Updated Title'
post.save()

在上述代码中,获取主键为1的Post对象,修改其title属性,然后调用save()方法将修改保存到数据库中。

  1. 使用update()方法:对于多个对象的批量更新,可以使用update()方法。例如,将所有标题包含DjangoPost对象的content字段添加一段文本:
from myapp.models import Post

Post.objects.filter(title__contains='Django').update(content= models.F('content') + ' This is an updated note.')

这里使用了models.F对象,它允许在不实际获取对象的情况下,对数据库中的字段进行操作。在上述例子中,将原有的content字段值加上一段新的文本。

数据库操作 - 删除对象

  1. 删除单个对象:获取对象后调用delete()方法删除对象。例如:
from myapp.models import Post

post = Post.objects.get(pk=1)
post.delete()

在上述代码中,获取主键为1的Post对象,然后调用delete()方法将其从数据库中删除。

  1. 删除多个对象:通过过滤查询获取多个对象,然后调用delete()方法进行批量删除。例如:
from myapp.models import Post

Post.objects.filter(title__contains='Old Post').delete()

在上述代码中,筛选出标题包含Old Post的所有Post对象,并将它们从数据库中删除。

关联关系处理

  1. 一对多关系(ForeignKey):在Django中,使用ForeignKey字段来建立一对多关系。例如,一个Author可以有多个Book。定义如下:
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

在上述代码中,Book模型的author字段是一个ForeignKey,关联到Author模型。on_delete=models.CASCADE表示当关联的Author对象被删除时,与之关联的所有Book对象也会被删除。

添加关联关系:

author = Author.objects.create(name='John Doe')
book = Book.objects.create(title='My Book', author=author)

查询关联对象:

author = Author.objects.get(name='John Doe')
books = author.book_set.all()
for book in books:
    print(book.title)

反向查询时,Django会自动为Author模型添加一个名为book_set的管理器(默认情况下,名称为关联模型名小写加上_set),用于获取与之关联的Book对象。

  1. 多对多关系(ManyToManyField):使用ManyToManyField来建立多对多关系。例如,一个Book可以有多个Genre(类别),一个Genre也可以对应多个Book。定义如下:
from django.db import models

class Genre(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=200)
    genres = models.ManyToManyField(Genre)

添加关联关系:

genre1 = Genre.objects.create(name='Fiction')
genre2 = Genre.objects.create(name='Mystery')
book = Book.objects.create(title='A Great Book')
book.genres.add(genre1, genre2)

查询关联对象:

book = Book.objects.get(title='A Great Book')
genres = book.genres.all()
for genre in genres:
    print(genre.name)
  1. 一对一关系(OneToOneField):使用OneToOneField来建立一对一关系。例如,一个Person可以有一个唯一的Passport。定义如下:
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=100)

class Passport(models.Model):
    number = models.CharField(max_length=20)
    person = models.OneToOneField(Person, on_delete=models.CASCADE)

添加关联关系:

person = Person.objects.create(name='Alice')
passport = Passport.objects.create(number='123456', person=person)

查询关联对象:

person = Person.objects.get(name='Alice')
passport = person.passport
print(passport.number)

反向查询时,Django会自动为Person模型添加一个与Passport模型同名的属性,用于获取与之关联的Passport对象。

性能优化

  1. 减少查询次数:使用select_related()prefetch_related()方法来减少数据库查询次数。select_related()用于处理一对多和一对一关系,通过SQL的JOIN操作在一次查询中获取关联对象。例如:
from myapp.models import Book, Author

books = Book.objects.select_related('author').all()
for book in books:
    print(book.title, book.author.name)

prefetch_related()用于处理多对多关系和反向的一对多关系,它通过分别执行查询,然后在Python层面进行合并。例如:

from myapp.models import Book, Genre

books = Book.objects.prefetch_related('genres').all()
for book in books:
    for genre in book.genres.all():
        print(book.title, genre.name)
  1. 使用索引:对于经常用于查询条件的字段,可以在模型中通过db_index=True来创建索引。例如:
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200, db_index=True)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

这样在对title字段进行查询时,数据库可以利用索引提高查询效率。

  1. 分页查询:当数据量较大时,使用分页可以减少一次性加载的数据量,提高性能和用户体验。Django提供了Paginator类来实现分页功能。例如:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from myapp.models import Post

def post_list(request):
    post_list = Post.objects.all()
    paginator = Paginator(post_list, 10)  # 每页显示10条记录
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'post_list.html', {'posts': posts})

在上述代码中,通过Paginator类对Post对象列表进行分页,每页显示10条记录,并根据用户请求的页码返回相应的页面数据。

通过以上对Django ORM系统的详细解析,开发者可以全面掌握如何利用Django ORM高效地进行数据库操作,构建功能强大且性能优良的Web应用。在实际开发中,根据具体的业务需求和数据特点,合理运用ORM的各种特性,可以大大提高开发效率和代码质量。