前回の
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
は真値に設定しましょう。
<続きの