跳至內容

資料庫

若要存取關聯式資料庫,您需要一個專為您想使用的資料庫伺服器設計的 shard。 crystal-lang/crystal-db 套件為不同驅動程式提供統一的 API。

以下套件與 crystal-db 相容

以及其他 更多

本指南介紹 crystal-db 的 API,由於 postgres、mysql 和 sqlite 之間存在差異,因此 sql 命令可能需要針對具體驅動程式進行調整。

此外,某些驅動程式可能會提供額外功能,例如 postgres 的 LISTEN/NOTIFY

安裝 shard

從上面的清單中選擇合適的驅動程式,並將其作為任何 shard 添加到您的應用程式的 shard.yml

無需顯式引入 crystal-lang/crystal-db

在本指南中,將使用 crystal-lang/crystal-mysql

dependencies:
  mysql:
    github: crystal-lang/crystal-mysql

開啟資料庫

DB.open 可讓您輕鬆使用連線 URI 連線到資料庫。URI 的 schema 會決定預期的驅動程式。以下範例連線到名為 test 的本機 mysql 資料庫,使用者為 root,密碼為空白。

require "db"
require "mysql"

DB.open "mysql://root@localhost/test" do |db|
  # ... use db to perform queries
end

其他連線 URI 為

  • sqlite3:///path/to/data.db
  • mysql://user:password@server:port/database
  • postgres://user:password@server:port/database

或者,您可以使用不產生 yield 的 DB.open 方法,只要在結尾呼叫 Database#close 即可。

require "db"
require "mysql"

db = DB.open "mysql://root@localhost/test"
begin
  # ... use db to perform queries
ensure
  db.close
end

或者,您可以使用 DB.connect 方法來開啟到資料庫的單一連線,而不是使用連線池。

Exec

若要執行 sql 陳述式,您可以使用 Database#exec

db.exec "create table contacts (name varchar(30), age int)"
db.exec "insert into contacts (name, age) values ('abc', 30)"

值可以作為查詢參數提供,請參閱下文。

Query

若要執行查詢並取得結果集,請使用 Database#query

Database#query 會回傳一個需要關閉的 ResultSet。如 Database#open 中所述,如果使用區塊呼叫,則 ResultSet 將會隱式關閉。

db.query "select name, age from contacts order by age desc" do |rs|
  rs.each do
    # ... perform for each row in the ResultSet
  end
end

值可以作為查詢參數提供,請參閱下文。

查詢參數

為避免 SQL 注入,值可以作為查詢參數提供。使用查詢參數的語法取決於資料庫驅動程式,因為它們通常只是傳遞到資料庫。MySQL 使用 ? 來展開參數,而賦值是基於引數順序。PostgreSQL 使用 $n,其中 n 是引數的序號(從 1 開始)。

# MySQL
db.exec "insert into contacts values (?, ?)", "John", 30
# Postgres
db.exec "insert into contacts values ($1, $2)", "Sarah", 33
# Queries:
db.query("select name from contacts where age = ?", 33) do |rs|
  rs.each do
    # ... perform for each row in the ResultSet
  end
end

查詢參數在底層會使用預備陳述式(有時會快取),或在客戶端插入,具體取決於驅動程式,但始終會避免 SQL 注入。

如果您想手動使用預備陳述式,可以使用 build 方法

# MySQL
prepared_statement = db.build("select * from contacts where id=?") # Use "... where id=$1" for PostgreSQL
# Use prepared statement:
prepared_statement.query(3) do |rs|
  # ... use rs
end
prepared_statement.query(4) do |rs|
  # ... use rs
end
prepared_statement.close

讀取查詢結果

從資料庫讀取值時,沒有 crystal 可以在編譯時期使用的型別資訊。您需要使用您期望從資料庫取得的型別 T 呼叫 rs.read(T)

db.query "select name, age from contacts order by age desc" do |rs|
  rs.each do
    name = rs.read(String)
    age = rs.read(Int32)
    puts "#{name} (#{age})"
    # => Sarah (33)
    # => John Doe (30)
  end
end

#query 之上建置了許多方便的查詢方法,使其更容易。

您可以一次讀取多個欄位

name, age = rs.read(String, Int32)

或讀取單一列

name, age = db.query_one "select name, age from contacts order by age desc limit 1", as: {String, Int32}

或者讀取純量值,而無需明確處理 ResultSet

max_age = db.scalar "select max(age) from contacts"

還有許多其他輔助方法可以使用型別進行查詢、使用型別查詢欄位名稱等。在資料庫中執行陳述式的所有可用方法都定義在 DB::QueryMethods 中。