前回の
DBIの使い方
(2)
なお、DBIのバージョン1.DBD::mysqlのバージョン4.
DBIとは何か
DBIは、DBIを利用することを前提にしています。
DBIは各データベース用のドライバモジュールであるDBD系モジュールと組み合わせて利用します。たとえば、DBIでMySQLを扱う場合はDBD::mysqlと組み合わせて利用します。同様にPostgreSQLではDBD::Pg、DBD::SQLiteを利用します。このようにDBIでは、
この性質は、DBIxというDBIのAPIを利用したモジュールを登録するネームスペースが存在します。そして、DBIxネームスペースにはなんと、
libmysqlclientのインストール
今回はMySQLを用いるため、DBD::mysqlを利用します。DBD::mysqlのインストールにはlibmysqlclientが必要です。次のようにインストールします。
$ brew install mysql$ sudo -H apt-get install libmysqlclient-dev※ OSによってはパッケージ名が異なる場合があります
DBI、DBD::mysqlのインストール
続いて、DBIとDBD::mysqlをcpanmでインストールします。
$ cpanm DBI DBD::mysqlモジュールがロードできればインストールに成功しています。
$ perl -MDBI -E 'say $DBI::VERSION'
$ perl -MDBD::mysql -E 'say $DBD::mysql::VERSION'基本的な使い方
DBIは、connectメソッド)、prepareメソッド)、executeメソッド)、fetchメソッド)
データベースへの接続――connect
データベースへ接続するためにはconnectメソッドを使います。connectメソッドは
my $dsn = "dbi:mysql:database=myappdb
;host=myapp-mysql.local;port=3306"; # (1)DSN
my $user = "myapp";
my $pass = q{fQ$aH'dKd#YxC};
my $dbh = DBI->connect($dsn, $user, $pass, {
# (2)DBI属性
AutoCommit => 1,
PrintError => 0,
RaiseError => 1,
ShowErrorStatement => 1,
AutoInactiveDestroy => 1
});接続にはDSN
"dbi:ドライバモジュール名: 属性名1= 属性値1; 属性名2= 属性値2"ドライバモジュール名には、DBD)DBD::mysqlなので、mysqlになります。
属性には、DBD::mysqlの場合は、DBD::mysqlの代表的な属性は表1のとおりです。つまりDBD::mysqlを利用し、myapp-mysql.のMySQLのmyappdbデータベースに接続します。
| 属性名 | 属性値 | 属性値の例 |
|---|---|---|
| database | 利用するMySQLのデータベース | myapp |
| host | 接続先のMySQLサーバのホスト名 | myapp-mysql. |
| port | 接続先のMySQLサーバのポート番号 | 3306 |
| mysql_ | MySQL上のデータがUTF-8で保存されることを前提に、 | 1 |
※ほかにもさまざまな属性を利用できる。詳しくはドキュメントを参照
また、DBIにもRaiseErrorを覚えておいてください。DBIの各メソッドの失敗時の挙動はRaiseErrorによって異なります。詳しくは後述します。
| 属性名 | 属性値 | 属性値の例 |
|---|---|---|
| AutoCommit | ステートメント単位でコミットする。トランザクションはbegin_ | 1 |
| PrintError | DBIのエラーを標準エラー出力にprintする | 1 |
| RaiseError | DBIのエラーが起きたときにdieする | 1 |
| ShowErrorStatement | DBIのエラーに原因となったSQL文を含める | 1 |
| InactiveDestroy | DBIにはデストラクタで接続を切断する機能があるが、 | 0 |
| AutoInactiveDestroy | fork時に自動的に子プロセスでInactiveDestroyを有効にする | 1 |
なおDBIの属性については、
SQLの準備と実行――prepare、execute
prepareメソッドはSQLを準備し、executeメソッドはSQLの実行に成功すれば真値を返します。失敗時の挙動はRaiseError属性に準じます。
my $sth = $dbh->prepare(
'INSERT chat (room, user, msg) VALUES (?, ?, ?)'
);
$sth->execute("room1", "karupanerura", "hello");
$sth->finish;ステートメントハンドラは再利用できます。再利用しない場合は明示的にfinishメソッドを呼び出しましょう。
my $sth = $dbh->prepare(
'INSERT chat (room, user, msg) VALUES (?, ?, ?)'
);
$sth->execute("room$_", "bot", "hello") for 1..10;
$sth->finish;上記のように、DBIでSQLに値を埋め込むにはプレースホルダというsprintfとよく似たしくみを利用します。値を埋め込みたい個所を?としてSQLに記述すると、executeメソッドの引数の値が対応する順序で埋め込まれます。
sprintfと違うのは、sprintfでは、
# 悪意のある入力
my $room = "room1";
my $user = "evil";
my $msg = "');
UPDATE chat SET msg = 'What a weak system!'; --";
# プレースホルダを利用せずに文字列結合で値を埋め込む
my $sth = $dbh->prepare(
"INSERT chat (room, user, msg)
VALUES ('$room', '$user', '$msg')"
);
$sth->execute();このケースでは次のようなSQLが実行されます。
INSERT chat (room, user, msg)
VALUES ('room1', 'evil', '');
UPDATE chat SET msg = 'What a weak system!'; --')意図していない悪意のあるUPDATEが出現しています。もとのSQLの一部はコメントアウトされ、
プレースホルダを利用して値を埋め込めばサニタイズが行われるため、DBIやドライバモジュールにバグがないことが前提になりますが、DBIもDBD::mysqlも枯れているモジュールなので安全性は高いです。少なくとも、
結果の取得――fetch系
ステートメントハンドラを利用してSQLの結果を得るには、fetch系メソッドを利用します。ここではfetchall_メソッドを例に紹介します。fetchall_は結果のすべての値を配列リファレンスとして返します。
my $sth = $dbh->prepare(
'SELECT user, msg FROM chat WHERE room = ?'
);
$sth->execute('room1');
my $rows = $sth->fetchall_arrayref();
# $rows => [
# ["karupanerura", "hello"]
# ["kfly8", "hello"]
# ]第1引数にリファレンスを指定することにより、
my $rows = $sth->fetchall_arrayref(+{});
# $rows => [
# { user => "karupanerura", msg => "hello" },
# { user => "kfly8", msg => "hello" },
# ]ほかにも、DBIのドキュメントを参照してください。
任意のSQLの実行――do
単純にSQLを実行するだけであればdoメソッドが便利です。これはprepare、のショートカットです。SQLの実行に成功すれば真値を返します。失敗時の挙動はRaiseError属性に準じます。第3引数以降にプレースホルダに埋め込む値を渡すことができます。
my $room = "room1";
my $user = "karupanerura";
my $msg = "hello";
$dbh->do(
'INSERT chat (room, user, msg) VALUES (?, ?, ?)', {},
$room, $user, $msg
);トランザクション処理── begin_work、commit、rollback
DBIでトランザクションを扱うにはbegin_メソッドを利用します。基本的にSQLを書く代わりに同様の名前のメソッドを呼ぶイメージですが、AutoCommit属性の状態によって正しい使い方が異なります。
$dbh->begin_work;
$dbh->do(...);
$dbh->do(...);
$dbh->commit;AutoCommit属性が偽値である場合は、begin_されているような状態になります。そのため、begin_を明示的に呼び出すとAlready in atransactionエラーが発生しますcommitかrollbackを明示的に呼び出す必要があります。
AutoCommit属性が真値である場合は、begin_を呼び出さない限りトランザクションとして処理されません。こちらのほうがコンソールから直接MySQLを操作する感覚に近いでしょう。
Webアプリケーションの場合など、AutoCommit属性は真値に設定しておくほうが無難です。
エラーハンドリング
エラーハンドリングの方法はRaiseErrorの値によって異なります。
RaiseErrorが偽値である場合は、undefなどの偽値を返します。エラーは$DBI::errstrに格納されます。
my $ret = $dbh->do(...);
unless ($ret) {
# エラー処理
my $err = $DBI::errstr;
...
}RaiseErrorが真値である場合は、$DBI::errstrでdieします。失敗時にリカバリ処理を行いたい場合、evalで例外を拾うとよいでしょう。
eval {
$dbh->do(...);
};
if (my $err = $@) {
# エラー処理
...
}ちょっとしたスクリプトなどで失敗しても処理を継続したいことが多い場合は、RaiseErrorに偽値を設定したほうが見通しが良くなることがあるかもしれません。しかし、RaiseErrorは真値に設定しましょう。
<続きの