大規模なサイトでは、ORMではSQLレベルのチューニングがしにくいので(Class::DBIで苦労してる)、SQLを直接書くことが多いと思います。その際SQLをいろいろなモジュールに書いてしまうと、あとからALTERやパーティショニングしようとした時に悲劇となります。できるだけ1つのテーブル・データベースのSQLは1つのモジュールにおさめておく事がおすすめです。
DBIx::Sunnyはそのようなモジュールを書く時に使えそうなライブラリで、SQLを管理すると共にクエリ実行時に渡された値のバリデーションもできる様になっています。まだまだ実験中ですが、githubにソースコードあります。
使い方はこんな感じ。まずSQLを書くクラスをDBIx::Sunnyを継承して作ります。query、select_one、select_row、select_allってのがSQLを管理する用の暮らすメソッドとして提供されています。queryは更新系のクエリ用、select_oneは1行目1個目のカラムを取得、select_rowは1行目を取得、select_allは全ての行を取得するメソッドして作成されます。
package MyProj::Data;
use parent 'DBIx::Sunny';
use Mouse::Util::TypeConstraints;
subtype 'Natural'
=> as 'Int'
=> where { $_ > 0 };
subtype 'Uint'
=> as 'Int'
=> where { $_ >= 0 };
no Mouse::Util::TypeConstraints;
__PACKAGE__->query(
'add_fuga',
subject => 'Str',
body => 'Str',
'INSERT INTO fuga (subject,body) VALUES (?,?)'
);
__PACKAGE__->select_one(
'count_fuga',
'SELECT COUNT(*) FROM fuga'
);
__PACKAGE__->select_row(
'get_data',
id => 'Natural',
'SELECT * FROM fuga WHERE id = ?'
);
__PACKAGE__->select_all(
'get_data_list',
offset => { isa => 'Uint', default => 0 },
limit => { isa => 'Uint', default => 10 },
'SELECT * FROM fuga ORDER BY id DESC LIMIT ?,?'
);
1;
4つのクラスメソッドには、作成するメソッド名、バリデーションルール、SQLの順に渡します。バリデーションルールはData::Validatorの形式となります。なので、Mouse::Util::TypeConstraintsで独自の型を作成して、使う事ができます。DSLっぽいインターフェイスもあれば便利そう。
使うときは、別途接続済みのDBIハンドラをSQLのクラスに渡します。
use MyProj::Data;
use DBI;
# RaiseErrorはおすすめ
my $dbh = DBI->connect('dbi:mysql(RaiseError=>1,PrintError=>0):test');
my $db = MyProj::Data->new(
dbh => $dbh,
readonly => 0 # 1にするとqueryメソッドがdieするようになる
);
$result = $db->add_fuga( subject => $subject, body => $body );
ok($result > 0);
# last_insert_idも取れる
my $last_insert_id = $db->last_insert_id;
# カウント
my $count = $db->count_fuga();
# ID指定で1行取得
my $row = $db->get_data( id => 1 );
my $row = $db->get_data( id => 'abc' ); #エラーになる
my $row = $db->get_data( id => 0 ); #エラーになる
# limit, offset付きで取得
my $rows = $db->get_data_list(); LIMIT 0,10
my $rows = $db->get_data_list(limit => 20); LIMIT 0,20
my $rows = $db->get_data_list(offset => 20); LIMIT 20,10
この他に、DBIx::TransactionManagerを利用したトランザクション機能もあります。
基本誰得モジュールなので自分で使ってみつつ、IN (…) とか bulk_insert とかどうしようかなと考え中。ご意見お待ちしています