MySQL JSON型のカラムで頑張らない開発生活
calendar_today
2023-01-21
insights
views: 963
thumbnail images

発端

システム開発の中で、(自分が)最も面倒くさいと思うもの第一位、テーブル構造変更。
カラムを一つ追加するだけなのに、やれマイグレーションファイルだ、モデル変更だ、バリデーション追加だなどとかなり面倒くさく思っていた。
この問題をなんとか解決できないかとNoSQLのMongoDBへDBの移行を検討するも、レプリケーションは必要だしLaravelのORM(laravel-mongodb)は出来ないことが多々あり微妙に使いづらく、移行には消極的だった。

そんな中、MySQL8.0のJSON型は機能追加てんこ盛りで色々出来るぞ!との記事を見かけ、最初は「RDBMSなのにNoSQLみたいなアプローチが混在してて微妙そう」と思いつつ簡単そうなのでこのサイトに導入してみたところ、メチャクチャ良い機能だったので記事にしてみる。

MySQL8.0で何が変わったか

さて、具体的に8.0で何が追加されたかを上げていく。

JSON型におけるPartial Update

JSON型を実質的な文字列ではなく、ちゃんと部分更新ができるようになった。

Multi-Valued Index

JSON型では直接インデックスを張ることが出来ない。そのため、JSONから抜き出したデータでカラムを作成し、仮想インデックスとすることで解決していた。
ただ、以前からJSON型ではインデックスを張っても速度があまり上がらないという問題があった。

それが8.0では、Multi-Valued Indexを張ることが出来るようになり、速度向上が見込めるようになった。

その他諸々JSON関係の関数の追加

色々出来るようになったぞ(省略)

メリット

メリットはたくさんあると思う。

  • テーブル構造を変更する作業が必要なくなる
    • 作業効率化
    • 変更作業によるミス・ヒューマンエラーの低減
  • 細かい正規化をする必要がなくなる
    • 開発速度向上

デメリット

逆にデメリットは以下のような感じ。

  • 対応ORM等が少なくなる
  • 何も考えずに作成すると、複雑すぎるクエリが出来る可能性
  • レコードの肥大化による速度低下

ただ、あまり改修しない場合や、堅牢なシステムが求められる場面以外は(特にWeb開発では)開発速度が格段に上がるので、一度使い心地を知ってもらってから判断するでも十分だと思う。

Laravelへの導入

色々出来るということがわかったので、早速このサイトにも導入してみよう。
といっても特に変わったことをする必要はなく、単純にJSON型のカラムを追加するだけ。

migration

// database/migration/add_json_to_users_table.php

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->json('json')->nullable();
    });
}

model

// app/Model/User.php
protected $fillable = [
    'user_id',
    'name',
    'email',
    'password',
    'json',
    'access_token',
    'refresh_token',
];

protected $casts = [
    'json' => 'json',
];

これでmigrationし、あとはcontrollerで使うだけ。

public function boot()
{
    $user = User::find(1);
}

基本的にはLaravelのORMが普通に利用できる。
ただ、where系はちょっと違う。

User::where('json->age', '>=', 20)
    ->orderByRaw('json->"$.age" ASC')
    ->get();

個人的にはorderByRaw使わないといけないのがとても悔しい。

create()ならば、


public function create()
{
    $user = new User;
    $user->name = $data->name;
    $user->json = [
        'age' => 30,
        'country' => 'Japan',
    ];
    $user->save();
}

JSON型のカラムに配列を突っ込むだけ。json_encode()が必要ないのがいい。

追記
なんと、$castsでAsCollectionを指定できるらしい。
これでデータを取得する際はCollectionと同様の使い心地になりそう。

protected $casts = [
    'json' => AsCollection::class,
];

update()だとちょっと厄介。
基本的にupdateでは、$fillableが動作して普通にセットしても更新されない。
なので、

  • カラムをブラックリスト方式にする。($fillableを空にして$guardedに指定する)
  • fill()の代わりにforceFill()を使用する
  • $model->jsonに、updateでも毎回配列を挿入する

いずれかの対応が必要。

追記
これもAsCollectionで、以下のように直感的に操作できるようになった。

$user = User::find($id);
$user->json['age'] = $age;
// AsCollectionならfill()も使える
// $user->fill($data);
$user->save();

使用した感想

個人的には、特にリレーショナルでないデータやフラグ、ちょっとしたパラメータ、カウンターなどは積極的にJSON型にブチ込んでくと開発速度が格段にアップすると思う。
またJSONだと配列として保存できるので、アップロードファイルの格納場所などを記録するのにも十分に威力を発揮するものだと感じた。

逆に、LaravelのORMが完全対応でないことに関しては少し残念だった。LaravelのORMは複雑なクエリをorWhereHasとかfirstOrCreateとかで綺麗なコードとして記述できるので、ぜひJSON型も完全対応してくれたらいいなあと思う。

calendar_today
2023-01-21
insights
views: 963