跳至内容

数据库

要访问关系型数据库,您需要一个专为要使用的数据库服务器设计的碎片。包 crystal-lang/crystal-db 提供了跨不同驱动程序的统一 API。

以下包与 crystal-db 兼容

以及其他一些 更多.

本指南介绍了 crystal-db 的 API,由于 postgres、mysql 和 sqlite 之间的差异,sql 命令可能需要针对具体驱动程序进行调整。

此外,一些驱动程序可能会提供额外的功能,例如 postgres 的 LISTEN/NOTIFY

安装碎片

从上面的列表中选择合适的驱动程序,并将其作为任何碎片添加到应用程序的 shard.yml

无需显式需要 crystal-lang/crystal-db

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

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

打开数据库

DB.open 允许您使用连接 URI 轻松连接到数据库。URI 的模式决定了预期驱动程序。以下示例连接到名为 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 方法打开一个连接到数据库的单个连接,而不是一个连接池。

执行

要执行 sql 语句,您可以使用 Database#exec

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

值可以作为查询参数提供,见下文。

查询

要执行查询并获取结果集,请使用 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 中定义。