Ruby データベースアクセス - DBI チュートリアル

この章では、Ruby を使用してデータベースにアクセスする方法を説明します。Ruby DBIDBI モジュールは、Ruby スクリプトに Perl DBI モジュールと同様の、データベースに依存しないインターフェースを提供します。

DBI は Database independent interface の略であり、Ruby におけるデータベース独立インターフェースを意味します。DBI は Ruby コードと基盤となるデータベースの間に抽象レイヤーを提供し、データベースの切り替えを簡単に実現できます。DBI は一連のメソッド、変数、仕様を定義し、データベースに依存しない一貫したデータベースインターフェースを提供します。

DBI は以下と対話できます:

  • ADO (ActiveX Data Objects)
  • DB2
  • Frontbase
  • mSQL
  • MySQL
  • ODBC
  • Oracle
  • OCI8 (Oracle)
  • PostgreSQL
  • Proxy/Server
  • SQLite
  • SQLRelay

DBI アプリケーションアーキテクチャ

DBI は、バックエンドで利用可能なあらゆるデータベースから独立しています。Oracle、MySQL、Informix のいずれを使用している場合でも、DBI を使用できます。次のアーキテクチャ図はこの点を明確に示しています。

Ruby DBI 架构

Ruby DBI の一般的なアーキテクチャは2つのレイヤーを使用します:

  • データベースインターフェース(DBI)レイヤー。このレイヤーはデータベースから独立しており、データベースサーバーの種類に関係なく使用できる一連の共通アクセスメソッドを提供します。
  • データベースドライバ(DBD)レイヤー。このレイヤーはデータベースに依存しており、異なるドライバが異なるデータベースエンジンへのアクセスを提供します。MySQL、PostgreSQL、InterBase、Oracle などではそれぞれ異なるドライバを使用します。各ドライバは DBI レイヤーからのリクエストを解釈し、それらのリクエストを特定の種類のデータベースサーバーに適したリクエストにマッピングする役割を担います。

インストール

MySQL データベースにアクセスする Ruby スクリプトを作成するには、まず Ruby MySQL モジュールをインストールする必要があります。

Mysql 開発パッケージのインストール

# Ubuntu sudo apt-get install mysql-client sudo apt-get install libmysqlclient15-dev # Centos yum install mysql-devel

Mac OS システムでは ~/.bash_profile または ~/.profile ファイルを変更し、次のコードを追加する必要があります:

MYSQL=/usr/local/mysql/bin export PATH=$PATH:$MYSQL export DYLD_LIBRARY_PATH=/usr/local/mysql/lib:$DYLD_LIBRARY_PATH

またはシンボリックリンクを使用します:

sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib

RubyGems を使用した DBI のインストール(推奨)

RubyGems は2003年11月頃に作成され、Ruby 1.9 から Ruby 標準ライブラリの一部となりました。詳細は以下を参照してください:Ruby RubyGems

gem を使用して dbi と dbd-mysql をインストールします:

sudo gem install dbi sudo gem install mysql sudo gem install dbd-mysql

ソースコードからのインストール(Ruby バージョン 1.9 未満の場合はこの方法を使用)

このモジュールは DBD であり、以下からhttp://tmtm.org/downloads/mysql/ruby/ダウンロードできます。

最新のパッケージをダウンロードして解凍し、ディレクトリに入って、以下のコマンドを実行してインストールします:

ruby extconf.rbまたはruby extconf.rb --with-mysql-dir=/usr/local/mysqlまたはruby extconf.rb --with-mysql-config

次にコンパイルします:

make

Ruby/DBI の取得とインストール

以下のリンクから Ruby DBI モジュールをダウンロードしてインストールできます:

https://github.com/erikh/ruby-dbi

インストールを開始する前に、root 権限を持っていることを確認してください。それでは、以下の手順に従ってインストールします:

ステップ 1

git clone https://github.com/erikh/ruby-dbi.git

または zip パッケージを直接ダウンロードして解凍します。

ステップ 2

ディレクトリに移動し、ruby-dbi-masterそのディレクトリ内でsetup.rbスクリプトを使用して構成します。最も一般的な構成コマンドは、config パラメータの後に何も指定しない方法です。このコマンドはデフォルトですべてのドライバをインストールするように構成されます。

ruby setup.rb config

より具体的には、--with オプションを使用して、使用する特定の部分を指定できます。たとえば、主要な DBI モジュールと MySQL DBD レイヤードライバのみを構成する場合は、次のコマンドを入力します:

ruby setup.rb config --with=dbi,dbd_mysql

ステップ 3

最後のステップはドライバをビルドし、以下のコマンドを使用してインストールすることです:

ruby setup.rb setup ruby setup.rb install

データベース接続

MySQL データベースを使用していると仮定します。データベースに接続する前に、以下を確認してください:

  • データベース TESTDB を作成していること。
  • TESTDB にテーブル EMPLOYEE を作成していること。
  • そのテーブルには FIRST_NAME、LAST_NAME、AGE、SEX、INCOME というフィールドがあります。
  • TESTDB にアクセスするためのユーザー ID "testuser" とパスワード "test123" を設定していること。
  • お使いのマシンに Ruby モジュール DBI が正しくインストールされていること。
  • MySQL チュートリアルを読み、MySQL の基本操作を理解していること。

以下は MySQL データベース "TESTDB" に接続する例です:

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") #サーバーのバージョン文字列を取得して表示する row = dbh.select_one("SELECT VERSION()") puts "Server version: " + row[0] rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" ensure #サーバーとの接続を切断する dbh.disconnect if dbh end

このスクリプトを実行すると、Linux マシンでは次の結果が生成されます。

Server version: 5.0.45

接続時にデータソースを指定すると、データベースハンドル(Database Handle)が返され、dbhに保存されて以降の処理で使用されます。そうでない場合、dbhは nil 値に設定され、e.errとe::errstrそれぞれエラーコードとエラー文字列が返されます。

最後に、プログラムを終了する前に、データベース接続を閉じてリソースを解放してください。

INSERT 操作

データベーステーブルにレコードを作成する場合、INSERT 操作を使用する必要があります。

データベース接続が確立されると、doメソッドまたはprepareとexecuteメソッドを使用して、テーブルを作成したり、データベーステーブルに挿入するレコードを作成したりできます。

do 文の使用

行を返さないステートメントは、doデータベース処理メソッドを呼び出すことで実行できます。このメソッドはステートメント文字列を1つの引数として受け取り、そのステートメントによって影響を受けた行数を返します。

dbh.do("DROP TABLE IF EXISTS EMPLOYEE") dbh.do("CREATE TABLE EMPLOYEE ( FIRST_NAME CHAR(20) NOT NULL, LAST_NAME CHAR(20), AGE INT, SEX CHAR(1), INCOME FLOAT )" );

同様に、SQLINSERTステートメントを実行して、EMPLOYEE テーブルにレコードを作成して挿入できます。

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") dbh.do( "INSERT INTO EMPLOYEE(FIRST_NAME, LAST_NAME, AGE, SEX, INCOME) VALUES ('Mac', 'Mohan', 20, 'M', 2000)" ) puts "Record has been created" dbh.commit rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" dbh.rollback ensure #サーバーとの接続を切断する dbh.disconnect if dbh end

使用prepareとexecute

DBI のprepareとexecuteメソッドを使用して、Ruby コード内の SQL ステートメントを実行できます。

レコードを作成する手順は次のとおりです:

  • INSERT ステートメントを含む SQL ステートメントを準備します。これは、prepareメソッドを使用して行われます。
  • SQL クエリを実行し、データベースからすべての結果を取得します。これは、executeメソッドを使用して行われます。
  • ステートメントハンドルを解放します。これは、finishAPI を使用して行われます。
  • すべてがうまくいけば、commitその操作をコミットし、そうでなければrollbackロールバックしてトランザクションを完了できます。

以下はこれら2つのメソッドを使用する構文です:

例

sth = dbh.prepare(statement) sth.execute ... zero or more SQL operations ... sth.finish

これら2つのメソッドは、bindSQL ステートメントに値を渡すために使用できます。入力される値が事前にわからない場合があります。その場合は、バインド値が使用されます。疑問符(?)が実際の値の代わりに使用され、実際の値は execute() API を通じて渡されます。

次の例では、EMPLOYEE テーブルに2つのレコードを作成します:

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") sth = dbh.prepare( "INSERT INTO EMPLOYEE(FIRST_NAME, LAST_NAME, AGE, SEX, INCOME) VALUES (?, ?, ?, ?, ?)" ) sth.execute('John', 'Poul', 25, 'M', 2300) sth.execute('Zara', 'Ali', 17, 'F', 1000) sth.finish dbh.commit puts "Record has been created" rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" dbh.rollback ensure #サーバーとの接続を切断する dbh.disconnect if dbh end

複数の INSERT を同時に使用する場合、1つのステートメントを準備してからループ内でそれを複数回実行する方が、ループ内で毎回 do を呼び出すよりもはるかに効率的です。

READ 操作

あらゆるデータベースに対する READ 操作とは、データベースから有用な情報を取得することです。

データベース接続が確立されると、データベースにクエリを実行する準備ができます。次を使用できます:doメソッドまたはprepareとexecuteメソッドを使用して、データベーステーブルから値を取得できます。

レコードを取得する手順は次のとおりです:

  • 必要な条件に基づいて SQL クエリを準備します。これは、prepareメソッドを使用して完了します。
  • SQL クエリを実行し、データベースからすべての結果を取得します。これは、executeメソッドを使用して完了します。
  • 結果を一つずつ取得し、出力します。これは、fetchメソッドを使用して完了します。
  • ステートメントハンドルを解放します。これは、finishメソッドを使用して完了します。

次の例では、EMPLOYEE テーブルから給与(salary)が 1000 を超えるすべてのレコードをクエリします。

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") sth = dbh.prepare("SELECT * FROM EMPLOYEE WHERE INCOME > ?") sth.execute(1000) sth.fetch do |row| printf "First Name: %s, Last Name : %s\n", row[0], row[1] printf "Age: %d, Sex : %s\n", row[2], row[3] printf "Salary :%d \n\n", row[4] end sth.finish rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" ensure #サーバーへの接続を切断する dbh.disconnect if dbh end

これにより、次の結果が生成されます:

First Name: Mac, Last Name : Mohan
Age: 20, Sex : M
Salary :2000

First Name: John, Last Name : Poul
Age: 25, Sex : M
Salary :2300

データベースからレコードを取得する方法は他にもたくさんあります。興味があれば、以下を参照してください。Ruby DBI 読み取り操作。

Update 操作

任意のデータベースに対する UPDATE 操作は、データベース内の1つ以上の既存レコードを更新することを指します。次の例では、SEX が 'M' のすべてのレコードを更新します。ここでは、すべての男性の AGE を1歳増やします。これは3つのステップに分けられます:

  • 必要な条件に基づいて SQL クエリを準備します。これは、prepareメソッドを使用して完了します。
  • SQL クエリを実行し、データベースからすべての結果を取得します。これは、executeメソッドを使用して完了します。
  • ステートメントハンドルを解放します。これは、finishメソッドを使用して完了します。
  • すべてが順調に進んだ場合は、commitその操作を実行し、そうでない場合は、rollbackトランザクションを完了します。

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") sth = dbh.prepare("UPDATE EMPLOYEE SET AGE = AGE + 1 WHERE SEX = ?") sth.execute('M') sth.finish dbh.commit rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" dbh.rollback ensure #サーバーへの接続を切断する dbh.disconnect if dbh end

DELETE 操作

データベースからレコードを削除する場合は、DELETE 操作を使用する必要があります。次の例では、EMPLOYEE から AGE が 20 を超えるすべてのレコードを削除します。この操作の手順は次のとおりです。

  • 必要な条件に基づいて SQL クエリを準備します。これは、prepareメソッドを使用して完了します。
  • SQL クエリを実行し、データベースから必要なレコードを削除します。これは、executeメソッドを使用して完了します。
  • ステートメントハンドルを解放します。これは、finishメソッドを使用して完了します。
  • すべてが順調に進んだ場合は、commitその操作を実行し、そうでない場合は、rollbackトランザクションを完了します。

例

#!/usr/bin/ruby -w require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") sth = dbh.prepare("DELETE FROM EMPLOYEE WHERE AGE > ?") sth.execute(20) sth.finish dbh.commit rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" dbh.rollback ensure #サーバーへの接続を切断する dbh.disconnect if dbh end

トランザクションの実行

トランザクションは、取引の一貫性を保証するメカニズムです。トランザクションには次の4つの属性が必要です。

  • 原子性(Atomicity):トランザクションの原子性とは、トランザクションに含まれるプログラムがデータベースの論理作業単位として、データ変更操作をすべて実行するか、まったく実行しないかのどちらかであることを指します。
  • 一貫性(Consistency):トランザクションの一貫性とは、トランザクションの実行前と実行後の両方でデータベースが一貫性のある状態でなければならないことを指します。データベースの状態がすべての整合性制約を満たす場合、そのデータベースは一貫性があると言います。
  • 分離性(Isolation):トランザクションの分離性とは、並行して実行されるトランザクションが互いに分離されていることを指します。つまり、トランザクション内部の操作と操作中のデータはロックされ、他の変更を試みるトランザクションから見えないようにしなければなりません。
  • 永続性(Durability):トランザクションの永続性とは、システムまたはメディアに障害が発生した場合でも、コミット済みトランザクションの更新が失われないことを保証することを意味します。つまり、トランザクションがコミットされると、データベース内のデータへの変更は永久的であり、あらゆるデータベースシステム障害に耐える必要があります。永続性は、データベースのバックアップとリカバリによって保証されます。

DBI はトランザクションを実行する2つの方法を提供します。1つはcommit 或 rollbackトランザクションをコミットまたはロールバックするためのメソッドです。もう1つはtransactionトランザクションの実装に使用できるメソッドです。次に、トランザクションを実装するためのこの2つの簡単な方法を紹介します。

方法 I

最初の方法では、DBI のcommitとrollbackメソッドを使用して、トランザクションを明示的にコミットまたはキャンセルします。

例

dbh['AutoCommit'] = false #自動コミットを false に設定します。 begin dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'John'") dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'Zara'") dbh.commit rescue puts "transaction failed" dbh.rollback end dbh['AutoCommit'] = true

方法 II

2番目の方法では、transactionメソッドを使用します。この方法は、トランザクションを構成するステートメントを含むコードブロックを必要とするため、比較的簡単です。transactionメソッドはブロックを実行し、ブロックが正常に実行されたかどうかに応じて、自動的にcommit 或 rollback:

例

dbh['AutoCommit'] = false #自動コミットを false に設定します dbh.transaction do |dbh| dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'John'") dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'Zara'") end dbh['AutoCommit'] = true

COMMIT 操作

COMMIT は、データベースが変更を完了したことを示す操作です。この操作の後、すべての変更は元に戻せません。

以下は、commitメソッドを呼び出す簡単な例です。

dbh.commit

ROLLBACK 操作

1つまたは複数の変更に満足できない場合、それらの変更を完全に元に戻したい場合は、rollbackメソッドを使用します。

以下は、rollbackメソッドを呼び出す簡単な例です。

dbh.rollback

データベースの切断

データベース接続を切断するには、disconnect API を使用してください。

dbh.disconnect

ユーザーが disconnect メソッドを使用してデータベース接続を閉じた場合、DBI は未完了のトランザクションをすべてロールバックします。ただし、DBI の実装の詳細に依存する必要はなく、アプリケーションは commit または rollback を明示的に呼び出すだけで十分です。

エラー処理

さまざまなエラー原因があります。たとえば、SQL ステートメントの実行中の構文エラー、接続失敗、または既にキャンセルまたは完了したステートメントハンドルに対して fetch メソッドを呼び出すことなどです。

特定の DBI メソッドが失敗すると、DBI は例外をスローします。DBI メソッドは任意のタイプの例外をスローできますが、最も重要な2つの例外クラスはDBI::InterfaceErrorとDBI::DatabaseError。

これらのクラスの Exception オブジェクトにはerr、errstrとstate3つの属性があり、それぞれエラー番号、説明的なエラー文字列、標準エラーコードを表します。属性の詳細は次のとおりです。

  • err:発生したエラーの整数表現を返します。DBD がサポートしていない場合は、nilを返します。たとえば、Oracle DBD はORA-XXXXエラーメッセージの数値部分を返します。
  • errstr:発生したエラーの文字列表現を返します。
  • state:発生したエラーの SQLSTATE コードを返します。SQLSTATE は5文字の長さの文字列です。ほとんどの DBD はこれをサポートしていないため、nil を返します。

上記の例で、次のコードをすでに確認しました。

rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" dbh.rollback ensure #サーバーへの接続を切断する dbh.disconnect if dbh end

スクリプト実行時のスクリプト実行内容に関するデバッグ情報を取得するには、トレースを有効にできます。そのためには、最初に dbi/trace モジュールをダウンロードし、トレースモードと出力先を制御するtraceメソッドを呼び出す必要があります。

require "dbi/trace" .............. trace(mode, destination)

mode の値は 0(オフ)、1、2、または 3 にできます。destination の値は IO オブジェクトである必要があります。デフォルト値はそれぞれ 2 と STDERR です。

メソッドのコードブロック

ハンドルを作成するメソッドがいくつかあります。 これらのメソッドはコードブロックとともに呼び出されます。 メソッドでコードブロックを使用する利点は、コードブロックにハンドルを引数として提供し、ブロックが終了するとハンドルを自動的にクリアすることです。 以下に、この概念を理解するのに役立つ例をいくつか示します。

  • DBI.connect :このメソッドはデータベースハンドルを生成します。ブロックの最後でdisconnectを呼び出してデータベースを切断することをお勧めします。
  • dbh.prepare :このメソッドはステートメントハンドルを生成します。ブロックの最後でfinishを呼び出すことをお勧めします。ブロック内では、executeメソッドを呼び出してステートメントを実行する必要があります。
  • dbh.execute :このメソッドは dbh.prepare と似ていますが、dbh.execute ではブロック内で execute メソッドを呼び出す必要はありません。ステートメントハンドルが自動的に実行されます。

例 1

DBI.connectコードブロックを指定でき、データベースハンドルが渡され、ブロックの最後でハンドルが自動的に切断されます。

dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", 
                  "testuser", "test123") do |dbh|

例 2

dbh.prepareコードブロックを指定でき、ステートメントハンドルが渡され、ブロックの最後で finish が自動的に呼び出されます。

dbh.prepare("SHOW DATABASES") do |sth| sth.execute puts "Databases: " + sth.fetch_all.join(", ") end

例 3

dbh.executeコードブロックを指定でき、ステートメントハンドルが渡され、ブロックの最後で finish が自動的に呼び出されます。

dbh.execute("SHOW DATABASES") do |sth| puts "Databases: " + sth.fetch_all.join(", ") end

DBI transactionメソッドもコードブロックを指定できます。これについては、前述の章で説明しました。

特定ドライバの関数と属性

DBI では、データベースドライバーが追加のデータベース固有関数を提供できます。これらの関数は、ユーザーが任意の Handle オブジェクトのfuncメソッドを通じて呼び出すことができます。

を使用して[]= or []メソッドで、特定のドライバーの属性を設定または取得できます。

DBD::Mysql は、次のドライバー固有の関数を実装しています。

番号関数と説明
1dbh.func(:createdb, db_name)
新しいデータベースを作成します。
2dbh.func(:dropdb, db_name)
データベースを削除します。
3dbh.func(:reload)
リロード操作を実行します。
4dbh.func(:shutdown)
サーバーをシャットダウンします。
5dbh.func(:insert_id) => Fixnum
この接続の最新の AUTO_INCREMENT 値を返します。
6dbh.func(:client_info) => String
バージョンに基づいて MySQL クライアント情報を返します。
7dbh.func(:client_version) => Fixnum
バージョンに基づいてクライアント情報を返します。これは :client_info と似ていますが、文字列ではなく fixnum を返します。
8dbh.func(:host_info) => String
ホスト情報を返します。
9dbh.func(:proto_info) => Fixnum
通信に使用するプロトコルを返します。
10dbh.func(:server_info) => String
バージョンに基づいて MySQL サーバー側の情報を返します。
11dbh.func(:stat) => Stringb>
データベースの現在の状態を返します。
12dbh.func(:thread_id) => Fixnum
現在のスレッドの ID を返します。

#!/usr/bin/ruby require "dbi" begin #MySQL サーバーに接続する dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") puts dbh.func(:client_info) puts dbh.func(:client_version) puts dbh.func(:host_info) puts dbh.func(:proto_info) puts dbh.func(:server_info) puts dbh.func(:thread_id) puts dbh.func(:stat) rescue DBI::DatabaseError => e puts "An error occurred" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" ensure dbh.disconnect if dbh end

これにより、次の結果が生成されます:

5.0.45
50045
Localhost via UNIX socket
10
5.0.45
150621
Uptime: 384981  Threads: 1  Questions: 1101078  Slow queries: 4 \
Opens: 324  Flush tables: 1  Open tables: 64  \
Queries per second avg: 2.860