コンテンツへスキップ

接続

接続は、データベースを操作する際の重要な要素の一つです。これは、アプリケーションからデータベースへのステートメントの経路を意味します。

Crystalでは、この接続を構築する2つの方法があります。次に、それぞれの使用に関するアドバイスとともに、例を示します。

DBモジュール

私に立つ場所を与えよ、されば地球を動かさん。 アルキメデス

DBモジュールは、Crystalでデータベースを操作する際の私たちの立つ場所です。ドキュメントに書かれているように:データベースアクセスのための統合インターフェースです

このモジュールに実装されているメソッドの1つはDB#connectです。このメソッドを使用することが、接続を作成する最初の方法です。使用方法を見てみましょう。

DB#connect

DB#connectを使用する場合、実際にデータベースへの接続を開きます。引数として渡されたuriは、モジュールがどのドライバーを使用するかを決定するために使用されます(例:mysql://postgres://sqlite://など)。つまり、どのデータベースを使用しているかを指定する必要はありません。

この例のurimysql://root:root@localhost/testであるため、モジュールはMySQLデータベースに接続するためにmysqlドライバーを使用します。

例はこちらです

require "mysql"

cnn = DB.connect("mysql://root:root@localhost/test")
puts typeof(cnn) # => DB::Connection
cnn.close

このメソッドはDB::Connectionオブジェクトを返すことは注目に値します。より具体的には、MySql::Connectionオブジェクトを返しますが、すべてのタイプの接続はポリモーフィックである必要があるため、問題ありません。したがって、今後はDB::Connectionインスタンスを使用して作業し、各データベースエンジンの特定の問題から抽象化するのに役立てます。

接続を手動で作成する場合(ここでやっているように)、このリソースの管理は私たちの責任であるため、使用が終わったら接続を閉じる必要があります。後者に関しては、この小さな詳細が大きなバグの原因になる可能性があります! Crystalは人間のための言語であるため、次のようにブロックを使用して、より安全な方法で接続を手動で作成できます。

require "mysql"

DB.connect "mysql://root:root@localhost/test" do |cnn|
  puts typeof(cnn) # => DB::Connection
end                # the connection will be closed here

さて、接続ができました。使ってみましょう!

require "mysql"

DB.connect "mysql://root:root@localhost/test" do |cnn|
  puts typeof(cnn)                         # => DB::Connection
  puts "Connection closed: #{cnn.closed?}" # => false

  result = cnn.exec("drop table if exists contacts")
  puts result

  result = cnn.exec("create table contacts (name varchar(30), age int)")
  puts result

  cnn.transaction do |tx|
    cnn2 = tx.connection
    puts "Yep, it is the same connection! #{cnn == cnn2}"

    cnn2.exec("insert into contacts values ('Joe', 42)")
    cnn2.exec("insert into contacts values (?, ?)", "Sarah", 43)
  end

  cnn.query_each "select * from contacts" do |rs|
    puts "name: #{rs.read}, age: #{rs.read}"
  end
end

まず、この例ではトランザクションを使用しています(このトピックの詳細については、トランザクションセクションを確認してください)。次に、トランザクションによって提供される接続は、トランザクションが開始される前に使用していた同じ接続であることに注意することが重要です。つまり、プログラムでは常に1つの接続しかありません。最後に、#execおよび#queryメソッドを使用しています。クエリの実行の詳細については、データベースセクションをご覧ください。

接続の作成について理解できたので、作成する2番目の方法DB#openを紹介しましょう。

DB#open

require "mysql"

db = DB.open("mysql://root:root@localhost/test")
puts typeof(db) # DB::Database
db.close

接続と同様に、データベースも不要になったら閉じる必要があります。代わりに、ブロックを使用すると、Crystalがデータベースを閉じるようにできます!

しかし、接続はどこにあるのでしょうか?さて、私たちは接続を求める必要があります。データベースが作成されると、データベースへの接続が用意された接続のプールが作成されます!(接続プールについてもっと読みたいですか? 接続プールセクションで、この興味深いトピックについてすべて読むことができます!)

databaseオブジェクトから接続を使用するにはどうすればよいでしょうか? このために、メソッドDatabase#checkoutを使用してデータベースに接続を要求できます。ただし、これを行うには、Connection#releaseを使用して接続をプールに明示的に返す必要があります。以下に例を示します。

require "mysql"

DB.open "mysql://root:root@localhost/test" do |db|
  cnn = db.checkout
  puts typeof(cnn)

  puts "Connection closed: #{cnn.closed?}" # => false
  cnn.release
  puts "Connection closed: #{cnn.closed?}" # => false
end

databaseから接続を要求および使用する安全な方法(つまり、接続を解放する必要がない)が必要な場合は、Database#using_connectionを使用できます。

require "mysql"

DB.open "mysql://root:root@localhost/test" do |db|
  db.using_connection do |cnn|
    puts typeof(cnn)
    # use cnn
  end
end

次の例では、databaseオブジェクトに接続を自身で管理させるようにします。

require "mysql"

DB.open "mysql://root:root@localhost/test" do |db|
  db.exec("drop table if exists contacts")
  db.exec("create table contacts (name varchar(30), age int)")

  db.transaction do |tx|
    cnn = tx.connection
    cnn.exec("insert into contacts values ('Joe', 42)")
    cnn.exec("insert into contacts values (?, ?)", "Sarah", 43)
  end

  db.query_each "select * from contacts" do |rs|
    puts "name: #{rs.read}, age: #{rs.read}"
  end
end

気づくかもしれませんが、databaseは、#exec / #query / #transactionメソッドに関してconnectionオブジェクトでポリモーフィックです。データベースは接続の使用を担当します。素晴らしい!

どちらを使うべきか?

例を考えると、接続数が関連していることに気づくかもしれません。データベースへのリクエストを開始するユーザーが1人しかいない短い寿命のアプリケーションをプログラミングする場合は、私たち(つまりDB::Connectionオブジェクト)が管理する1つの接続で十分です(パラメーターを受信し、データベースへのリクエストを開始して、最後にユーザーに結果を表示するコマンドラインアプリケーションを考えてください)。一方、多くの同時ユーザーとデータベースへの頻繁なアクセスがあるシステムを構築している場合は、DB::Databaseオブジェクトを使用する必要があります。これは、接続プールを使用することにより、既に準備ができていて使用できる多数の接続を持つことになります(ブートストラップ/初期化時間のペナルティなし)。または、長寿命のアプリケーション(バックグラウンドジョブなど)を構築していると想像してみてください。接続プールは、接続の状態を監視する責任から解放されます。接続はアクティブですか?それとも再接続する必要がありますか?

接続設定

uriを使用して接続を作成する場合、ユーザー、パスワード、ホスト、データベースなどを指定できるだけでなく、各ドライバーが提供するいくつかの接続プール構成とカスタムオプションも指定できます。詳細については、各ドライバーのドキュメントを確認してください。

いくつかの例を挙げると

  • crystal-lang/crystal-sqlite3 では、?journal_mode=WAL を指定することで、journal_modeWAL に設定できます。
  • crystal-lang/crystal-mysql では、?encoding=utf8mb4_unicode_ci を指定することで、照合順序と文字セットを utf8mb4_unicode_ci に設定できます。
  • will/crystal-pg では、?auth_methods=scram-sha-256 を指定することで、認証方式を scram-sha-256 のみに許可できます。

高度な接続設定

場合によっては、uri の柔軟性では十分ではないことがあります。コネクションプールが必要な場合、手動で接続オブジェクトやデータベースオブジェクトを作成できます。各ドライバーは異なるオプションを持っている可能性があるため、このための方法を提供します。

# for a single connection
connection = TheDriver::Connection.new(crystal_db_connection_options, driver_connection_options)

# for a connection pool
db = DB::Database.new(crystal_db_connection_options, crystal_db_pool_options) do
  TheDriver::Connection.new(crystal_db_connection_options, driver_connection_options)
end

crystal-db#181 では、crystal-pg を使用して、接続で使用する基盤となる IO を手動で作成することにより、SSH トンネルを介して Postgres データベースに接続する例を見ることができます。