本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回はカヤックの村瀬大輔さんで、テーマはDBIx::Classです。
DBIx::Classとは
DBIx::ClassはPerlのO/Rマッピングモジュールです。O/Rマッピング(Object/Relational Mapping、以下ORM)とは、オブジェクト指向言語におけるオブジェクトとリレーショナルデータベースを紐づけるしくみのことで、ORMを使用するとユーザは直感的なオブジェクト操作によってデータベースを操作できるようになります。
DBIx::ClassはPerlのORMとしては現在世界で一番使われているモジュールです。日本では最近データベース操作モジュールとしてより軽量なDBIx::SkinnyやData::Modelなどの注目が高まってきていますが、機能的に枯れている点や豊富にテストされている点でDBIx::Classが現時点では最も信頼できるデータベース操作モジュールと言えるでしょう。
DBIx::Classも登場時にはいろいろな記事になりましたが、最近ではあまり取り上げられることもなくなりました。しかし登場時と比べると機能はずいぶん強化され、基本的な使い方での推奨方法も若干変更されていたりします。本稿では基本的な使い方を一通りおさらいしたあと、日本ではあまり記事にされていない発展的な使い方をいくつか紹介します。
サンプルDBスキーマ
本稿ではリスト1の定義のデータベースをサンプルとして使用します。このサンプルデータベースはTwitterのデータ構造を模したものになっていて、次のテーブルを持ちます。
- user:ユーザ
- tweet:つぶやき
- following_map:フォロー関係
- user_profile:ユーザ詳細プロフィール
基本的な使い方
まずはDBIx::Classの基本的な使い方を見てみましょう。
データベースクラス定義
DBIx::Classを使うには、まずそれを継承した自前のクラスを作成し、使用するデータベース情報を定義します。作成するクラスは次の3つがあります。
- Schemaクラス
- Resultクラス
- ResultSetクラス
●Schemaクラス
SchemaクラスはDBIx::Classを使ううえでベースとなるクラスで、次のように定義します。
この例ではMy::Schemaというクラス名でSchemaクラスを作成しています。
SchemaクラスはDBIx::Class::Schemaを継承して作成し、その中ではどのようにコンポーネントクラスを読み込むかなどを定義します。後方互換的にいろいろな定義方法があるのですが、現在は例のようにload_namespaces
を使用するのが一般的です。
load_namespaces
は、
- My::Schema::Result::*にあるResultクラス
- My::Schema::ResultSet::*にあるResultSetクラス
を自動的にロードするメソッドです。
●Resultクラス
Resultクラスはデータベースのテーブルを表すクラスで、操作する必要のあるテーブルの数だけ定義する必要があります。
Resultクラスは次のようにDBIx::Class::Coreを継承して作成します。
そしてこのResultクラスがデータベースのどのテーブルに紐付いているかを定義します。
続いてそのテーブルがどのようなカラムを持つのかも定義します。
カラム名だけでなく、より詳細な情報を登録することもできます。
DBIx::Classのコアではこの詳細情報は使われないので必ずしも詳細に定義する必要はありませんが、詳細に定義しておくとこの情報からデータベースにCREATETABLE
を発行したり、DBIx::Class::WebFormなどのコンポーネントが使用できるようになったりします。
そして、プライマリキーの設定をします。
ここまでが、テーブル定義に最低限必要な記述です。
●ResultSetクラス
ResultSetクラスはResultクラスの集合を表すクラスです。このクラスの定義は必須ではありません。定義しなかった場合はデフォルトのDBIx::Class::ResultSetがそのまま使用されます。
定義したクラスを使用する
●データベース接続情報定義
定義したスキーマクラスを使用するには、まずconnectメソッドを使用してスキーマオブジェクトを作ります。
connectメソッドに渡す引数はDBIのそれとほとんど同じで、次のようになっています。
●テーブルの操作
データベーステーブルを操作するにはそのテーブルに対するResultSet オブジェクトを取得します。ResultSet オブジェクトはSchema オブジェクトのresultsetメソッドで取得できます。
このResultSetオブジェクトが持つ各種メソッドを使用することで、ユーザテーブルに対する操作を行うことができます(リスト2)。
●レコードの操作
特定のレコードに対する操作はResultオブジェクトを使用して行います(リスト3)。
このようにレコードに対する操作はResultオブジェクトで、レコードの集合であるテーブルに対する操作はResultSetオブジェクトでそれぞれ操作します。
リレーションの定義
DBIx::Classでは複数テーブルの関係性をResultクラスに定義することで、その関係を使って楽にデータを扱うことができます。
●1:多──belongs_toとhas_many
ユーザ(User)とつぶやき(Tweet)との関係性に注目した場合、つぶやきは1つのユーザに属し(belongs_to
)、ユーザは複数のつぶやきを持つ(has_many
)関係にあります。これを1:多のリレーションと言い、データベースモデルの中で最も使われるリレーションモデルです。
DBIx::Class で1:多を定義するには、それぞれbelongs_toメソッド
、has_manyメソッド
を用います。
has_many
メソッドの第3引数ではTweet側のユーザIDを格納しているカラム名を指定します。このようにすることで、
と関係しているオブジェクトを直感的に取得できるようになります。
また、データ取得だけでなく検索やデータ追加なども次のようにより直感的に行えます。
●1:1──has_oneとmight_have
1:1のリレーションとは、複数のテーブルが共通の(プライマリ)キーを持つというようなリレーションモデルです。
DBIx::Classで1:1のリレーションを定義するには、has_one
メソッド、might_have
メソッドの2つがあります。has_one
とmight_have
の違いは、has_one
はリレーション先が必ず存在する場合にしか使えないのに対し、might_have
はリレーション先が存在しても存在しなくても定義できるということと、has_one
はINNERJOIN
を使用するのに対し、might_have
はLEFT JOIN
を使用するということです。
今回のサンプルではUserとUserProfileの関係が1:1にあたります。
●多:多──many_to_many
多:多のリレーションとは今回の例で言うと、ユーザテーブルとそれらのフォロー関係を定義するfollowing_mapテーブルがあった場合の、マッピングテーブルを介したユーザテーブル同士の関係性を言います(図1)。
DBIx::Classで多:多を定義にするには、many_to_many
メソッドを用います。
まず、1:多のリレーションを定義します。
そしてこの1:多のリレーションを使用して多:多のリレーションを使用できるよう定義します。
以上の定義をすることで$user->followers
という直感的なコードでユーザのフォロワー一覧のオブジェクトを取得できるようになります。
●カスケーディングデリート
さて、複数のテーブルがリレーションしている場合、削除処理が面倒になることがあります。ユーザを削除しようとした場合にそのユーザのつぶやきデータも併せて削除しないと、データの整合性がとれなくなってしまいます。DBIx::Classではリレーション定義を行っておくと関連するすべてのデータを同時に削除してくれます。これをカスケーディングデリートと言います。
基本事項はこのくらいにして、以降では発展的な使い方を見ていきましょう。記事の都合でDBIx::Classのすべてをカバーすることはできませんので、書ききれない分はドキュメントを参照してください。