接続¶
接続は、データベースを操作する際の重要な要素の一つです。これは、アプリケーションからデータベースへのステートメントの経路を意味します。
Crystalでは、この接続を構築する2つの方法があります。次に、それぞれの使用に関するアドバイスとともに、例を示します。
DBモジュール¶
私に立つ場所を与えよ、されば地球を動かさん。 アルキメデス
DBモジュールは、Crystalでデータベースを操作する際の私たちの立つ場所です。ドキュメントに書かれているように:データベースアクセスのための統合インターフェースです。
このモジュールに実装されているメソッドの1つはDB#connect
です。このメソッドを使用することが、接続を作成する最初の方法です。使用方法を見てみましょう。
DB#connect¶
DB#connect
を使用する場合、実際にデータベースへの接続を開きます。引数として渡されたuri
は、モジュールがどのドライバーを使用するかを決定するために使用されます(例:mysql://
、postgres://
、sqlite://
など)。つまり、どのデータベースを使用しているかを指定する必要はありません。
この例のuri
はmysql://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_mode をWAL
に設定できます。 - 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 データベースに接続する例を見ることができます。