Nova Kwok's Awesome Blog

Django 和 Laravel 的一些使用上的异同对比——数据模型和 ORM

在之前的文章《Django 和 Laravel 的一些使用上的异同对比——路由篇》提到了路由和视图(控制器)之间的一些对比,不过 Django 和 Laravel 的区别不仅仅在于这两个方面,数据模型和 ORM 也是,在一个 MVC 的框架中,我们多半需要使用数据库来存储我们的数据(无论是文章,还是评论,或者用户~~,当然你要存图片的话,或许也行~~),我们一般很少涉及到一些裸 SQL 的编写,而是使用到 ORM,同样,我们也很少需要手动创建一个数据库,而是使用模型(Models)来迁移(Migrate)。

数据模型(Models)

数据模型的概念让我们对于数据的存放和操作有了一层封装,对于简单的操作来说,有了 ORM 之后会非常的方便(但是数据库原理这门课程依然要好好学)。

Laravel

如果要创建一个 Model,会使用到指令:php artisan make:model User -m,这个指令会创建一个位于app/User.php 的文件,同时创建对应的数据库模型迁移文件(migration),在 Laravel 中,数据模型的定义大致类似如下:

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

一般来说,这些文件被统一地存放在 database/migrations/ 中(如果需要手动创建的话,创建指令为:php artisan make:migration users),一个表就会有一个对应的文件(被称为 migrations),文件名会按照创建对应文件的时间为前缀命名,比如上面 Laravel 自带的 User 表的 migration 文件的文件名为:2014_10_12_000000_create_users_table.php

要把这个所有已经定义好的模型「迁移」(migrate)到数据库中,使用指令:

php artisan migrate

如果迁移坏了,需要从 0 开始(删除之前的表并重新创建结构)的话:

php artisan migrate:fresh

Laravel 中对于数据库结构的改变是体现在 migrations 中的文件,每修改一次都会创建一个新的 migration 文件,文件名一般为 20xx_mm_dd_hhmmss_alter_xx_table.php,指令是大致类似: php artisan make:migration add_votes_to_users_table --table=users

Django

在 Django 中,数据模型的定义大致如下:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

对应的文件会默认在每个 app 下的 models.py 中存放,如果要迁移的话,需要先生成「迁移文件」,通过指令:python manage.py makemigrations <app名字>,然后通过 python manage.py migrate 来迁移。

Django 中对于数据库结构的变化并不会直接让使用者创建新的 migrations,如果需要修改数据库的话,直接修改 models.py 就可以了,在 makemigrations 之后 Django 会自动生成额外的 migration 文件。

ORM

所谓 ORM,即 Object Relational Mapping,翻译过来就是对象关系映射,Wiki 上的描述如下:

Object-relational mapping (ORM, O/RM, and O/R mapping tool) in computer science is a programming technique for converting data between incompatible type systems using object-oriented programming languages. This creates, in effect, a “virtual object database” that can be used from within the programming language. There are both free and commercial packages available that perform object-relational mapping, although some programmers opt to construct their own ORM tools.

以一个不严谨但是简单以及实用的角度上来说,就是将一个个的数据库的操作变成一个个类的函数调用,还是不理解?假设我们需要查询 id(假设是主键)为 2 的文章,我们在 SQL 中可能会有如下几个指令:

SELECT * FROM articles WHERE id = 2;
SELECT * FROM articles WHERE type = "gallery";
SELECT * FROM articles WHERE type = "post" ORDER BY created_at DESC;

但是如果在 ORM 中话,可能是如下写法。

Laravel

查询指令

假设在 Laravel 中:

article = Article::find(2);
gallery_list = Article::where(type,"gallery")->get();
post_list = Article::where(type,"gallery")->desc()->get();

要用到这个需要引入我们的类,一般来说在自己的 Controller 顶部这样写:

use App\Article;

就可以了。

多对多关系查询

假设在某个系统中有这么个需求,一个学生(user)可以属于多个班级,一个班级(team)又包含了许多学生,这样学生和班级之间就构成了一个多对多的关系,通过中间表 team_user ,在 Laravel 中我们应该如何做呢?

首先在 User 模型(app/User.php)中定义好对于 Team 的连接:

public function teams()
{
    return $this->belongsToMany('App\Team','team_user','user_id','team_id');
}

然后在控制器中就可以通过一个 User 来找出 TA 属于哪一个 Team 了:

$user_object = User::find($user_id);
$user_teams = $user_object->teams()->get();

很方便是不是?

要把一个 User 和一个 Team 关联起来呢?

$team_object = Team::find($team_id); # 找到 Team
$user_object = User::find($user_id); # 找到 User
$user_object->teams()->attach($team_object); # 关联~

如果希望反向使用,如下:

$team_object->users()->attach($user_id);

的话,需要在 Team 模型(app/Team.php)中申明对于 User 的连接:

public function users()
{
	return $this->belongsToMany('App\User','team_user','team_id','user_id');
}

Django

查询指令

如果在 Django 中需要完成这些操作的话该如何操作呢?

article = Article.objects.get(pk=2)
gallery_list = Article.objects.filter(type == "gallery")
post_list = Article.objects.filter(type == "post")->order_by('-created_at')

同样,需要引入模型的文件:

from .models import Article

多对多关系查询

Django 中的多对多关系需要在数据库设计的时候加入一些额外的操作,官方的示例如下(一个 Article 可以被发布到多个 Publications 中,一个 Publication 中可以包含多个 Articles):

from django.db import models

class Publication(models.Model):
    title = models.CharField(max_length=30)

    class Meta:
        ordering = ('title',)

    def __str__(self):
        return self.title

class Article(models.Model):
    headline = models.CharField(max_length=100)
    publications = models.ManyToManyField(Publication)

    class Meta:
        ordering = ('headline',)

    def __str__(self):
        return self.headline

这样要给一个 Publication 加入一些 Articles 可以如下操作:

p1 = Publication(title='The Python Journal')
p1.save() # 先创建一个 Publication
a1 = Article(headline='Django lets you build Web apps easily') # 创建文章
a1.publications.add(p1) # 关联~

如果需要反向关联的话,需要在 Models 中定义,不过似乎在 Django 中没法双向关联,毕竟是由数据库模型保证的。

小结

本文列举了少量在 Django/Laravel 开发中常用的数据模型和 ORM 的一些操作的对比,可以看出 Laravel 和 Django 在这个方面是走了两个完全不同的风格,尤其是在数据定义的部分,个人感觉 Laravel 倾向于让程序解决数据的操作问题,而 Django 更加倾向于让数据库承担一部分操作。

不过 Laravel 在设计数据迁移的时候似乎有一个比较诟病的地方,这里引用知乎网友的评论:

检出代码想看看这个版本的代码的数据库结构,需要创建一个数据库,然后运行迁移创建表,最后去数据库才能查看当前的数据库结构… 想直接从代码里面看当前的数据库结构?对不起,只有每次的数据库迁移脚本,没有最新版的数据库结构,你自己从头到尾滤迁移脚本去吧…

请问laravel优雅在何处? - GameXG的回答 - 知乎https://www.zhihu.com/question/30279133/answer/95285320

这个的确是,Django 做到了看一遍 models.py 就可以知道当前数据库的结构是如何的(除非你没跑 migrate),而 Laravel 如果做了一些表的修改,就会生成一系列的 alter_table 文件,比如一个比较有名的基于 Laravel 的监控系统 Cachet 的 migrations 便是如此,列出文件供大家欣赏:

➜  migrations git:(2.4) tree
.
├── 2015_01_05_201324_CreateComponentGroupsTable.php
├── 2015_01_05_201444_CreateComponentsTable.php
├── 2015_01_05_202446_CreateIncidentTemplatesTable.php
├── 2015_01_05_202609_CreateIncidentsTable.php
├── 2015_01_05_202730_CreateMetricPointsTable.php
├── 2015_01_05_202826_CreateMetricsTable.php
├── 2015_01_05_203014_CreateSettingsTable.php
├── 2015_01_05_203235_CreateSubscribersTable.php
├── 2015_01_05_203341_CreateUsersTable.php
├── 2015_01_09_083419_AlterTableUsersAdd2FA.php
├── 2015_01_16_083825_CreateTagsTable.php
├── 2015_01_16_084030_CreateComponentTagTable.php
├── 2015_02_28_214642_UpdateIncidentsAddScheduledAt.php
├── 2015_05_19_214534_AlterTableComponentGroupsAddOrder.php
├── 2015_05_20_073041_AlterTableIncidentsAddVisibileColumn.php
├── 2015_05_24_210939_create_jobs_table.php
├── 2015_05_24_210948_create_failed_jobs_table.php
├── 2015_06_10_122216_AlterTableComponentsDropUserIdColumn.php
├── 2015_06_10_122229_AlterTableIncidentsDropUserIdColumn.php
├── 2015_08_02_120436_AlterTableSubscribersRemoveDeletedAt.php
├── 2015_08_13_214123_AlterTableMetricsAddDecimalPlacesColumn.php
├── 2015_10_31_211944_CreateInvitesTable.php
├── 2015_11_03_211049_AlterTableComponentsAddEnabledColumn.php
├── 2015_12_26_162258_AlterTableMetricsAddDefaultViewColumn.php
├── 2016_01_09_141852_CreateSubscriptionsTable.php
├── 2016_01_29_154937_AlterTableComponentGroupsAddCollapsedColumn.php
├── 2016_02_18_085210_AlterTableMetricPointsChangeValueColumn.php
├── 2016_03_01_174858_AlterTableMetricPointsAddCounterColumn.php
├── 2016_03_08_125729_CreateIncidentUpdatesTable.php
├── 2016_03_10_144613_AlterTableComponentGroupsMakeColumnInteger.php
├── 2016_04_05_142933_create_sessions_table.php
├── 2016_04_29_061916_AlterTableSubscribersAddGlobalColumn.php
├── 2016_06_02_075012_AlterTableMetricsAddOrderColumn.php
├── 2016_06_05_091615_create_cache_table.php
├── 2016_07_25_052444_AlterTableComponentGroupsAddVisibleColumn.php
├── 2016_08_23_114610_AlterTableUsersAddWelcomedColumn.php
├── 2016_09_04_100000_AlterTableIncidentsAddStickiedColumn.php
├── 2016_10_24_183415_AlterTableIncidentsAddOccurredAtColumn.php
├── 2016_10_30_174400_CreateSchedulesTable.php
├── 2016_10_30_174410_CreateScheduleComponentsTable.php
├── 2016_10_30_182324_AlterTableIncidentsRemoveScheduledColumns.php
├── 2016_12_04_163502_AlterTableMetricsAddVisibleColumn.php
├── 2016_12_05_185045_AlterTableComponentsAddMetaColumn.php
├── 2016_12_29_124643_AlterTableSubscribersAddPhoneNumberSlackColumns.php
├── 2016_12_29_155956_AlterTableComponentsMakeLinkNullable.php
├── 2017_01_03_143916_create_notifications_table.php
├── 2017_02_03_222218_CreateActionsTable.php
├── 2017_06_13_181049_CreateMetaTable.php
├── 2017_07_18_214718_CreateIncidentComponents.php
├── 2017_09_14_180434_AlterIncidentsAddUserId.php
├── 2018_04_02_163328_CreateTaggablesTable.php
├── 2018_04_02_163658_MigrateComponentTagTable.php
├── 2018_06_14_201440_AlterSchedulesSoftDeletes.php
└── 2018_06_17_182507_AlterIncidentsAddNotifications.php

0 directories, 54 files

的确有点感人…虽然这样的形式可以按照文件前的时间顺序执行迁移并且有向后兼容的能力,但是把 Model 和 Migration 写在一个文件里面的话对于大型项目来说可能并不是非常容易管理,虽然 Laravel 在路由和控制器上的优雅占有很大优势,不过这一点上我投 Django~

#Chinese #Django #Laravel