ユーザ用ツール

サイト用ツール


perl:dbic

DBIx::Class

DBIx::Class(DBIC) はPerlのデータアクセスライブラリ

インストール

CPANインストール

DBICをCPANでインストール

  • DBI
  • DBD::mysql
  • DBIx::Class
  • DBIx::Class::Schema::Loader

DBIx::Class::Schema::Loaderを入れれば、その上の3つもインストールされる。DBDはどれを使うか聞かれるがデフォルトは「インストールしない」なので必要と思うならば連打しないように注意。DBD::*は後からインストールしても問題ない。

大量の依存パッケージをインストールするので未読のRSSでも読みながら気長に待ちましょう。

使い方

準備

まずMySQLにテーブルを作る

CREATE TABLE article (
  id         BIGINT AUTO INCREMENT NOT NULL,
  author_id  BIGINT NOT NULL,
  title      VARCHAR(255) NOT NULL,
  content    TEXT NOT NULL,
  written_at DATETIME NOT NULL,
  PRIMARY KEY(id),
  FOREIGN KEY(category_id) REFERENCES author (id) ON DELETE RESTRICT ON UPDATE RESTRICT
);
 
CREATE TABLE author (
  id BIGINT AUTO INCREMENT NOT NULL,
  name BIGINT NOT NULL,
  PRIMARY KEY(id),
);

Blog::Schema.pm を作成

use strict;
use warnings;
 
package Blog::Schema;
use base "DBIx::Class::Schema::Loader";
 
__PACKAGE__->loader_options(
  debug => 1, 
  components => ["InflateColumn::DateTime"]
)

debugはデバッグログを標準エラーに書き出す。components ⇒ [“InflateColumn::DateTime”] はdatetime型カラムをPerlのDateTimeクラスにマッピングするための設定。

DBICでデータアクセスするには、テーブルのスキーマクラスを作成する必要がある。これにはテーブルのカラム情報などが記述されるが、俺はDRY野郎なのでDDLで一度記述した情報をPerlでまた書くなんて面倒である。そこで DBIx::Class::Schema::Loader を Blog::Schema の親クラスにすると、スキーマクラスをデータベースの情報から実行時に動的生成する。この場合、Blog::Schema::Article と Blog::Schema::Author というクラスが生成されている。

使ってみる

$schema = Blog::Schema->connect("dbi:mysql:test", "user", "password", { mysql_enable_utf8 => 1, on_connect_do => ["SET NAMES utf8"] });
 
# DBIx::Class::ResultSet の取得
$resultset =  $schema->resultset("Article");
 
# リストコンテキストでsearchを実行すると DBIx::Class::Row の配列が取得される
@articles = $resultset->search("author_id" => 1);
 
# スカラコンテキストでsearchを実行すると検索条件を設定して DBIx::Class::ResultSet(自分自身)を返す、SQLは遅延実行される。
$articles = $resultset->search("author_id" => 2);
 
# 最初のレコードを取る
$articles->first;
 
# レコードに順番にアクセス
binmode STDOUT, ":utf8";
while($article = $articles->next) {
  print($article->title);
}

connectで接続する

  • mysql_enable_utf8は文字列のデータ保存時にutf8フラグを落とし、データ取り出し時にutf8フラグを立てるオプション。まだ試験実装らしいがみんな気にせず本番に使ってる。 
  • on_connect_doは接続時に実行するクエリを設定するもの。文字化けしないようにおなじみSET NAMES utf8を実行させる。

resultsetで指定するテーブル名先頭が大文字になっているので注意。ここで指定するのはテーブル名ではなく、スキーマクラスの Blog::Schema::Article から、Blog::Schemaを除いた名前である。

なお draft_item のようにテーブル名にアンダースコアがある場合は、Blog::Schema::DraftItems というクラス名になる。

InflateColumn::DateTime

DBのdatetime型をPerlのDateTimeオブジェクトを相互に自動変換することができる。

インストール

最新のDBICではDBIx::Class::InflateColumn::DateTimeがコアコンポーネントとなっているのでプラグインは不要だが、DateTimeとFormatterが必要。以下の2つのモジュールをCPANからインストールする。

  • DateTime
  • DateTime::Format::MySQL

設定

スキーマクラスのcomponentsに設定を追加する。

use strict;
use warnings;
 
package Blog::Schema;
use base "DBIx::Class::Schema::Loader";
 
__PACKAGE__->loader_options(
  debug => 1, 
  components => ["InflateColumn::DateTime"]
)

これでdatetimeカラムやtimestampカラムにDateTimeオブジェクトをセットしたり、DateTimeオブジェクトで取得したりできるようになる。

SQLログ

環境変数、DBIC_TRACE=1 をセットするコンソールにSQLログ出力する。ファイル名を指定するとファイルに書き出す。

DBIC_TRACE "=/tmp/sql.log"

環境変数DBIX_CLASS_STORAGE_DBI_DEBUGでも同じらしい(詳細不明…)

リレーションの設定

DBIx::Class::Schema::Loaderは外部キー制約を見てリレーションも設定してくれるが、アクセサメソッドのネーミングセンスがイマイチなので自分で設定する。自動設定させたいならloader_optionsで

relationships => 1

とする。

リレーション設定を書く

リレーションだけ自分で設定する場合は、動的生成されるスキーマクラスにリレーションだけ自分で記述すればいい。Schema::Loaderを使った場合は基本は自動生成で差分を自分で記述することができる。

先のテーブル設定では Blog::Schema::Author と Blog::Schema::Article というスキーマクラスが動的生成されるので、Blog/Schema/Author.pmとBlog/Schema/Article.pm ファイルを自分で作成してリレーション設定を記述。

# Blog/Schema/Author.pm
package Blog::Schema::Author;
 
__PACKAGE__->has_many("articles", "Blog::Schema::Article", "author_id");

アクセサメソッド名, リレーション先のクラス, 相手の外部キー名(参照されるインデックスがプライマリキーの場合はその記述を省略可能)

# Blog/Schema/Article.pm
package Blog::Schema::Article;
 
__PACKAGE__->belongs_to("author", "Blog::Schema::Author", "author_id");

アクセサメソッド名, リレーション先のクラス, 外部キー名(参照先のインデックスがプライマリキーの場合はその記述を省略可能)

使い方の例

$author_name = $schema->resultset("Article")->find(1)->author->name;
 
$rs_authors = $schema->resultset("Author")->search({"name" => {"like" => "あ%"}});
while(my $author = $rs_authors->next) {
  print($author->name);
  print($author->articles->first->title);
}

プリフェッチ

一対一、多対一リレーションのときは、最初のSQLのときに予めリレーションテーブルのデータも取得できる。

my $beginning_of_year = DateTime->today(time_zone => "local")->set(month => 1, day => 1);
my @results = $schema->resultset("article")->search({ written_at => { ">" => $beginning_of_year } },{ prefetch => "author" });
while( @results ) {
  print $_->title, "\n";
  print $_->author->name, "\n";
}

search_literalの条件とハッシュによる普通のsearch条件を同時に指定

search_literalを使えばSQLのwhere句を自在に書けるが、search_literalによる指定とハッシュによる普通のsearch条件と混在させたい場合。

Itemが1つ以上存在するCategoryで、CategoryのID

my %where;
my %param;
 
$where{ id } = { ">" => $id }
$where{ name } = { -like => $name . "%" };
$param{ order_by } = "me.name";
$where{ -and } = "EXISTS (SELECT 1 FROM items WHERE items.category_id = me.id AND items.attribute = ? AND items.name like ?)";
$param{ bind } = [ 100, "%Perl%"];
 
my @results = $schema->resultset("Categories")->search(\%where, \%param);

生成SQLは…

SELECT ....(省略).... FROM categories me 
WHERE (id = ? AND name LIKE = ? AND (EXISTS (SELECT 1 FROM items WHERE items.category_id = me.id AND items.attribute = ? AND items.name LIKE ?))) 
ORDER_BY me.name;

bindというキーでサブクエリのバインド変数を指定しているが、これはソースを読んで見つけた方法なのでバージョンが違うと動作しないかもしれない(調べたDBIx::Classのバージョンは0.08007)

perl/dbic.txt · 最終更新: 2010/01/17 14:10 by 127.0.0.1