Perlでデータベースまわりの処理をスッキリ書きたい


最近のPerlどうなってんの?ってtwitterで聞いたらモダンPerl入門をオススメされたので買ってみました。

この本は2009年に発売された本なので今でもモダンな内容なのか分かりませんが、自分がPerlで遊んでたのは10年前なので全く問題なし。

で、パラパラっと全体を眺めたところ、第3章のデータベースの項目が偶然にもちょうど今知りたかったことだったので、早速テストプログラムを書いてみました。

試したのは3つ。

  • SQLクエリをハッシュで持っておく方法
  • SQL::Abstractを使う方法
  • DBIx::Classを使う方法

(ソースは下の方にあります)

SQLクエリをハッシュで持っておく方法

SQLクエリを別ファイルに分けるなど実装と分離する方法は、ソースコード内がスッキリするので個人的には好きな方法です。
ちなみに、SQLカタログとかSQLライブラリとか言うらしいです。SQLライブラリという呼び名は少し紛らわしい気がしますね・・・

プリペアドステートメントを使って可変パラメータに対応するのがポイントです。
後述のライブラリなどを使っても、結局のところ直にクエリを書かなければいけないケースは排除しきれないですし、この方法は言語やモジュールを問わないので汎用的でもありますし、常に押さえておくべき方法だと思いました。

SQL::Abstractを使う方法

データベースベンダーごとの書式の差を埋めてくれるという部分は便利だとは思いますが、関数と配列を使った定義なので、見やすくしようと思うとどうしても複数行の記述になってしまいます。
そのため、見た目のスッキリさで言えば前述のSQLカタログの方が上だと思います。

関数呼び出しなのでソースコードからも分離出来ないですし、最終的にはprepare()やらexecute()やらの呼び出しなので、SQLカタログとさほど事態は変わらないという。

DBIx::Class

O/Rマッパです。
呼び出し側はスッキリ見えますが、実際にはテーブルごとにクラスを追加する必要があります。
とはいってもそれ自体はさほど難しい作業ではなく、リファレンスを見れば
ほぼコピペで行けるレベルです。

そしてO/Rマッパなので、パフォーマンス問題など、他の実装と同じ問題は当然抱えているようですが、どの程度のパフォーマンスなのかは今回調べていません。

CPANモジュールガイドの著者の方は使わなくなってしまったようですね・・・
use DBIx::Class; – 今日のCPANモジュール(跡地)
http://e8y.net/mag/011-dbix-class/

コードなど

まずはcpanでSQL::AbstractとDBIx::Classをインスコ。

$ sudo perl -MCPAN -e shell

cpan[1]> install SQL::Abstract
cpan[2]> install DBIx::Class
cpan[3]> q

ソースはこんな感じ。
DBIx::Class派生のスキーマクラスは省略。
ほとんどオフィシャルのドキュメントのコピペでいけます。
参考:DBIx::Class – search.cpan.org
http://search.cpan.org/~arodland/DBIx-Class-0.08196/lib/DBIx/Class.pm

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;
use utf8;
use Data::Dumper;

use DBI;
use SQL::Abstract;

# DBIx::Classで定義したO/Rマッパ用クラス
use Users::Schema;

#---------------------
# DB設定
#---------------------
my $dbs = 'DBI:mysql:Hoge:localhost:3306';
my $dbuser = 'root';
my $dbpass = '';

# テーブルの定義
# CREATE TABLE `users` (
#  `id` int(10) NOT NULL AUTO_INCREMENT,
#  `name` varchar(255) NOT NULL,
#  `nickname` varchar(255) NOT NULL,
#  `gender` int(1) NOT NULL DEFAULT '0',
#  `created` datetime NOT NULL,
#  `modified` datetime NOT NULL,
#  PRIMARY KEY (`id`),
#  UNIQUE KEY `user_name_idx` (`id`,`name`)
# ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;


#---------------------
# SQLカタログ
#---------------------
my $sql_catalog = {
    sel_all    => 'SELECT name, nickname, gender FROM users',
    sel_gender => 'SELECT name, nickname FROM users WHERE gender = ?',
};
#---------------------
# SQLカタログを使ったテスト
#---------------------
sub test_sql_catalog {
    my ($dbh, $gender) = @_;

    say "call test_sql_catalog gender=${gender}";

    my $sth = $dbh->prepare($sql_catalog->{sel_gender});
    $sth->execute($gender);

    my ($name, $nickname);
    $sth->bind_columns(\($name, $nickname));
    while ($sth->fetchrow_arrayref()) {
        say "name:[${name}] nickname:[${nickname}]";
    }
    say "end test";
    say "";
}

#---------------------
# SQL::Abstractを使ったテスト
#---------------------
sub test_sql_abstract {
    my ($dbh, $gender) = @_;

    say "call test_sql_abstract gender=${gender}";

    my $sqla = SQL::Abstract->new();
    my ($sql, @bind) = $sqla->select (
        'users',
        ['name', 'nickname'],
        {
            gender => $gender,
        }
    );

    my $sth = $dbh->prepare($sql);
    $sth->execute(@bind);

    my ($name, $nickname);
    $sth->bind_columns(\($name, $nickname));
    while ($sth->fetchrow_arrayref()) {
        say "name:[${name}] nickname:[${nickname}]";
    }

    say "end test";
    say "";
}

#---------------------
# DBIx::Classを使ったテスト
#---------------------
sub test_dbix_class {
    my ($dbh, $gender) = @_;

    say "call test_dbix_class gender=${gender}";

    my $schema = Users::Schema->connect($dbs, $dbuser, $dbpass);
    my $users = $schema->resultset('User')->search(
        {gender => $gender}
    );
    while (my $user = $users->next) {
        say "name:[".$user->name."] nickname:[".$user->nickname."]";
    }

    say "end test";
    say "";
}


#---------------------
# メイン
#---------------------
sub main {
    my $dbh = DBI->connect($dbs, $dbuser, $dbpass) || &dbErr("Database can't connect.".$DBI::errstr);

    test_sql_catalog($dbh, 0);
    test_sql_catalog($dbh, 1);

    test_sql_abstract($dbh, 0);
    test_sql_abstract($dbh, 1);

    test_dbix_class($dbh, 0);
    test_dbix_class($dbh, 1);

    $dbh->disconnect();
}

main();

出力結果

call test_sql_catalog gender=0
name:[男前二郎] nickname:[大ブタ]
name:[田中太郎] nickname:[ナカタ]
end test

call test_sql_catalog gender=1
name:[山田花子] nickname:[はなちゃん]
name:[咲山咲子] nickname:[さっちゃん]
end test

call test_sql_abstract gender=0
name:[男前二郎] nickname:[大ブタ]
name:[田中太郎] nickname:[ナカタ]
end test

call test_sql_abstract gender=1
name:[山田花子] nickname:[はなちゃん]
name:[咲山咲子] nickname:[さっちゃん]
end test

call test_dbix_class gender=0
name:[男前二郎] nickname:[大ブタ]
name:[田中太郎] nickname:[ナカタ]
end test

call test_dbix_class gender=1
name:[山田花子] nickname:[はなちゃん]
name:[咲山咲子] nickname:[さっちゃん]
end test

Leave a Comment