Skip to content

Ruby 数据库访问

本教程将指导您如何使用 Ruby 访问数据库。虽然存在 Ruby/DBI 等较旧的库,但现代 Ruby 应用程序通常使用针对每个数据库系统的特定适配器 gem(例如,用于 PostgreSQL 的 pg、用于 MySQL 的 mysql2、用于 SQLite 的 sqlite3)。这些 gem 提供了数据库的直接接口。

对于更复杂的应用程序,通常使用对象关系映射(ORM),例如 ActiveRecord(Ruby on Rails 的一部分)或 Sequel。ORM 在 SQL 之上提供更高级别的抽象。本教程将重点介绍如何使用直接适配器 gem sqlite3,因为其简单易于设置(无需独立的数据库服务器)。

支持的数据库 (通过 Gems):

  • SQLite (通过 sqlite3 gem)
  • PostgreSQL (通过 pg gem)
  • MySQL/MariaDB (通过 mysql2 gem)
  • Oracle (通过 ruby-oci8 gem)
  • SQL Server (通过 tiny_tds gem)
  • 以及通过特定 gem 支持许多其他数据库。

使用适配器 gem 时,您的 Ruby 脚本通过 gem 的 API 直接与数据库通信。gem 负责处理特定数据库的低级通信协议。

Ruby 应用程序 -> 适配器 Gem (例如 sqlite3) -> 数据库 (例如 SQLite 文件)

要遵循本教程,您需要安装 Ruby 和 sqlite3 gem。您可能还需要在系统上安装 SQLite3 开发库,以便 gem 能够编译。

使用命令行安装 gem:

gem install sqlite3

首先,require (引入) gem,然后创建数据库连接。对于 SQLite,这涉及创建或打开数据库文件。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
# 打开数据库。如果 'test.db' 不存在,将会创建它。
db = SQLite3::Database.new 'test.db'
db.results_as_hash = true # 将结果作为哈希而不是数组返回
puts "Opened database successfully"
# 示例: 获取 SQLite 版本
version = db.get_first_value 'SELECT SQLITE_VERSION()'
puts "SQLite version: #{version}"
rescue SQLite3::Exception => e
puts "Database error: #{e.message}"
puts "Exception class: #{e.class}"
ensure
# 如果数据库连接已打开,始终关闭它
db.close if db
end

如果连接建立成功,将返回一个 SQLite3::Database 对象。ensure 块确保调用 db.close,从而释放资源。

execute 方法用于运行不返回行的 SQL 语句,例如 CREATE TABLE。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
db = SQLite3::Database.new 'company.db'
db.execute "DROP TABLE IF EXISTS employees"
db.execute <<~SQL
CREATE TABLE employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT NOT NULL,
last_name TEXT,
age INTEGER,
position TEXT,
salary REAL
);
SQL
puts "表 'employees' 已成功创建。"
rescue SQLite3::Exception => e
puts "创建表时出错: #{e}"
ensure
db.close if db
end

要插入数据,请使用 execute 方法和 INSERT 语句。至关重要的是要使用参数化查询(占位符,例如 ?)来防止 SQL 注入漏洞。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
db = SQLite3::Database.new 'company.db'
# 如果表不存在则创建 (假定之前的脚本已运行或在此处添加创建代码)
# db.execute "CREATE TABLE IF NOT EXISTS employees (...);"
# 使用占位符插入单条记录
db.execute("INSERT INTO employees (first_name, last_name, age, position, salary) VALUES (?, ?, ?, ?, ?)",
'John', 'Doe', 30, 'Developer', 60000.0)
# 插入多条记录
employees_data = [
['Jane', 'Smith', 28, 'Designer', 55000.0],
['Mike', 'Johnson', 35, 'Manager', 75000.0]
]
employees_data.each do |emp|
db.execute("INSERT INTO employees (first_name, last_name, age, position, salary) VALUES (?, ?, ?, ?, ?)",
emp[0], emp[1], emp[2], emp[3], emp[4])
end
puts "已插入 #{db.changes} 条记录。"
# 注意: db.changes 返回最近执行的 DML 语句 (INSERT, UPDATE, DELETE) 影响的行数
# 对于循环中插入的总行数,您需要对它们进行求和或单独计数。
puts "记录已成功创建"
rescue SQLite3::Exception => e
puts "插入记录时出错: #{e}"
ensure
db.close if db
end

要获取数据,请使用 execute 方法和 SELECT 语句。如果设置了 db.results_as_hash = true,则每行是一个哈希。否则,它是一个数组。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
db = SQLite3::Database.new 'company.db'
db.results_as_hash = true
puts "薪水高于 58000 的员工:"
# 使用占位符作为查询参数
min_salary = 58000.0
db.execute("SELECT id, first_name, last_name, position, salary FROM employees WHERE salary > ?", min_salary) do |row|
puts "ID: #{row['id']}, Name: #{row['first_name']} #{row['last_name']}, Position: #{row['position']}, Salary: #{row['salary']}"
end
# 获取单个值
count = db.get_first_value "SELECT COUNT(*) FROM employees"
puts "\n总员工数: #{count}"
rescue SQLite3::Exception => e
puts "读取记录时出错: #{e}"
ensure
db.close if db
end

使用 execute 方法和 UPDATE 语句以及占位符。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
db = SQLite3::Database.new 'company.db'
# 给 John Doe 加薪
new_salary = 65000.0
target_id = 1 # 假定 John Doe 的 ID 是 1
db.execute("UPDATE employees SET salary = ? WHERE id = ?", new_salary, target_id)
puts "已更新 #{db.changes} 条记录。"
rescue SQLite3::Exception => e
puts "更新记录时出错: #{e}"
ensure
db.close if db
end

使用 execute 方法和 DELETE 语句以及占位符。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
begin
db = SQLite3::Database.new 'company.db'
# 按 ID 删除员工 (例如,ID 为 2 的员工)
employee_id_to_delete = 2
db.execute("DELETE FROM employees WHERE id = ?", employee_id_to_delete)
puts "已删除 #{db.changes} 条记录。"
rescue SQLite3::Exception => e
puts "删除记录时出错: #{e}"
ensure
db.close if db
end

事务确保一系列数据库操作要么全部成功,要么全部失败,从而维护数据一致性。这些属性包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)(ACID)。

sqlite3 gem 提供了一个 transaction 块方法,这是处理事务的推荐方式。它在成功时自动处理 COMMIT,在发生异常时自动处理 ROLLBACK。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'sqlite3'
db = SQLite3::Database.new 'company.db'
begin
# 示例: 在部门之间转移虚构的“项目预算” (概念性示例)
# 假定存在一个 'departments' 表,包含 'name' 和 'budget' 列
# db.execute "CREATE TABLE IF NOT EXISTS departments (name TEXT, budget REAL);"
# db.execute "INSERT INTO departments VALUES ('Sales', 10000);"
# db.execute "INSERT INTO departments VALUES ('Marketing', 5000);"
db.transaction do
# 此块在事务中执行
puts "正在开始事务..."
# 减少销售预算
db.execute("UPDATE employees SET salary = salary - 1000 WHERE first_name = ?", 'John')
puts "已减少 John 的薪水。"
# 增加市场预算 (此处模拟错误)
# 如果此行引发错误,整个事务将被回滚。
# db.execute("UPDATE employees SET salary = salary + 1000 WHERE non_existent_column = ?", 'Jane') # 有意制造的错误
db.execute("UPDATE employees SET salary = salary + 1000 WHERE first_name = ?", 'Jane')
puts "已增加 Jane 的薪水。"
puts "事务操作已在块内完成。"
# 如果块在没有异常的情况下完成,则会自动进行 COMMIT。
end
puts "事务已成功提交。"
rescue SQLite3::Exception => e
puts "事务失败并已回滚: #{e.message}"
rescue StandardError => e
puts "事务期间发生意外错误: #{e.message}"
ensure
db.close if db
end

您也可以使用 db.execute 'BEGIN TRANSACTION'、db.execute 'COMMIT' 和 db.execute 'ROLLBACK' 手动管理事务,但块形式更安全,更符合 Ruby 习惯用法。

本教程简要介绍了使用 sqlite3 gem 的基本数据库操作。对于大型应用程序,可以进一步探索:

  • 其他数据库系统及其特定的 gem(例如用于 PostgreSQL 的 pg、用于 MySQL 的 mysql2)。
  • 像 ActiveRecord 或 Sequel 这样的 ORM,提供更高级的、面向对象的方式与数据库交互。
  • 高级主题,如连接池、数据库迁移和复杂的查询构建。