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
と比較してみてください。
さて、