跳至内容

连接

连接是使用数据库时关键部分之一。它代表了从我们的应用程序到数据库的语句流经的通道

在 Crystal 中,我们有两种建立这种连接的方式。接下来,我们将介绍一些示例,并提供一些关于何时使用每种方式的建议。

DB 模块

给我一个支点,我可以撬起地球。 阿基米德

DB 模块是我们在 Crystal 中使用数据库时的立足点。正如文档中所述:是一个统一的数据库访问接口

此模块中实现的方法之一是 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

首先,在这个示例中,我们使用的是事务(有关此主题的更多信息,请查看 事务 部分)。其次,重要的是要注意,事务提供的连接与我们之前使用过的连接相同,在事务开始之前。也就是说,在我们的程序中始终只有一个连接。最后,我们使用 #exec#query 方法。您可以在 数据库 部分了解更多关于执行查询的信息。

现在我们已经对创建连接有了很好的了解,让我们介绍创建连接的第二种方式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 对象是多态的,它具有一个 connection 对象,用于 #exec / #query / #transaction 方法。数据库负责连接的使用。太棒了!

何时使用哪一个?

从这些示例中,我们可能会注意到连接的数量是相关的。如果我们正在编写一个短生命周期的应用程序,只有一个用户向数据库发出请求,那么一个由我们管理的单一连接(即 DB::Connection 对象)就足够了(想想一个接收参数的命令行应用程序,然后向数据库发出请求,最后将结果显示给用户)。另一方面,如果我们正在构建一个具有多个并发用户并且数据库访问量很大的系统,那么我们应该使用 DB::Database 对象;它使用连接池,将有许多已准备好的连接可以随时使用(没有引导/初始化时的性能损失)。或者想象一下,您正在构建一个长生命周期的应用程序(如后台作业),那么连接池将使您免于监控连接状态的责任:它是否存活,或者是否需要重新连接?

连接配置

使用 uri 创建连接时,我们不仅可以指定用户、密码、主机、数据库等,还可以指定一些连接池配置以及每个驱动程序提供的一些自定义选项。有关更多信息,请查看每个驱动程序的文档。

以下是一些示例

高级连接设置

在某些情况下,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 通过 SSH 隧道连接到 postgres 数据库的示例,方法是手动创建连接将使用的底层 IO