使用 SQLite 数据库访问

数据库的主题是深远在编程世界中,尤其是对 Web 开发人员来说,而且有充分的理由。数据库非常适合存储和组织大量信息,而当涉及到稍后使用这些数据时,几乎没有比数据库更好的选择了。

例如,如果您正在编写“笔记”应用,您应该理想上将用户的各条笔记存储在数据库中。当然,您也可以将每条笔记作为单独的文本文件进行存储,但是这样一来,您可能需要分别使用大量文件,而不是使用一个合并的数据库文件。一个更大的障碍是数据库世界中常见的分类和搜索等任务几乎不可能实现。

在本教程中,我们将逐步讲解创建数据库、将数据库保存到文件中、存储信息以及检索数据以在 Corona 应用中使用。

JSON 怎么样?

熟悉 JSON 的开发者知道它非常适合轻松存储类似类型的信息,因为 Corona 中解码后的 JSON 字符串将返回为 Lua 表。那么您为什么使用 JSON 而不使用数据库或反之亦然?这两者都是强大的工具,但它们有关键的区别,您应该根据应用的需求来利用这些区别,换句话说,让最佳工具来发挥作用!

在决定使用哪个工具时的基本经验原则是,如果数据量很大,尤其是需要搜索或分类数据时,数据库每次都是最优选择。然而,对于较小的数据集(例如存储配置数据的表),JSON 因其简单性而胜出。

有关以 JSON 格式保存和加载数据的更多详细信息,请参阅 使用 JSON 保存/加载表 教程。

创建数据库

您可以通过两种方式创建 SQLite 数据库

  1. 创建内存中数据库,其使用寿命仅限于应用的运行时间。
  2. 创建数据库文件,该文件可以随时存储和访问。

由于您几乎肯定想要存储数据以便将来访问,因此本教程将仅讨论第二种方法。

此示例展示了如何打开已保存的数据库文件和/或创建尚未存在的数据库文件

-- Require the SQLite library
local sqlite3 = require( "sqlite3" )

-- Create a file path for the database file "data.db"
local path = system.pathForFile( "data.db", system.DocumentsDirectory )

-- Open the database for access
local db = sqlite3.open( path )

请注意,创建数据库的建议位置是 system.DocumentsDirectory,如下例所示。无法写入您的项目资源目录,而临时/缓存目录会定期被操作系统清除,因此使用文档目录将确保您能够读写数据库,并且该数据库驻留在安全、持久的位置。

创建表

使用 SQLite 数据库时你将听到的一些常用术语包括(不是 Lua 表,而是 SQL 表)、。从根本上讲,SQL 可以看作是数据的“类别”。进而,每个表都可以有许多,这些列可以看作是这个表的“属性”,例如,UserIDFirstNameLastName 等。最后,插入表中的各个“记录”称为

行——更具体地说它们的属性——是你最常要处理的实际数据,但在我们可以添加行之前,我们必须设置一个具有特定列的表

local tableSetup = [[CREATE TABLE IF NOT EXISTS test ( UserID INTEGER PRIMARY KEY autoincrement, FirstName, LastName );]]
db:exec( tableSetup )

在上面的代码中,tableSetup 是一个字符串,代表一个SQL 查询——从根本上讲,一个告诉数据库该做什么的命令。在本例中,我们将创建一个名为 test并添加三个

  1. UserID
  2. FirstName
  3. LastName

然后,我们只需对上述中创建的数据库对象 (db) “执行”此查询。

重要
  • 表中的第一列通常是设置为自增 主键的“ID”列。这是几乎每个数据库表中极其重要的列,因为它为你提供了数据库中每个的永久的、唯一的识别值。此识别值允许你访问特定行,更新该行的属性甚至在必要时删除整行。从本质上讲,你应该始终设置一个自增主键,除非你有一个非常明确的理由不这样做。

  • 请注意,查询字符串是用双中括号包裹的([[]])而不是引号。这是因为在 SQL 查询中可以同时使用单引号和双引号,因此使用中括号是最安全的选择。

创建新行

通过 INSERT 语句完成创建新行。首先,我们展示基本用法,然后我们将介绍一个更动态的示例。

local insertQuery = [[INSERT INTO test VALUES ( NULL, "John", "Smith" );]]
db:exec( insertQuery )

此示例相当简单

记住,由于 autoincrement 标志,UserID自增到下一个数字——这就是我们能够将 NULL 作为该列值(而不是一个实际数字)传递的原因。

Lua 表到 SQL 表

现在,让我们发挥创造力,举一个更动态的示例。下面的代码将根据从 Lua 表中提取的值向 SQL 表 (test) 中插入三行(这假设你已经创建了数据库和 test 表)。

local people = {
    {
        FirstName = "John",
        LastName = "Smith",
    },
    {
        FirstName = "James",
        LastName = "Nelson",
    },
    {
        FirstName = "Tricia",
        LastName = "Cole",
    },
}

for i = 1,#people do
    local q = [[INSERT INTO test VALUES ( NULL, "]] .. people[i].FirstName .. [[","]] .. people[i].LastName .. [[" );]]
    db:exec( q )
end

更新现有行

你并不总需要创建新行——事实上,你经常需要更新已经存在的一行。在下面的示例中,我们假设前面的示例中的三行已插入到 test 表中。

local q = [[UPDATE test SET FirstName="Trisha" WHERE UserID=3;]]
db:exec( q )

从本质上讲,此查询查找其中 UserID(主键)等于 3 的行,并将 FirstName 值更改为 Trisha。虽然你不一定要使用主键列来查找行,但这通常是查找特定行的最简单方法,因为它将始终是唯一的。

删除行

用于删除行的 SQL 查询看起来与我们用于更新行的查询非常相似,主要区别在于使用了DELETE FROM而不是 UPDATE。下面的示例从 test 表中删除John Smith

local q = [[DELETE FROM test WHERE UserID=1;]]
db:exec( q )

检索数据

有多种方法可以从 SQL 数据库中检索数据。有时,您只需要一行特定行,而另一些时候您可能需要特定表中的所有行。在其他情况下,为了稍微缩小范围,您可能只想根据特定条件获取某个表中一部分行。所有这些(以及更多)都可以使用 SQLite 实现!

以下示例说明如何从文件中加载现有数据库,并从特定查询的行中填充 Lua 数组。这假定data.db包含我们的三人表(test

-- Require the SQLite library
local sqlite3 = require( "sqlite3" )

-- Create a file path for the database file "data.db"
local path = system.pathForFile( "data.db", system.DocumentsDirectory )

-- Open the database for access
local db = sqlite3.open( path )

-- Create empty "people" table
local people = {}

-- Loop through database table rows via a SELECT query
for row in db:nrows( "SELECT * FROM test" ) do

    print( "Row:", row.UserID )

    -- Create sub-table at next available index of "people" table
    people[#people+1] =
    {
        FirstName = row.FirstName,
        LastName = row.LastName
    }
end

关注的最重点是第 14 行,我们在其中执行 SQL SELECT 语句,并通过 nrows() 方法返回一个迭代器,以便与 for 循环结合使用,以便轻松遍历所有找到的行。在此示例中,我们只需将数据复制到 people 数组中,以便稍后在应用程序中使用它。

有关使用 SELECT 命令筛选数据的详细信息,请阅读这篇文章

关闭数据库

完成对数据库的访问时,非常务必通过在数据库对象上调用 close() 方法(例如 db:close())关闭与数据库的“连接”。

if ( db and db:isopen() ) then
    db:close()
end

当然,此代码必须存在于您要关闭的数据库对象的正确作用域中,即上述示例中的 db 对象,以便 Lua 了解它应关闭哪个数据库。

结论

这总结了 Corona 中数据库访问的基本知识。不过,我们仅介绍了 SQLite 所能实现的皮毛,因此我们鼓励您进一步探索以发现更多可能性,从我们的 SQLite 文档 开始。