如何在 MySQL 中使用 JSON

介绍

MySQL 5.7.8 版引入了一种 JSON 数据类型,允许您访问 JSON 文档中的数据。

SQL 数据库在设计上趋于僵化。就其性质而言,结构化查询语言强制执行数据类型和大小限制。

相比之下,NoSQL 数据库鼓励设计的灵活性。在这些无模式数据库中,没有强加的结构限制,只有要保存的数据。

MySQL 中的 JSON 数据类型赋予您这两种系统的优势。它允许您构建数据库的某些部分,而让其他部分保持灵活。

本文的前半部分将设计一个带有 JSON 字段的数据库。它将逐步使用 MySQL 可用的内置函数来创建、读取、更新和删除行。

本文的后半部分将利用 Eloquent ORM 和 Laravel 与数据库进行通信。您将构建一个支持显示产品、添加新产品、修改现有产品和删除产品的管理面板。

先决条件

如果你想跟随这篇文章,你需要:

注意: Laravel 现在提供了一个名为Sail的工具来与 Docker 一起使用,该工具将使用 MySQL、PHP 和 Composer 配置环境。

如果您在设置本地环境时遇到困难,这可能是另一种选择。

本教程已通过 MySQL v8.0.23、PHP v7.3.24、Composer v2.0.9 和 Laravel v8.26.1 验证。

步骤 1 — 定义架构

出于本教程的目的,您将从定义销售各种电子产品的在线商店的库存的模式构建。

传统上,实体-属性-价值模型 (EAV)模式将用于允许客户比较产品的功能。

但是,对于 JSON 数据类型,可以以不同的方式处理此用例。

该数据库将被命名e_store,并有三个表命名brandscategoriesproducts分别。

创建e_store数据库:

CREATE DATABASE IF NOT EXISTS `e_store`
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;

SET default_storage_engine = INNODB;

brandscategories表将各有一个idname领域。

创建brands表:

CREATE TABLE `e_store`.`brands`(
    `id` INT UNSIGNED NOT NULL auto_increment ,
    `name` VARCHAR(250) NOT NULL ,
    PRIMARY KEY(`id`)
);

创建categories表:

CREATE TABLE `e_store`.`categories`(
    `id` INT UNSIGNED NOT NULL auto_increment ,
    `name` VARCHAR(250) NOT NULL ,
    PRIMARY KEY(`id`)
);

接下来,添加一些示例brands

INSERT INTO `e_store`.`brands`(`name`)
VALUES
    ('Samsung');

INSERT INTO `e_store`.`brands`(`name`)
VALUES
    ('Nokia');

INSERT INTO `e_store`.`brands`(`name`)
VALUES
    ('Canon');

然后,添加一些categories

INSERT INTO `e_store`.`categories`(`name`)
VALUES
    ('Television');

INSERT INTO `e_store`.`categories`(`name`)
VALUES
    ('Mobile Phone');

INSERT INTO `e_store`.`categories`(`name`)
VALUES
    ('Camera');

接下来,创建一个products与该表idnamebrand_idcategory_id,和attributes字段:

CREATE TABLE `e_store`.`products`(
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
    `name` VARCHAR(250) NOT NULL ,
    `brand_id` INT UNSIGNED NOT NULL ,
    `category_id` INT UNSIGNED NOT NULL ,
    `attributes` JSON NOT NULL ,
    PRIMARY KEY(`id`) ,
    INDEX `CATEGORY_ID`(`category_id` ASC) ,
    INDEX `BRAND_ID`(`brand_id` ASC) ,
    CONSTRAINT `brand_id` FOREIGN KEY(`brand_id`) REFERENCES `e_store`.`brands`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE ,
    CONSTRAINT `category_id` FOREIGN KEY(`category_id`) REFERENCES `e_store`.`categories`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE
);

此表定义为brand_idcategory_id字段指定外键约束,指定它们分别引用brandscategories表。此表定义还指定不应允许删除引用的行,如果更新,更改也应反映在引用中。

attributes字段的列类型已声明为 JSON,这是 MySQL 中现在可用的本机数据类型。这允许您在attributes现场使用 MySQL 中的各种 JSON 相关构造

下面是创建的数据库的实体关系图:

e_store数据库实体关系图

这种数据库设计在效率和准确性方面并不是最好的。有一些常见的现实世界用例没有被考虑在内。例如,products表中没有价格列,也不支持一个产品属于多个类别。但是,本教程的目的不是教授数据库设计,而是教授如何使用 MySQL 的 JSON 功能在单个表中对不同性质的对象进行建模。

第 2 步 – 在 JSON 字段中创建数据

现在,您将使用INSERT INTO来创建要添加到数据库的产品VALUES

以下是一些示例电视,其中包含使用字符串化 JSON 对象的屏幕尺寸、分辨率、端口和扬声器数据:

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Prime' ,
    '1' ,
    '1' ,
    '{"screen": "50 inch", "resolution": "2048 x 1152 pixels", "ports": {"hdmi": 1, "usb": 3}, "speakers": {"left": "10 watt", "right": "10 watt"}}'
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Octoview' ,
    '1' ,
    '1' ,
    '{"screen": "40 inch", "resolution": "1920 x 1080 pixels", "ports": {"hdmi": 1, "usb": 2}, "speakers": {"left": "10 watt", "right": "10 watt"}}'
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Dreamer' ,
    '1' ,
    '1' ,
    '{"screen": "30 inch", "resolution": "1600 x 900 pixles", "ports": {"hdmi": 1, "usb": 1}, "speakers": {"left": "10 watt", "right": "10 watt"}}'
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Bravia' ,
    '1' ,
    '1' ,
    '{"screen": "25 inch", "resolution": "1366 x 768 pixels", "ports": {"hdmi": 1, "usb": 0}, "speakers": {"left": "5 watt", "right": "5 watt"}}'
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Proton' ,
    '1' ,
    '1' ,
    '{"screen": "20 inch", "resolution": "1280 x 720 pixels", "ports": {"hdmi": 0, "usb": 0}, "speakers": {"left": "5 watt", "right": "5 watt"}}'
);

此示例声明了五种不同的电视产品。

或者,您可以使用内置JSON_OBJECT函数来创建 JSON 对象。

JSON_OBJECT函数接受表单中的键/值对列表JSON_OBJECT(key1, value1, key2, value2, ... key(n), value(n))并返回一个 JSON 对象。

以下是一些使用该JSON_OBJECT功能的手机示例

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Desire' ,
    '2' ,
    '2' ,
    JSON_OBJECT(
        "network" ,
        JSON_ARRAY("GSM" , "CDMA" , "HSPA" , "EVDO") ,
        "body" ,
        "5.11 x 2.59 x 0.46 inches" ,
        "weight" ,
        "143 grams" ,
        "sim" ,
        "Micro-SIM" ,
        "display" ,
        "4.5 inches" ,
        "resolution" ,
        "720 x 1280 pixels" ,
        "os" ,
        "Android Jellybean v4.3"
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Passion' ,
    '2' ,
    '2' ,
    JSON_OBJECT(
        "network" ,
        JSON_ARRAY("GSM" , "CDMA" , "HSPA") ,
        "body" ,
        "6.11 x 3.59 x 0.46 inches" ,
        "weight" ,
        "145 grams" ,
        "sim" ,
        "Micro-SIM" ,
        "display" ,
        "4.5 inches" ,
        "resolution" ,
        "720 x 1280 pixels" ,
        "os" ,
        "Android Jellybean v4.3"
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Emotion' ,
    '2' ,
    '2' ,
    JSON_OBJECT(
        "network" ,
        JSON_ARRAY("GSM" , "CDMA" , "EVDO") ,
        "body" ,
        "5.50 x 2.50 x 0.50 inches" ,
        "weight" ,
        "125 grams" ,
        "sim" ,
        "Micro-SIM" ,
        "display" ,
        "5.00 inches" ,
        "resolution" ,
        "720 x 1280 pixels" ,
        "os" ,
        "Android KitKat v4.3"
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Sensation' ,
    '2' ,
    '2' ,
    JSON_OBJECT(
        "network" ,
        JSON_ARRAY("GSM" , "HSPA" , "EVDO") ,
        "body" ,
        "4.00 x 2.00 x 0.75 inches" ,
        "weight" ,
        "150 grams" ,
        "sim" ,
        "Micro-SIM" ,
        "display" ,
        "3.5 inches" ,
        "resolution" ,
        "720 x 1280 pixels" ,
        "os" ,
        "Android Lollipop v4.3"
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Joy' ,
    '2' ,
    '2' ,
    JSON_OBJECT(
        "network" ,
        JSON_ARRAY("CDMA" , "HSPA" , "EVDO") ,
        "body" ,
        "7.00 x 3.50 x 0.25 inches" ,
        "weight" ,
        "250 grams" ,
        "sim" ,
        "Micro-SIM" ,
        "display" ,
        "6.5 inches" ,
        "resolution" ,
        "1920 x 1080 pixels" ,
        "os" ,
        "Android Marshmallow v4.3"
    )
);

此示例声明了五种不同的手机产品。

它还利用了JSON_ARRAY在传递一组值时返回 JSON 数组的函数。

如果多次指定单个键,则只会保留第一个键/值对。这在 MySQL 的术语中称为标准化 JSON。此外,作为规范化的一部分,对象键被排序并删除键/值对之间的额外空白。

此外,您可以使用内置函数JSON_MERGE_PRESERVEJSON_MERGE_PATCH函数来创建 JSON 对象。

注意:在以前的 MySQL 版本中,您可以使用JSON_MERGE,但此功能已被弃用。

'JSON_MERGE' is deprecated and will be removed in a future release. Please use JSON_MERGE_PRESERVE/JSON_MERGE_PATCH instead

出于本教程的目的,您将使用该JSON_MERGE_PRESERVE函数。此函数采用多个 JSON 对象并生成单个聚合对象。

以下是使用该JSON_MERGE_PRESERVE功能的一些示例相机

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Explorer' ,
    '3' ,
    '3' ,
    JSON_MERGE_PRESERVE(
        '{"sensor_type": "CMOS"}' ,
        '{"processor": "Digic DV III"}' ,
        '{"scanning_system": "progressive"}' ,
        '{"mount_type": "PL"}' ,
        '{"monitor_type": "LCD"}'
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Runner' ,
    '3' ,
    '3' ,
    JSON_MERGE_PRESERVE(
        JSON_OBJECT("sensor_type" , "CMOS") ,
        JSON_OBJECT("processor" , "Digic DV II") ,
        JSON_OBJECT("scanning_system" , "progressive") ,
        JSON_OBJECT("mount_type" , "PL") ,
        JSON_OBJECT("monitor_type" , "LED")
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Traveler' ,
    '3' ,
    '3' ,
    JSON_MERGE_PRESERVE(
        JSON_OBJECT("sensor_type" , "CMOS") ,
        '{"processor": "Digic DV II"}' ,
        '{"scanning_system": "progressive"}' ,
        '{"mount_type": "PL"}' ,
        '{"monitor_type": "LCD"}'
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Walker' ,
    '3' ,
    '3' ,
    JSON_MERGE_PRESERVE(
        '{"sensor_type": "CMOS"}' ,
        '{"processor": "Digic DV I"}' ,
        '{"scanning_system": "progressive"}' ,
        '{"mount_type": "PL"}' ,
        '{"monitor_type": "LED"}'
    )
);

INSERT INTO `e_store`.`products`(
    `name` ,
    `brand_id` ,
    `category_id` ,
    `attributes`
)
VALUES(
    'Jumper' ,
    '3' ,
    '3' ,
    JSON_MERGE_PRESERVE(
        '{"sensor_type": "CMOS"}' ,
        '{"processor": "Digic DV I"}' ,
        '{"scanning_system": "progressive"}' ,
        '{"mount_type": "PL"}' ,
        '{"monitor_type": "LCD"}'
    )
);

此示例声明了五种不同的相机产品。

请注意,只有对象被传递给JSON_MERGE_PRESERVE函数。其中一些是使用该JSON_OBJECT函数构建的其他人已作为有效的 JSON 字符串传递。

JSON_MERGE_PRESERVE函数的情况下,如果一个键重复多次,它的值将作为数组保留在输出中。

例如,这是具有相同network的 JSON 对象的集合

SELECT JSON_MERGE_PRESERVE(
    '{"network": "GSM"}' ,
    '{"network": "CDMA"}' ,
    '{"network": "HSPA"}' ,
    '{"network": "EVDO"}'
);

这将产生一个值数组:

Output
{"network": ["GSM", "CDMA", "HSPA", "EVDO"]}

现在,此时,您可以使用JSON_TYPE显示字段值类型函数来验证您的查询

SELECT JSON_TYPE(attributes) FROM `e_store`.`products`;

此查询将产生 15 个OBJECT结果来表示所有产品 – 五台电视、五台手机和五台相机。

现在,您可以在 JSON 字段中创建数据。

第 3 步 – 从 JSON 字段读取数据

现在您在数据库中有一些产品可以使用,您可以尝试读取数据。

对于非 JSON 类型的典型 MySQL 值,您通常会依赖于WHERE子句。启发式地,当使用 JSON 列时,这不起作用。

当您希望使用 JSON 字段选择行时,您应该熟悉路径表达式的概念。路径表达式使用美元符号 ( $) 和目标对象键。

JSON_EXTRACT函数结合使用时,您可以检索指定列的值。

假设您对所有至少具有一个 USB 和一个 HDMI 端口的电视感兴趣:

SELECT
    *
FROM
    `e_store`.`products`
WHERE
    `category_id` = 1
AND JSON_EXTRACT(`attributes` , '$.ports.usb') > 0
AND JSON_EXTRACT(`attributes` , '$.ports.hdmi') > 0;

JSON_EXTRACT函数的第一个参数是用于应用列所在路径表达式的 JSON attributes$符号标记要使用的对象。$.ports.usb$.ports.hdmi路径表达式转换为“取USB项下口”和“走HDMI项下口”分别。

提取出您感兴趣的键后,您可以>对它们使用 MySQL 运算符,例如大于号 ( )。

此查询将产生三个结果:

显示 Prime、Octoview 和 Dreamer 型号电视的行的查询结果屏幕截图

这三台电视至少有一个 USB 端口和一个 HDMI 端口。“Bravia”和“Proton”车型不满足这些条件。

或者,该JSON_EXTRACT函数具有->可用于使查询更具可读性的别名

修改前面的查询以使用->别名:

SELECT
    *
FROM
    `e_store`.`products`
WHERE
    `category_id` = 1
AND `attributes` -> '$.ports.usb' > 0
AND `attributes` -> '$.ports.hdmi' > 0;

现在,您可以从 JSON 字段读取数据。

第 4 步 – 更新 JSON 字段中的数据

您可以使用JSON字段更新数据JSON_INSERTJSON_REPLACEJSON_SET功能。这些函数还需要一个路径表达式来指定要修改 JSON 对象的哪些部分。这些函数的输出是应用了更改的有效 JSON 对象。

首先,更新 JSON 字段,为所有手机JSON_INSERT添加一个chipset值为“Qualcomm”的新键:

UPDATE `e_store`.`products`
SET `attributes` = JSON_INSERT(
    `attributes` ,
    '$.chipset' ,
    'Qualcomm'
)
WHERE
    `category_id` = 2;

所述$.chipset路径表达式的识别的位置chipset特性是在该对象的根。

使用以下查询检查更新的手机类别:

SELECT
    *
FROM
    `e_store`.`products`
WHERE
    `category_id` = 2

“高通”现在适用于所有手机:

查询结果截图显示了现在添加了高通芯片组的手机型号的行。

现在,更新 JSON 字段JSON_REPLACE以修改chipset所有手机的值为“Qualcomm Snapsdragon”的现有键:

UPDATE `e_store`.`products`
SET `attributes` = JSON_REPLACE(
    `attributes` ,
    '$.chipset' ,
    'Qualcomm Snapdragon'
)
WHERE
    `category_id` = 2;

现在所有手机的“Qualcomm”都替换为“Qualcomm Snapdragon”:

高通骁龙芯片组手机型号行查询结果截图。

最后,更新 JSON 字段,为所有电视JSON_SET添加一个body_color值为“red”的新键:

UPDATE `e_store`.`products`
SET `attributes` = JSON_SET(
    `attributes` ,
    '$.body_color' ,
    'red'
)
WHERE
    `category_id` = 1;

“红色”颜色现在适用于所有电视:

查询结果的屏幕截图,显示现在添加了红色的电视机型号的行。

所有这些功能看似相同,但它们的行为方式却有所不同。

JSON_INSERT如果该属性不存在,函数只会将该属性添加到该对象中。

JSON_REPLACE函数仅在找到时才替换该属性。

JSON_SET如果未找到该属性,则函数将添加该属性,否则将替换它。

现在,您可以从 JSON 字段更新数据。

第 5 步 – 从 JSON 字段中删除数据

您可以使用JSON_REMOVE函数 和删除 JSON 字段中的数据DELETE

JSON_REMOVE 允许您从 JSON 列中删除某个键/值。

使用JSON_REMOVE函数,可以mount_type从所有相机中删除键/值对:

UPDATE `e_store`.`products`
SET `attributes` = JSON_REMOVE(`attributes` , '$.mount_type')
WHERE
    `category_id` = 3;

JSON_REMOVE函数根据路径表达式在删除指定的键后返回更新后的 JSON。

或者,您可以DELETE使用 JSON 列整行。

使用DELETEandJSON_EXTRACTLIKE,可以删除所有具有“Jellybean”版本Android操作系统的手机:

DELETE FROM `e_store`.`products`
WHERE `category_id` = 2
AND JSON_EXTRACT(`attributes` , '$.os') LIKE '%Jellybean%';

此查询将删除手机的“Desire”和“Passion”型号。

使用特定属性需要使用该JSON_EXTRACT函数。首先,os提取手机属性。然后将LIKE运算符应用于DELETE包含字符串的所有记录Jellybean

现在,您可以从 JSON 字段中删除数据。

第 6 步 – 创建迁移

现在,创建一个新的 Laravel 项目。

警告:此 Web 应用程序仅用于教程目的,不应在生产环境中使用。

打开终端窗口并运行以下命令:

  • composer create-project laravel/laravel estore-example

导航到新创建的项目目录:

  • cd estore-example

将 Laravel 应用程序配置为使用 MySQL 数据库。

您可能需要修改您的.env文件来设置DB_DATABASEDB_USERNAMEDB_PASSWORD

您将创建三个迁移brandscategoriesproducts分别。

进行create_brands迁移:

  • php artisan make:migration create_brands

create_brands.php使用以下代码行修改迁移:

数据库/迁移/(…)create_brands.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBrands extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('brands', function(Blueprint $table){
            $table->engine = 'InnoDB';
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('brands');
    }
}

进行create_categories迁移:

  • php artisan make:migration create_categories

create_categories.php使用以下代码行修改迁移:

数据库/迁移/(…)create_categories.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCategories extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('categories', function(Blueprint $table){
            $table->engine = 'InnoDB';
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('categories');
    }
}

create_products迁移也将有索引和外键的指示:

  • php artisan make:migration create_products

create_products.php使用以下代码行修改迁移:

数据库/迁移/(…)create_products.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProducts extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function(Blueprint $table){
            $table->engine = 'InnoDB';
            $table->increments('id');
            $table->string('name');
            $table->unsignedInteger('brand_id');
            $table->unsignedInteger('category_id');
            $table->json('attributes');
            $table->timestamps();
            // foreign key constraints
            $table->foreign('brand_id')->references('id')->on('brands')->onDelete('restrict')->onUpdate('cascade');
            $table->foreign('category_id')->references('id')->on('categories')->onDelete('restrict')->onUpdate('cascade');
            // indexes
            $table->index('brand_id');
            $table->index('category_id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

注意$table->json('attributes');迁移中声明。

注意:这仅适用于支持 JSON 数据类型的数据库引擎。

引擎,例如旧版本的 MySQL,将无法执行这些迁移。

类似于使用适当的数据类型命名的方法创建其他类型的表字段,已创建使用一个JSON列json名为方法attributes

第 7 步 – 创建模型

你要为创建三个模型brandscategoriesproducts分别。

创建Brand模型:

  • php artisan make:model Brand

Brand.php使用以下代码行修改文件:

应用程序/模型/Brand.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Brand extends Model
{
    use HasFactory;

    // A brand has many products
    public function products(){
        return $this->hasMany('Product')
    }
}

创建Category模型:

  • php artisan make:model Category

Category.php使用以下代码行修改文件:

应用程序/模型/Category.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    // A category has many products
    public function products(){
        return $this->hasMany('Product')
    }
}

创建Product模型:

  • php artisan make:model Product

Product.php使用以下代码行修改文件:

应用程序/模型/Product.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    public $timestamps = false;

    // Cast attributes JSON to array
    protected $casts = [
        'attributes' => 'array'
    ];

    // Each product has a brand
    public function brand(){
        return $this->belongsTo('Brand');
    }

    // Each product has a category
    public function category(){
        return $this->belongsTo('Category');
    }
}

设置$casts了键数组可确保每当从数据库中获取产品时,其JSON 都会转换为关联的数组。这允许您从控制器操作更新记录。attributesarrayattributes

步骤 8 — 创建产品

本教程其余部分的重点将放在相机产品类别上。

您将使用具有特定于相机的字段的表单构建视图。为简洁起见,不会涵盖电视和手机产品类别 – 但在设计上会非常相似。

为相机产品类别创建控制器:

  • php artisan make:controller CameraController

CameraController.php使用以下代码行修改

应用程序/Http/Controller/CameraController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CameraController extends Controller
{
    // creates product in database
    // using form fields
    public function store(Request $request){
        // create object and set properties
        $camera = new \App\Models\Product();
        $camera->name = $request->name;
        $camera->brand_id = $request->brand_id;
        $camera->category_id = $request->category_id;
        $camera->attributes = [
            'processor' => $request->processor,
            'sensor_type' => $request->sensor_type,
            'monitor_type' => $request->monitor_type,
            'scanning_system' => $request->scanning_system,
        ];
        // save to database
        $camera->save();
        // show the created camera
        return view('product.camera.show', ['camera' => $camera]);
    }
}

这样就完成了store相机功能。

通过new.blade.phpresources/views/product/camera目录树中创建文件来创建视图

资源/视图/产品/相机/new.blade.php
<form method="POST" action="/product/camera/store">
    @csrf
    <table>
        <tr>
            <td><label for="name">Name</label></td>
            <td><input id="name" name="name" type="text"></td>
        </tr>
        <tr>
            <td><label for="brand-id">Brand ID</label></td>
            <td>
                <select id="brand-id" name="brand_id">
                    <option value="1">Samsung</option>
                    <option value="2">Nokia</option>
                    <option value="3">Canon</option>
                </select>
            </td>
        </tr>
        <tr>
            <td><label for="attributes-processor">Processor</label></td>
            <td><input id="attributes-processor" name="processor" type="text"></td>
        </tr>
        <tr>
            <td><label for="attributes-sensor-type">Sensor Type</label></td>
            <td><input id="attributes-sensor-type" name="sensor_type" type="text"></td>
        </tr>
        <tr>
            <td><label for="attributes-monitor-type">Monitor Type</label></td>
            <td><input id="attributes-monitor-type" name="monitor_type" type="text"></td>
        </tr>
        <tr>
            <td><label for="attributes-scanning-system">Scanning System</label></td>
            <td><input id="attributes-scanning-system" name="scanning_system" type="text"></td>
        </tr>
    </table>
    <input name="category_id" value="3" type="hidden">
    <button type="submit">Submit</button>
</form>

brand_id以硬编码select元素形式呈现,其中包含早先创建的三个品牌作为options。category_id是作为设置为一个硬编码隐藏输入值id的相机。

修改路由routes/web.php以显示摄像机:

路线/ web.php
// ...

use App\Http\Controllers\CameraController;

Route::get('/product/camera/new', function() {
    return view('product/camera/new');
});

Route::post(
    '/product/camera/store',
    [CameraController::class, 'store']
);

使用以下命令为应用程序提供服务:

  • php artisan serve

然后,localhost:8000/product/camera/new使用 Web 浏览器访问)。它将显示用于添加新相机的表单。

步骤 9 — 获取产品

模型中$casts先前声明数组Product将通过将属性视为关联数组来帮助您阅读和编辑产品。

CamerasController使用以下代码行修改

应用程序/Http/Controller/CameraController.php
<?php

// ...

class CameraController extends Controller
{
    // ... store ...

    // fetches a single product
    // from database
    public function show($id){
        $camera = \App\Models\Product::find($id);
        return view('product.camera.show', ['camera' => $camera]);
    }
}

这样就完成了show相机功能。

通过show.blade.phpresources/views/product/camera目录树中创建文件来创建视图

资源/视图/产品/相机/show.blade.php
<table>
    <tr>
        <td>Name</td>
        <td>{{ $camera->name }}</td>
    </tr>
    <tr>
        <td>Brand ID</td>
        <td>{{ $camera->brand_id }}</td>
    </tr>
    <tr>
        <td>Category ID</td>
        <td>{{ $camera->category_id }}</td>
    </tr>
    <tr>
        <td>Processor</td>
        <td>{{ $camera->attributes['processor'] }}</td>
    </tr>
    <tr>
        <td>Sensor Type</td>
        <td>{{ $camera->attributes['sensor_type'] }}</td>
    </tr>
    <tr>
        <td>Monitor Type</td>
        <td>{{ $camera->attributes['monitor_type'] }}</td>
    </tr>
    <tr>
        <td>Scanning System</td>
        <td>{{ $camera->attributes['scanning_system'] }}</td>
    </tr>
</table>

修改路由routes/web.php以显示摄像机:

路线/ web.php
// ...

Route::get(
    '/product/camera/show/{id}',
    [CameraController::class, 'show']
);

使用以下命令为应用程序提供服务:

  • php artisan serve

然后,使用 Web 浏览器访问适用id于相机产品的有效产品(例如localhost:8000/product/camera/show/11)。它将显示带有id“11”的产品的相机信息表

步骤 10 — 编辑产品

By using a combination of the techniques for store and show, you can create a view to edit an existing product.

You can create a form similar to the one in new.blade.php. And then prepopulate it with a product variable similiar to the one used in show.blade.php:

<tr>
    <td><label for="attributes-processor">Processor</label></td>
    <td><input id="attributes-processor" name="processor" type="text" value="{{ $camera->attributes['processor'] }}"></td>
</tr>

Now, the form displays the existing values, making it easier for users to see what needs updating.

First, the id is used to retrieve the model. Next, the values from the request are applied. Lastly, the new values are saved to the database.

Step 11 — Searching Based on JSON Attributes

You can also query JSON columns using the Eloquent ORM.

Consider a search page that allows users to search for cameras based upon attributes that they are interested in.

public function search(Request $request){
    $cameras = \App\Models\Product::where([
        ['attributes->processor', 'like', $request->processor],
        ['attributes->sensor_type', 'like', $request->sensor_type],
        ['attributes->monitor_type', 'like', $request->monitor_type],
        ['attributes->scanning_system', 'like', $request->scanning_system]
    ])->get();
    return view('product.camera.search', ['cameras' => $cameras]);
}

The retrieved records will now be available to the product.camera.search view as a $cameras collection. This will allow you to loop through the results and display the cameras that satisfy the conditions from the user’s search request.

Step 12 — Deleting a Product

Using a non-JSON column attribute, you can delete products by specifying a where clause and then calling the delete method.

For example, in the case of an id.

\App\Models\Product::where('id', $id)->delete();

For JSON columns, specify a where clause using a single or multiple attributes and then call the delete method.

\App\Models\Product::where('attributes->sensor_type', 'CMOS')->delete();
}

In this example, this code will remove all products that have a sensor_type attribute set to “CMOS”.

Conclusion

In this article, you designed a MySQL database with the JSON data type and connected to it with a Laravel web application.

每当您需要将数据作为键/值对保存在单独的表中或使用实体的灵活属性时,您应该考虑改用 JSON 数据类型字段,因为它可以极大地帮助压缩您的数据库设计。

如果您有兴趣深入研究,MySQL 文档是进一步探索 JSON 概念的绝佳资源。

有关 Laravel 的更多信息,您可以查阅我们关于 Laravel入门的技术讲座

觉得文章有用?

点个广告表达一下你的爱意吧 !😁