3caa6e0 13 years ago
1 contributor
770 lines | 26.264kb
=encoding utf8

=head1 NAME

DBIx::Custom::Guide::Ja - DBIx::Customの日本語ガイド

=head1 ガイド

L<DBIx::Custom>はデータベースへのクエリの発行を簡単に行うための
クラスです。L<DBIx::Class>やL<DBIx::Simple>と同じように
L<DBI>のラッパクラスになっています。L<DBIx::Class>よりも簡単に、
L<DBIx::Simple>よりもはるかに柔軟なことを行うことができます。

L<DBIx::Custom>はO/Rマッパーではありません。O/Rマッパーは
便利ですが、O/Rマッパのたくさんの文法を覚える必要があります。
また、O/Rマッパによって生成されたSQLは非効率なことがありますし、
複雑なSQLを生成することができないので、
生のSQLを発行しなければならない場合がたくさんあります。

L<DBIx::Custom>はO/Rマッパとは対照的な設計が行われています。
L<DBIx::Custom>の主な目的は、SQLを尊重しつつ、L<DBI>だけでは
とてもめんどうな作業を簡単にすることです。もしSQLについて
多くの知識を持っているならば、L<DBIx::Custom>でそのまま
活用することができます。

L<DBIx::Custom>の仕組みを簡単に説明しておきましょう。
L<DBIx::Custom>では、タグと呼ばれるものを
SQLの中に埋め込むことができます。

    select * from book where {= title} and {=author};

{}で囲まれた部分がタグです。このSQLは実際に実行されるときには
次のようにプレースホルダに展開されます。

    select * from book where title = ? and author = ?;

これらの展開にはどのような意味があるのでしょうかと質問
されることかと思います。この簡単な仕組みの上に非常にたくさんの
有用で便利で使いやすい機能が構築されます。それは以下のようなものです。

=over 4

=item 1. プレースホルダのパラメータをハッシュリファレンスで指定

L<DBI>をそのまま使うのであればプレースホルダのパラメータは配列
で指定する必要があります。

    $sth->execute(@bind);

L<DBIx::Custom>を利用するのであればハッシュリファレンスで指定すること
できます。
    
    my $param = {title => 'Perl', author => 'Ken'};
    $dbi->execute($sql, $param);

=item 2. パラメータのフィルタリング

たとえば、日付の列は、Perlで扱うときにはC<Time::Piece>などの日付オブジェクト
で扱い、データベースに格納するときはデータベースの日付型に変換したい
と思うのではないでしょうか。またデータベースから取り出すときは
データベースの日付型から日付オブジェクトに変換したと思うのでは
ないでしょうか。

このようなときはフィルタ機能を使うことができます。

まずフィルタを登録します。

    $dbi->register_filter(
        tp_to_date => sub {
            ...
        },
        date_to_tp => sub {
            ...
        }
    );

次にテーブルの各列にこのフィルタを適用します。

    $dbi->apply_filter('book',
        'issue_date' => {out => 'tp_to_date', in => 'date_to_tp'}
    );

outはPerlからデータベースに保存する方向、inはデータベースからPerlに取得する方向です。

SQLを発行するときにテーブルの指定を行えば、自動的にこのフィルタが適用されます。

    $dbi->execute($sql, $param, table => 'book');

=item 3. 選択的な検索条件

生のDBIを利用しているとき一番たいへんなのは選択的な検索条件を作成したいときです。

たとえば、検索条件にtitleとauthorが指定された場合は次のSQLを

    select * from book where title = ? and author = ?;

titleだけの場合は次のSQLを

    select * from book where title = ?;
    
authorだけの場合は次のSQLを発行した場合を考えましょう。

    select * from book where author = ?;

これはとても大変な作業なので、通常はL<SQL::Abstract>を動的に生成してくれる
モジュールを利用することになります。

L<DBIx::Custom>はさらに簡単で便利な方法を用意しています。

    my $where = $dbi->where;
    $where->param({title => 'Perl'});
    $where->clause(
        ['and', '{= title}', {'= author'}]
    );

    my $sql = "select * from book $where";

詳しい説明は後ほど行いますが、上記のように記述すれば、
L<DBIx::Custom>では選択的な検索条件を持つWhere句を生成することができます。
検索条件が入れ子になった構造やorについても対応しています。

=item 4. 挿入、更新、削除、選択を行うためのメソッド

L<DBIx::Custom>ではSQLをさらに簡単に実行するための
メソッドも提供しています。
C<insert()>, C<update()>, C<delete()>,C<select()>などの
シュガーメソッドを使って、挿入、更新、削除、選択という操作を行うことが
できます。

    my $param = {title => 'Perl', author => 'Ken'};
    $dbi->insert(table => 'book', param => $param);

=item 5. テーブル単位の操作の登録

テーブルに対して操作を登録することができます。これによって
テーブル名を繰り返し指定する必要がなくなり、ソースコードの
見通しが良くなります。

    $dbi->talbe('book',
        list => sub {
            ...
        },
        list_somethin => sub {
            
        }
    );

登録したメソッドはそのまま利用することができます。

    $dbi->table('book')->list;

通常O/Rマッパはテーブルに対応するクラスを作成しなければ
ならないことが多いですが、L<DBIx::Custom>ではこの作業を簡便に
しており、上記のように登録することができます。

=back

L<DBIx::Custom>はL<DBI>を補うとても便利なモジュールです。
興味をもたれた方は、この後で詳しい解説を行いますので、
ご覧になってみてください。

=head2 1. データベースへの接続

まずL<DBIx::Custom>を読み込みます。

    use DBIx::Custom;

L<DBIx::Custom>オブジェクトを生成し、データベースに接続するには
C<connect()>メソッドを使用します。

    my $dbi = DBIx::Custom->connect(
        data_source => "dbi:mysql:database=dbname",
        user => 'ken',
        password => '!LFKD%$&',
        dbi_options => {mysql_enable_utf8 => 1}
    );

C<data_source>はデータベースシステムに応じたフォーマットで
指定する必要があります。以下にデータベースごとのフォーマット
方法のサンプルを掲載しておきます。

B<MySQL>

    "dbi:mysql:database=$database"
    "dbi:mysql:database=$database;host=$hostname;port=$port"

B<SQLite>

    "dbi:SQLite:dbname=$database"
    "dbi:SQLite:dbname=:memory:"

B<PostgreSQL>

    "dbi:Pg:dbname=$dbname"

B<Oracle>

    "dbi:Oracle:$dbname"
    "dbi:Oracle:host=$host;sid=$sid"

B<ODBC(Microsoft Access)>

    "dbi:ODBC:driver=Microsoft Access Driver (*.mdb);dbq=hoge.mdb"

B<ODBC(SQL Server)>

   "dbi:ODBC:driver={SQL Server};Server=(local);database=test;Trusted_Connection=yes;AutoTranslate=No;"

また認証が求められる場合は、C<user>とC<password>ユーザ名と
パスワードを指定する必要があります。

L<DBIx::Custom>はL<DBI>のラッパです。
L<DBI>のデータベースハンドルはC<dbh>で取得することができます。

    my $dbh = $dbi->dbh;

L<DBIx::Custom>ではデータベースハンドル属性にはデフォルトで次のものが設定されます。
    
    $dbi->dbh->{RaiseError} = 1;
    $dbi->dbh->{PrintError} = 0;
    $dbi->dbh->{AutoCommit} = 1;

この設定を行っているので、致命的なエラーが起こると、
例外が発生しプログラムは終了します。
またクエリが発行されると自動的にコミットされます。

=head2 2. 挿入、更新、削除、選択のためのメソッド

L<DBIx::Custom>は、
C<insert()>、C<update()>、C<delete()>、C<select()>
のような挿入、更新、削除、選択を行うためのメソッドを持っています。
簡単なことをを行うのであれば、SQLを自分で記述する必要がありません。

=head3 C<insert()>

データベースにデータを挿入するにはC<insert()>を使用します。

    $dbi->insert(table  => 'book',
                 param  => {title => 'Perl', author => 'Ken'});

C<table>にはテーブル名、C<param>には挿入したいデータを指定します。

次のSQLが発行されます。

    insert into (title, author) values (?, ?);

=head3 C<update()>

データベースのデータを更新するには、C<update()>を使用します。

    $dbi->update(table  => 'book', 
                 param  => {title => 'Perl', author => 'Ken'}, 
                 where  => {id => 5});

C<table>にはテーブル名、C<param>には挿入したいデータ、C<where>には
条件を指定します。

次のSQLが発行されます。

    update book set title = ?, author = ?;

C<update>メソッドは安全のため
where句のないSQLを発行することを許可していません。
もしすべての行を更新したい場合は
C<update_all()>を使用してください。

    $dbi->update_all(table  => 'book', 
                     param  => {title => 'Perl', author => 'Ken'});

=head3 C<delete()>

データベースのデータを1件削除するには、C<delete()>を使用します。

    $dbi->delete(table  => 'book',
                 where  => {author => 'Ken'});

C<table>にはテーブル名、C<where>には条件を指定します。

次のSQLが発行されます。

    delete from book where id = ?;

C<delete>メソッドは安全のため
where句のないSQLを発行することを許可していません。
もしすべての行を削除したい場合は
C<delete_all()>を使用してください。

    $dbi->delete_all(table  => 'book');

=head3 C<select()>

行を選択するにはC<select()>を使用します。

    my $result = $dbi->select(table => 'book');

C<table>だけを指定して、他の条件を指定しない場合は次のSQLが発行されます。

    select * from book;

C<select()>メソッドの戻り値はL<DBIx::Custom::Result>
オブジェクトです。行をフェッチするにはC<fetch()>を使用します。

    while (my $row = $result->fetch) {
        my $title  = $row->[0];
        my $author = $row->[1];
    }

L<DBIx::Custom::Result>についてはこの後L<3. 行のフェッチ/"3. 行のフェッチ">で詳しく扱います。

さまざまなC<select()>の使い方を見ていきましょう。
次のC<select>は行の名前とwhere句を指定したものです。

    my $result = $dbi->select(
        table  => 'book',
        column => ['author',  'title'],
        where  => {author => 'Ken'}
    );

C<column>には列名を、C<where>には条件を指定することができます。
次のSQLが発行されます。

    select author, title from book where author = ?;

テーブルを結合したい場合ははC<relation>にテーブルの
関係を記述します。

    my $result = $dbi->select(
        table    => ['book', 'rental'],
        where    => {book.name => 'Perl'},
        relation => {'book.id' => 'rental.book_id'}
    );

bookテーブルのid列とrentalテーブルのbook_idが関連付けられます。
次のSQLが発行されます。

    select * from book, rental where book.name = ? and book.id = rental.book_id;

SQL文の末尾に文字列を追加したい場合は<append>を使用します。

    my $result = $dbi->select(
        table  => 'book',
        where  => {author => 'Ken'},
        append => 'for update',
    );

次のSQLが発行されます。

    select * book where author = ? for update;

またC<append>は、C<select>だけでなくC<insert()>、C<update()>、C<update_all()>
C<delete()>、C<delete_all()>、C<select()>で使用することもできます。

=head2 3. 行のフェッチ

C<select()>メソッドの戻り値はL<DBIx::Custom::Result>オブジェクトです。
L<DBIx::Custom::Result>には行をフェッチするためのさまざまなメソッドが
用意されています。

=head3 C<fetch>

一行フェッチして配列のリファレンスに格納するにはC<fetch()>を使用します。

    while (my $row = $result->fetch) {
        my $title  = $row->[0];
        my $author = $row->[1];
    }

whileループを使って、すべての行を取得することができます。

=head3 C<fetch_first>

一行だけフェッチして配列のリファレンスに格納するにはC<fetch_first()>
を使用します。

    my $row = $result->fetch_first;

一行のフェッチが終わった後はそれ以上フェッチできなくなります。
内部的には1行のフェッチが終わった後に
ステートメントハンドルのC<finish()>が実行されます。

=head3 C<fetch_multi>

複数行をフェッチして配列のリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_multi()>を使用します。

    while (my $rows = $result->fetch_multi(2)) {
        my $title0   = $rows->[0][0];
        my $author0  = $rows->[0][1];
        
        my $title1   = $rows->[1][0];
        my $author1  = $rows->[1][1];
    }

引数には取り出したい行数を指定します。

指定した行を格納した次のようなデータを取得できます。

    [
        ['Perl', 'Ken'],
        ['Ruby', 'Mark']
    ]

=head3 C<fetch_all>

すべての行をフェッチして配列のリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_all()>を使用します。

    my $rows = $result->fetch_all;

すべての行を格納した次のようなデータを取得できます。

    [
        ['Perl', 'Ken'],
        ['Ruby', 'Mark']
    ]

=head3 C<fetch_hash>

一行フェッチしてハッシュのリファレンスに格納するにはC<fetch_hash()>を使用します。

    while (my $row = $result->fetch_hash) {
        my $title  = $row->{title};
        my $author = $row->{author};
    }

=head3 C<fetch_hash_first>

一行だけフェッチしてハッシュのリファレンスに格納するには
C<fetch_hash_first()>を使用します。

    my $row = $result->fetch_hash_first;

一行のフェッチが終わった後はそれ以上フェッチできなくなります。
内部的には1行のフェッチが終わった後に
ステートメントハンドルのC<finish()>が実行されます。

=head3 C<fetch_hash_multi>

複数行をフェッチしてハッシュのリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_hash_multi()>
を使用します。

    while (my $rows = $result->fetch_hash_multi(5)) {
        my $title0   = $rows->[0]{title};
        my $author0  = $rows->[0]{author};
        my $title1  = $rows->[1]{title};
        my $author1 = $rows->[1]{author};
    }

引数には取り出したい行数を指定します。

指定した行を格納した次のようなデータを取得できます。

    [
        {title => 'Perl', author => 'Ken'},
        {title => 'Ruby', author => 'Mark'}
    ]

=head3 C<fetch_all>

すべての行をフェッチしてハッシュのリファレンスを要素に持つ
配列のリファレンスに格納するにはC<fetch_hash_all()>
を使用します。

    my $rows = $result->fetch_hash_all;

すべての行を格納した次のようなデータを取得できます。

    [
        {title => 'Perl', author => 'Ken'},
        {title => 'Ruby', author => 'Mark'}
    ]

=head3 sth

ステートメントハンドルに直接アクセスしたい場合は
<sth>で取得することができます。

    my $sth = $result->sth;

フェッチのパフォーマンスが用件を満たさないときには、
ステートメントハンドルから
利用できる速度の速いメソッドを利用することができます。

=head2 4. フィルタリング

データベースにデータを登録するときやデータベースからデータを取得する
ときに自動的に値の変換を行いたい場合が多いと思います。
たとえば、日付を表現する列の場合は、
データベースに登録する場合はL<Time::Piece>オブジェクトから
データベースの日付のフォーマットに、
データベースからデータを取得するときは、その逆を行えると便利です。

=head3 フィルタの登録

フィルタを登録するにはC<register_filter()>を使用します。

    $dbi->register_filter(
        # Time::Piece object to DATE format
        tp_to_date => sub {
            my $date = shift;

            return '0000-00-00' unless $tp;
            return $tp->strftime('%Y-%m-%d');
        },
        
        # DATE to Time::Piece object
        date_to_tp => sub {
            my $date = shift;

            return if $date eq '0000-00-00';
            return Time::Piece->strptime($date, '%Y-%m-%d');
        },
    );

登録したフィルタはC<apply_filter()>などで利用することができます。

=head3 フィルタの適用

作成したフィルタを適用するには、C<apply_filter()>を使用します。

    $dbi->apply_filter('book',
        issue_date => {out => 'tp_to_date', in => 'date_to_tp'},
        first_issue_date => {out => 'tp_to_date', in => 'date_to_tp'}
    );

第一引数はテーブル名です。第二引数以降は、列名とフィルタルールのペアを記述します。
フィルタルールのoutには、データベースにデータを送信するときに適用するフィルタを、
フィルタルールのinには、データベースからデータを取得するときに適用するフィルタを
記述します。outがデータベースに送信する方向、inがデータベースから取り出す方向です。
フィルタには、C<register_filter>で登録したフィルタ名の他に、コードリファレンスを
指定することもできます。

    issue_date => {out => sub { ... }, in => sub { ... }}

適用されたフィルタはC<insert()>、C<update()>、C<update_all()>、C<delete()>、
C<delete_all()>、C<select()>で有効になります。

    my $tp = Time::Piece->strptime('2010/10/14', '%Y/%m/%d');
    my $result = $dbi->select(table => 'book', where => {issu_date => $tp});

データベースにデータが送信されるときに、L<Time::Piece>オブジェクトは
データベースの日付のフォーマット「2010-10-14」に変換されます。

また逆にデータをフェッチするときには、データベースの日付のフォーマットは
タイムピースオブジェクトに変換されます。

    my $row = $resutl->fetch_hash_first;
    my $tp = $row->{issue_date};

このような自動的に実行されるフィルタを登録できることがL<DBIx::Custom>の
特徴のひとつです。

=head3 個別にフィルタを適用する

C<apply_filter()>を使って最初にすべてのテーブルの列について
フィルタを定義しておくこともできますが、
さらに個別にフィルタを適用することもできます。
個別のフィルタはC<apply_filter()>で適用したフィルタを上書きます。

行をフェッチするときに個別にフィルタを適用するには、
C<DBIx::Custom::Result>クラスのC<filter>メソッドを使用します。

    $result->filter(issue_date => 'date_to_tp', first_issue_date => 'date_to_tp');

これは、C<apply_filter()>のフィルタルールのinで定義されたフィルタを上書きます。

=head2 5. タグ

L<DBIx::Custom>はハッシュパラメタバインドを提供します。

まず最初にL<DBI>による通常のパラメタバインドをご覧ください。

    use DBI;
    my $dbh = DBI->connect(...);
    my $sth = $dbh->prepare(
        "select * from book where author = ? and title like ?;"
    );
    $sth->execute('Ken', '%Perl%');

これはデータベースシステムがSQLをキャッシュすることができ、
パラメータは自動的にクォートされるので、
パフォーマンス面でも、セキュリティ面でも
とても良い方法です。


L<DBIx::Custom>はこれを改善して、ハッシュで
パラメタを指定できるようにしました。

    my $result = $dbi->execute(
        "select * from book where {= author} and {like title};"
        param => {author => 'Ken', title => '%Perl%'}
    );

C<{= author}>とC<{like title}>はタグと呼ばれます。
タグは内部ではプレースホルダを含む文字列に置き換えられます。

    select * from book where {= author} and {like title}

という文は以下のSQLに置き換えられます。

    select * from book where author = ? and title like ?;

このようにタグを使ってSQL文を表現するのがL<DBIx::Custom>の
特徴です。以下のタグが利用可能です。

    [TAG]                       [REPLACED]
    {? NAME}               ->   ?
    {= NAME}               ->   NAME = ?
    {<> NAME}              ->   NAME <> ?
    
    {< NAME}               ->   NAME < ?
    {> NAME}               ->   NAME > ?
    {>= NAME}              ->   NAME >= ?
    {<= NAME}              ->   NAME <= ?
    
    {like NAME}            ->   NAME like ?
    {in NAME COUNT}        ->   NAME in [?, ?, ..]
    
    {insert_param NAME1 NAME2}   ->   (NAME1, NAME2) values (?, ?)
    {update_param NAME1 NAME2}   ->   set NAME1 = ?, NAME2 = ?

これらの変換はL<DBIx::Custom::QueryBuilder>によって行われます。

C<{>とC<}>は予約語です。これらの文字を使いたい場合は
「\」を使ってエスケープする必要があります。
'\'はPerlのエスケープ文字なので、
エスケープするためには'\\'と書く必要があることに注意
してください。

    'select * from book \\{ something statement \\}'

=head2 6. パフォーマンスの改善

=head3 シュガーメソッドを使わない

もしC<insert()>メソッドを使用してインサートを実行した場合、
必要なパフォーマンスを得られない場合があるかもしれません。
C<insert()>メソッドは、SQL文とステートメントハンドルを
毎回作成するためすこし遅いです。

そのような場合は、C<create_query()>メソッドによって
クエリを用意しておくことができます。
    
    my $query = $dbi->create_query(
        "insert into book {insert_param title author};"
    );

戻り値はL<DBIx::Custom::Query>オブジェクトです。
このオブジェクトはSQL文とパラメータバインド時の列名を
保持しています。またステートメントハンドルも保持しています。

    {
        sql     => 'insert into book (title, author) values (?, ?);',
        columns => ['title', 'author'],
        sth     => $sth
    }

クエリオブジェクトを使って繰り返し実行するには次のようにします。
    
    my $inputs = [
        {title => 'Perl',      author => 'Ken'},
        {title => 'Good days', author => 'Mike'}
    ];
    
    foreach my $input (@$inputs) {
        $dbi->execute($query, $input);
    }

C<execute>メソッドの第一引数にクエリオブジェトを渡すことができます。
これはC<insert()>メソッドよりも高速です。

=head2 7. その他の機能

=head3 トランザクション

トランザクションを便利に利用するために、
C<begin_work()>、C<commit()>、C<rollback()>
という三つのメソッドが容易されています。
これはL<DBI>の同名のメソッドと同じ機能を持ちます。
fc
    $dbi->begin_work;
    
    eval {
        $dbi->update(...);
        $dbi->update(...);
    };
    
    if ($@) {
        $dbi->rollback;
    }
    else {
        $dbi->commit;
    }

=head3 selectメソッドの結果クラスの変更

必要ならばC<select()>メソッドの結果クラスを変更することができます。

    package Your::Result;
    use base 'DBIx::Custom::Result';
    
    sub some_method { ... }

    1;
    
    package main;
    
    use Your::Result;
    
    my $dbi = DBIx::Custom->connect(...);
    $dbi->result_class('Your::Result');

=head3 L<DBIx::Custom::QueryBuilder>の機能の拡張

新しいタグが欲しい場合はL<DBIx::Custom::QueryBuilder>の機能を拡張
することができます。

    my $dbi = DBIx::Custom->connect(...);
    $dbi->query_builder->register_tag_processor(
        name => sub {
           ...
        }
    );

=head3 ヘルパーメソッドの登録

ヘルパーメソッドを登録することができます。

    $dbi->helper(
        update_or_insert => sub {
            my $self = shift;
            # do something
        },
        find_or_create   => sub {
            my $self = shift;
            # do something
        }
    );

<helper()>メソッドで登録したメソッドは
L<DBIx::Custom>オブジェクトから直接呼び出すことができます。

    $dbi->update_or_insert;
    $dbi->find_or_create;

=head3 ユーティリティメソッド

C<expand>メソッドを使用すると次のようなハッシュに含まれる
テーブル名と列名を結合することができます。

    my %expanded = $dbi->expand(\%source);

以下のハッシュ

    {book => {title => 'Perl', author => 'Ken'}}

は次のように展開されます。

    ('book.title' => 'Perl', 'book.author' => 'Ken')

これはテーブル名を含むselect文で利用すると便利です。

    my $param = {title => 'Perl', author => '%Ken%'};
    $dbi->execute(
        'select * from book where {= book.title} && {like book.author};',
        param => {$dbi->expand({book => $param})}
    );

=cut