Tengの使い方
DBIを使ってどのようにデータベースを操作できるかを解説しました。DBIはそのままでも十分に高機能ですが、
O/
(3)Tengの機能とその基本的な使い方を解説します。なお、Tengのバージョン0.
Tengとは何か
Tengは非常にシンプルなO/DBIx::ClassなどのほかのO/
Tengのインストール
cpanmでインストールします。
$ cpanm Tengモジュールがロードできればインストールに成功しています。
$ perl -MTeng -E 'say $Teng::VERSION'基本的な使い方
本項では、Tengを使ってデータベースプログラミングを行う基本的な手順を解説します。
Tengを継承したクラスの用意
Tengを利用するためには、Tengを継承したクラスを用意する必要があります。今回はMyApp::DBに用意します。次のようにただ継承するだけです。
package MyApp::DB;
use parent qw/Teng/;
1;スキーマ情報の定義
スキーマ情報とは、
O/Tengも例外ではなく、TengではTeng::Schema::LoaderとTeng::Schema::Declareの2つの方法でスキーマ情報を得ることができます。
Teng::Schema::Loaderを利用すると、Tengはデータベースサーバから自動的にスキーマ情報を取得して利用します。細かいスキーマ情報の指定はできませんが、
Teng::Schema::Declareを利用すると、inflate/機能Tengのスキーマ定義から削るなど、
また、Teng::Schema::Dumperを利用すると、Teng::Schema::Declareを利用したスキーマクラスのソースコードを生成できます。基本的にはTeng::Schema::Dumperを利用してスキーマクラスを生成するとよいでしょう。
今回はTeng::Schema::Loaderを利用してみましょう。次のようにして利用できます。
use DBI;
use MyApp::DB;
use Teng::Schema::Loader;
my $dbh = DBI->connect(...);
my $teng = Teng::Schema::Loader->load(
dbh => $dbh,
namespace => 'MyApp::DB'
);データベースへの接続――connect_info
Teng::Schema::Loaderを利用する場合、DBIで一度接続してしまえば接続のためにそれ以上の処理は必要ありません。
Teng::Schema::Declareを利用する場合は次のようにして接続できます。
use MyApp::DB;
use MyApp::DB::Schema;
my $teng = MyApp::DB->new({
connect_info => [$dsn, $user, $pass, $attr],
schema_class => 'MyApp::DB::Schema',
});connect_にDBIのconnectメソッドに渡すべき値を渡すことにより、schema_にはTeng::Schema::Declareで定義したスキーマクラスを指定します。
DBIのデータベースハンドラの取得――dbh
Tengにはトランザクションの状態管理も考慮した、dbhメソッドにより、DBIのメソッドを直接利用したい場合などはdbhメソッドを利用するとよいでしょう。
$teng->dbh->prepare(...);単一行の取得――single
単一の行を取得したい場合はsingleメソッドを利用します。Tengはクエリビルダとして標準でSQL::Makerを利用しているので、SQL::Makerのフォーマットで指定を行います。詳しくはSQL::Makerのドキュメントを参照してください。
my $row = $teng->single(chat => {
# WHERE
room => 'room1',
user => 'karupanerura',
}, {
# ORDER BY、LIMIT
order_by => { created_at => 'DESC' },
limit => 1,
});
# Row オブジェクトからカラムの値が得られる
say $row->room;なお、singleメソッドでは暗黙的にLIMITが1であるものとしてSQLが生成されますが、LIMITを明示したほうがよいでしょう。
複数行の取得――search
複数の行を取得したい場合はsearchメソッドを利用します。
my $iter = $teng->search(chat => {
room => 'room1',
}, {
order_by => { created_at => 'DESC' },
});
my @rows = $iter->all;戻り値としてTeng::Iteratorのオブジェクトが得られます。リストコンテキストで戻り値を評価した場合は、Teng::Iteratorのallメソッドが暗黙的に呼び出されRowオブジェクトの配列が返ります。
データの更新――insert、update、delete
データを更新する場合はinsert、update、deleteメソッドを利用します。singleメソッドなどと同様にSQL::Makerのフォーマットで引数を渡します。また、
my $row = $teng->insert(chat => {
room => 'room1',
user => 'karupanerura',
msg => 'Hello, Teng!'
});
$teng->update(chat => { msg => '<deleted>' }, {
id => $room->id,
});
$row->update({ msg => '<deleted>' });
$teng->delete(chat => { id => $room->id });
$row->delete();トランザクション処理── txn_scope、commit、rollback
txn_メソッドを利用してトランザクションを利用します。これはDBIx::TransactionManagerの同メソッドへの委譲となっており、commit/を呼び出すことによりトランザクションを反映できます。
また、returnなどでスコープを抜けてしまった場合は暗黙的にrollbackが実行されます。これにより、
さらに、
# トランザクションの開始
my $txn = $teng->txn_scope();
# ロックの獲得
$chat = $chat->refetch({ for_update => 1 });
# NG ワードが含まれていなければROLLBACKして終了
if ($chat->msg !~ /ngword/) {
$txn->rollback;
return;
}
# NG ワードが含まれる投稿に対する処理
$chat->update({ message => '<censored>' });
$user = $user->refetch({ for_update => 1 });
$user->update({ violations => $user->violations + 1 });
# トランザクションをCOMMIT
$txn->commit;エラーハンドリング――handle_error
Tengでは、RaiseError属性に暗黙的に真値がセットされて接続されます。エラーをハンドルするためにはhandle_メソッドをオーバーライドし、Duplicate entryエラーを例外オブジェクトでthrowする例です。
use MyApp::DB::Exception::DuplicateEntry;
sub handle_error {
my $self = shift;
my ($stmt, $bind, $reason) = @_;
if ($reason =~ /Duplicate entry/) {
MyApp::DB::Exception::DuplicateEntry->throw(
message => $reason,
stmt => $stmt,
bind => $bind,
);
}
$self->SUPER::handle_error(@_);
}呼び出しもとではevalなどを利用して例外を捕捉します。たとえば、Try::Liteを利用すると次のようになります。
use Try::Lite;
try {
$teng->insert(user => { ... });
}
'MyApp::DB::Exception::DuplicateEntry' => sub {
# エラー処理
...
};直接SQLを指定する
これまでTengのクエリビルダを利用してSQLを実行する方法を解説してきましたが、Tengでは直接SQLを指定して実行することもできます。
名前ベースでのデータのバインド── search_named
search_メソッドを利用することで、SELECTし、single_メソッドも利用できます。なお、
my $iter = $teng->search_named(
'SELECT user, msg FROM chat WHERE room = :room', { room
=> 'room1' });任意のSQLの実行――do
DBIのdoメソッドと利用方法は同じですが、handle_メソッドでハンドリングできます。
Rowクラスを拡張する
これまでに紹介したコードでは、
独自のRowクラスを定義する
Tengを継承したクラス以下のRow名前空間にテーブル名をCamelCaseで表現したクラスを作成すると、Tengを継承したMyApp::DBでfoo_テーブルのRowクラスを独自定義する場合は、MyApp::DB::Row::FooBarを定義すればよいです。独自に定義したRowクラスではTeng::Rowを継承してください。
package MyApp::DB::Row::FooBar;
use parent qw/Teng::Row/;
1;Rowクラス拡張の勘どころ
Rowクラスは自由に拡張できるため、
たとえば、$chat->msg !~ /ngword/というコードが登場しました。このコードに相当するものをRowクラスにメソッドとして定義することにより、!$chat->has_と書けるようになります。説明的なコードになり、
また、msgメソッドを独自定義して、filtered_など別名のメソッドとして定義してそれを利用するほうがよいでしょう。
まとめ
本稿では、DBIが汎用的な低レベルAPIになっていること、TengなどのO/
さらなるステップアップを目指す人は、DBIx::Sunny、DBIx::Classなど、DBIx::SunnyはDBIに便利なメソッド群などを追加してくれるモジュールです。最近は筆者はDBIの代わりにDBIx::Sunnyを利用することが多いです。また、DBIx::ClassはPerlで最もメジャーなO/Tengと比較してみてください。
さて、