数据库¶
要访问关系型数据库,您需要一个专为要使用的数据库服务器设计的碎片。包 crystal-lang/crystal-db 提供了跨不同驱动程序的统一 API。
以下包与 crystal-db 兼容
- crystal-lang/crystal-sqlite3 用于 sqlite
- crystal-lang/crystal-mysql 用于 mysql & mariadb
- will/crystal-pg 用于 postgres
以及其他一些 更多.
本指南介绍了 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
中定义。