S60平台:使用DBMS APIs —DBMS的管理结构和元素

本文档介绍了在Symbian系统上使用关系型数据库APIs的方法,包括数据库的创建、管理和数据操作等关键技术。涵盖了数据库结构定义、数据类型、索引创建、查询计划以及数据的增删改查等方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、绪论
这篇文档的目的是演示在Symbian 系统上如何使用关系型数据库的APIs。总之,这部分功能被看作数据库管理系统的APIs。
Symbian系统的DBMS提供了创建与维持数据库的功能,通过本地调用和SQL调用,其可以对数据库进行安全可靠的数据访问。这种调用支持事务/回滚机制用以保证所有数据被写入或数据为空的情形获得支持。
文档提供的代码片段出自 S60 Platform: DBMS Example [1],用以演示创建和操作一个简单图书数据库的关键技术。贯穿整个文档的代码片段都源自样例的重要部分或自样例引申而来。
虽然文档主要描述了S60平台对DBMS APIs的支持,但这种支持在其他大多数Symbian系统设备上也适用。

2、DBMS的管理结构和元素
Symbian操作系统的DBMS为关系型数据库提供了一个功能性接口。任何Symbian系统的DBMS都采用层次性的结构—最底层是保存数据库的文件仓库。DBMS中,表是行的容器,由列来定义。列通过数据类型与数据长度来定义,列的组合就是行集。DBMS支持丰富的数据类型。
对开发人员来说,他们过去常常在类似ISAM的无DBMS的数据库环境中工作,在该类环境中,行可被视为记录,行可被视为域。然而,必须要指出的是,在有DBMS的数据环境中,行和列还拥有其他属性,其并非简单地与记录和域相联系。
Symbian操作系统的DBMS是一个功能强大,或为轻量集的数据库关系系统实现。它支持普通的添加/查找/检索/更新/删除功能,同时它还支持基本的结构化查询语言(Structured Query Language:SQL),数据定义语言(Data Definition Language:DDL)和数据建模语言(Data Modeling Language:DML)语句处理。
对于其他高级关系型数据库管理(Relational Database Management System)功能, 例如SQL集合与触发器事件,Symbian 系统的DBMS并不支持。

2.1 永久文件存储系统和流
Symbian 系统的DBMS依赖于文件服务器提供的资源,Symbian系统所的永久文件存储和流能提供系统所需要的底层存储功能。在Symbian 系统中,文件存储和流的联系很紧密。
需要指出的是,除了与DBMS相关联的情形,文档并不依赖于文件存储和流。关于文件服务器上,文件存储与流APIs的详细信息可以在Symbian主站上的S60 SDK文档中找到。
概括地说,文件存储是在“磁盘”上的一个文件,文件存储有一个目录列表可视的名字。然而,从过去文件存储的使用执行情况来看,在那种模式下,不论是文件存储的内容还是使用它的目的可能都无法立刻显现出来。
流是使存储系统数据进入与流出过程内部化或具体化的对象。因此,一个存储系统可以被简单地看做是多个流的集合。
一个永久文件存储系统模式使用可独立变更的流存储类型(而不是像其他存储模式那样使用所有流统一变更的方式)并且,对象网络包含结构化的应用程序数据,以便存储系统修改时数据段的装载和写入。数据变更时使用的媒介是RAM。
永久文件系统通过使用CPermanentFileStore 来获得,它是一个源自CFileStore的具体类。这些类在文件存储API中定义,可为DBMS提供组建模块。
大多数操作的文件存储执行过程都由抽象储存架构来定义。更为重要的是,在一个永久文件存储系统中,流可以是:
? ·写入前进行创建
? ·覆盖
? ·替换
? ·删除

2.2 创建数据库
有两种APIs可以用来创建数据库:
? RDbStoreDatabase提供了一个接口用来创建和打开一个专门的数据库,这种数据库的访问方式是非共享的。数据库直接对文件进行操作。因此这种方式也被称为客户端访问。
? RDbNameDatabase提供了一个接口用来创建,格式化和打开一个用名字定义的数据库。这类方式允许双客户端(独占式)和共享式客户端/服务器访问。
不考虑API的选择问题,数据库在文件中创建。在创建阶段,两种APIs都使用RFs文件服务会话以独占模式打开数据库。如果数据库需要被共享,则其必须使用RDbNameDatabase创建和关闭,最终使通过使用服务器会话RDbs的RDbNamedDatabase来打开数据库。
RDbNameDatabase提供了操作数据库的首选方法。如果数据库必须是文件的一部分(例如,一个统一配置文件同时也有其他流),可以使用RDbStoreDatabase,这时,数据库的流ID不能与根文件重名。当文件存储系统的根节点流ID 指向数据库时,使用RDbStoreDatabase创建的数据库可以用RDbNamedDatabase来打开,反之亦然。但是,在这种情况下,客户端/服务器机制无法使用。
下面说明了APIs可以:
? ·创建数据库结构(创建表)
? ·创建索引(es)
当这些步骤完成时,数据库便可以预备接受数据。Symbian 系统DBMS确保了在一次操作中可以:
? ·当入口创建时,将其添加到数据库中
? ·在数据库中保存一份索引的内部拷贝
? ·当一个入口被选择用于显示或编辑时,其数据从数据库中获得并且所有的入口数据都保存在一个内部临时表格中
? ·当入口被编辑和保存时,入口在数据库中被替换。
? ·当应用程序已建立时,由于数据都已保存,除了当前接口被编辑时有可能进行数据操作,其它时候则无需进行操作。
一个数据库应用程序很少替换整个存储系统。数据库文件本身是永久存在的,当入口被保存时,其并不会被覆盖。正由于这样,当采用这种类型的存储系统时应当留意。数据库中的所有链接必须保持无损状态;为了避免空间浪费,数据库中的所有子网流都应可达。
覆盖已有的流不会改变流的长度。然而,试图越过流文件尾进行写操作会失败并导致流的脱离。对一个流进行置换可导致流的大小发生变化。由于流可变可复写,在永久文件系统中,流的写顺序并不重要。为了避免流的问题,应当使用RDbNamedDatabase。

创建一个使用RDbStoreDatabase 的数据库
下列代码段展示了如何使用RDbStoreDatabase创建一个数据库。其创建了一个文件存储对象并在其中构建了一个数据库。
class CBookDb : public CBase
{
...
private: // Member data
RFs iFsSession;
RDbStoreDatabase iBookDb;
CFileStore* iFileStore;
...
};
TInt CBookDb::CreateDb(const TFileName& aNewBookFile)
{
Close();
// Create empty database file.
TRAPD(error,
iFileStore = CPermanentFileStore::ReplaceL(iFsSession,
aNewBookFile, EFileRead|EFileWrite);
iFileStore->SetTypeL(iFileStore->Layout());// Set file store type
TStreamId id = iBookDb.CreateL(iFileStore);// Create stream object
iFileStore->SetRootL(id);// Keep database ID as root of store
iFileStore->CommitL();// Complete creation by committing
// Create Book tables and indexes
CreateBooksTableL();
CreateBooksIndexL();
);

CreateDb创建了一个新的数据库文件。其使用完全文件名(包括路径)的TFileName。例子中,代码段使用同样的名字来覆盖先前的文件。(Replace())。最后,其创建了根的流对象并向系统提交数据库结构。
创建一个使用RDbNamedDatabase的数据库
由于无需使用流,使用RDbNamedDatabse创建一个数据库将会是件轻松的工作。
...
RFs iFsSession;
RDbNamedDatabase iBookDb;
...
TInt CBookDb::CreateDb(const TFileName& aNewBookFile)
{
Close();
TInt error=iBookDb.Replace(iFsSession, aNewBookFile);
if(error!=KErrNone)
{
return error;
}
...// Database is now open. Create tables etc.
Replace方法将创建一个新的数据库文件,若有数据库文件存在的话,其将执行替换操作。它同时将格式化文件使之成为一个空数据库。之后,数据库开启并且等待创建表结构。需要指出的是,当创建数据库时,它所执行的访问模式是唯一的,Replace采用全文件名(包括路径)的TFileName参数。
 在Symbian系统的DBMS中,DBMS 的名字长度限制为64个字符。数据库名字需要与文件名字相一致。由程序员来决定是否采用文件扩展名。
下列命名是有效的:
        _LIT(KDbName,"C:\\system\\apps\\DBMS\\DBMS.dat");
        _LIT(KDbName,"C:\\system\\apps\\DBMS\\DBMS");
在S60第三版中的有效路径和名称如下:
应用程序私有路径:
       _LIT(KDbName,"C:\\Private\\<SID>\\DBMS.dat");
       _LIT(KDbName,"C:\\Private\\<SID>\\DBMS");
公共路径(e.g):
      _LIT(KDbName,"C:\\Data\\<路径元素>\\DBMS.dat");
      _LIT(KDbName,"C:\\Data\\<路径元素>\\DBMS");

2.3 在数据库中定义表
为了在数据库中定义表,开发者需要注意以下三个关键的API概念:列,列集和索引键。
在DBMS中,表名必须唯一;对于表所包含的列,列名必须唯一。表名和列名的确定则无具体限制。

2.3.1 列
数据库中的表由列的集合定义。每一列都有属性,例如:名字,数据类型。如果数据是文本或二进制类型的话,还需要确定最大长度。

2.3.2 列集
用来描述表的列集合压缩存储在CDBColSet中。
TDbColSetter()被用于遍历集合。
下列代码段向我们展示了,在2.1节中创建的永久文件存储站——“永久文件存储站和流”中,如何创建一个数据库表的过程。
...
const int KTitleMaxLength = 60;
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksDescriptionCol, "Description");
...
// Books 表列详细清单
TDbCol authorCol(KBooksAuthorCol, EDbColText); // 缺省长度
TDbCol titleCol(KBooksTitleCol, EDbColText, KTitleMaxLength);
titleCol.iAttributes = TDbCol::ENotNull;
// 流数据
TDbCol descriptionCol(KBooksDescriptionCol, EDbColLongText);
// 创建列集
CDbColSet* bookColSet = CDbColSet::NewLC();
bookColSet->AddL(authorCol);
bookColSet->AddL(titleCol);
bookColSet->AddL(descriptionCol);
// 创建Books表
User::LeaveIfError(iBookDb.CreateTable(KBooksTable,
*bookColSet));
CleanupStack::PopAndDestroy(bookColSet);
...
首先,代码段中使用_LIT宏定义了列名。随后,详细定义了列属性,bookColSet对象被初始化,列随即被添加到集合中。关于文本域的设定,代码段中提供了一个缺省长度样例和一个自定义最大长度的样例。缺省的文本域长度为50。
对titleCol列,成员变量iAttributes赋值为ENotNull,表示当titleCol列属性为空时,无法创建行。
最后,因为bookColSet在创建的时候被添加进清除栈,当程序段结束前,其必须被从栈移除;对清除栈的该项操作通过调用PopAndDestroy(bookColSet)来完成。

2.3.3 索引键值
索引键用来确定一个或多个表列的次序。每个键都有一些属性,例如唯一性或主键属性,一个对文本列的比较说明和一个关于列的列表,这些属性构成了键。如果没有主键被定义,行将按任意顺序被检索。
主键被封装在CDbKey中,列键值被封装在TDbKeyCol中。
下列代码段展示了如何在2.3.2节创建的表——“Column Sets”中创建一个双列索引:
...
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksIndexName,"BooksIndex");
...
// 创建一个包含两个列的索引
TDbKeyCol authorCol(KBooksAuthorCol);
TDbKeyCol titleCol(KBooksTitleCol);
CDbKey* index = CDbKey::NewLC(); // 创建一个索引键集
index->AddL(titleCol);
index->AddL(authorCol);
User::LeaveIfError(iBookDb.CreateIndex(
KBooksIndexName, KBooksTable, *index));
CleanupStack::PopAndDestroy(index);
首先,列索引被定义,随后CDbKey索引进行初始化,列被添加到该索引中。通过调用CreateIndex,我们可以创建一个数据库索引。
最后,由于在创建索引时,其被加入清除栈,程序结束前,我们需要调用PopAndDestroy(index)来将索引移出清除栈。

2.4  查询计划信息
有一些APIs被用来执行查询数据库结构(索引,表和结构)。数据库基类,RdbDatabase提供了下列方法:
? ·TableNamesL():封装在CDbTableName中的表名列表方法。
? ·IndexNamesL():封装在CDbIndexNames中的索引名列表方法。该方法采用表名参数。
? ·ColSetL():封装在CDbColSet中的表列定义方法。该方法采用一个一个表名参数。
? ·KeyL():表索引定义方法,该方法使用一个表名和一个索引名做为参数。
表的列定义CDbColSet也可对表或一个SQL视图进行查询(参见3.1节,“行集定义”)使用他们的基类方法RdbRowSet::ColSetL()。使用这个方法或利用TdbColSetIter 类便可以重新申明CdbColSet。

下列样例代码从Books表获得列名与规格。结果打包进入CdesCArryFlat。
...
private: // 成员数据
RDbStoreDatabase iBookDb;
...
// 数组中的项格式化为 <列名>: <列规格>
CDesCArrayFlat* CBookDb::ColumnNamesAndSizesL()
{
RDbTable booksTable;
TBuf<KDbMaxColName> columnNameAndSize;
_LIT(KDelimiter, ": ");
_LIT(KNoSize,"No size");
// 打开Books表
User::LeaveIfError(
booksTable.Open(iBookDb, KBooksTable,
booksTable.EReadOnly));
CleanupClosePushL(booksTable); // 记忆弹出与关闭
CDesCArrayFlat* resultArray =
new (ELeave)CDesC16ArrayFlat(KArrayGranularity);
CleanupStack::PushL(resultArray);
//重新声明Books 表的列,析取列名与列规格(仅对文本列的规格)
CDbColSet* colSet = booksTable.ColSetL();
CleanupStack::PushL(colSet);
TDbColSetIter colIter(*colSet);
while(colIter)
{
columnNameAndSize.Zero();
columnNameAndSize.Append(colIter->iName);
columnNameAndSize.Append(KDelimiter);
if(colIter->iType == EDbColText)
columnNameAndSize.AppendNum(colIter->iMaxLength);
else
columnNameAndSize.Append(KNoSize);
resultArray->AppendL(columnNameAndSize);
colIter++;
}
CleanupStack::PopAndDestroy(colSet);
CleanupStack::Pop(resultArray);
//从清除栈中探出booksTable,关闭清除栈。
CleanupStack::PopAndDestroy();
return resultArray;
}

2.5  打开与关闭数据库
本节中描述的数据库创建自2.2节,“创建一个数据库”,数据库已打开并在等待数据操作。在实践中,打开一个已有数据库是很频繁的事情。
如2.2节讨论的一样,数据库可以独占客户端模式打开与以客户/服务器共享模式打开。RDbNamedDatabase对这两种都支持,而RDbStoreDatabase API仅能以独占客户端模式打开数据库。
推荐使用RDbNamedDatabase API。客户端访问模式效率略高,但是由于文件被锁定,数据库无法访问其它应用程序。对于通常的的应用程序,推荐使用客户/服务器模式。

客户/服务器 模式
下列代码片段显示了以客户/服务器模式打开和关闭一个数据库。执行过程需要使用RDbNamedDatabase和数据库服务器会话RDbs。
...
private: //数据成员
RDbs iDbSession;
RDbNamedDatabase iBookDb;
...
void CBookDb::OpenDbInClientServerModeL(const TFileName&
aExistingBookFile)
{
User::LeaveIfError(iDbSession.Connect());
User::LeaveIfError(iBookDb.Open(iDbSession,
aExistingBookFile));
}
... // 打开数据库
void CBookDb::CloseDb()
{
iBookDb.Close(); //注意关闭的顺序.
iDbSession.Close();
}
 RdbNamedDatabase的Open方法打开数据库。其采用一个开启(连接)数据库会话RDbs。
数据库与会话都用Close()方法关闭。
 为了保证清理CbookDb销毁过程,析构器应调用CloseDb;通常用户使用CloseDb。但是,当打开数据库(iBookDb.Open)时,使用OpenDbInClientServerModeL可能会出现RDbs会话未关闭的情况。

客户端模式
下列代码段展示了打开与关闭一个使用RDbNameDatabase API客户端模式的数据库。除了使用文件服务器会话RFs 替代数据库服务器会话RDbs,这些代码与客户/服务器的代码相类似。
...
private: // 数据成员
RFs iFsSession;
RDbNamedDatabase iBookDb;
...
void CBookDb::OpenDbInClientSideModeL(const TFileName&
aExistingBookFile)
{
User::LeaveIfError(iFsSession.Connect());
User::LeaveIfError(iBookDb.Open(iFsSession,
aExistingBookFile));
}
... // 打开数据库
void CBookDb::CloseDb()
{
iBookDb.Close(); //注意关闭的次序
iFsSession.Close();
}
如果使用RDbStoreDatabase,必须使用正确的流与会话。
...
private: //数据成员
RFs iFsSession;
RDbStoreDatabase iBooksDb;
CFileStore* iFileStore;
...
TInt CBookDb::OpenDb(const TFileName&
aExistingBookFile)
{
Close();
if(!BaflUtils::FileExists(iFsSession, aExistingBookFile))
{
return KErrNotFound;
}
TRAPD(error,
iFileStore = CPermanentFileStore::OpenL(iFsSession,
aExistingBookFile, EFileRead|EFileWrite);
// 设定文件存储类型
iFileStore->SetTypeL(iFileStore->Layout());
iBookDb.OpenL(iFileStore,iFileStore->Root())
);
if(error!=KErrNone)
{
return error;
}
iOpen = ETrue;
return KErrNone;
}
void CBookDb::Close()
{
iBookDb.Close();
if(iFileStore)
{
delete iFileStore; //也关闭文件
iFileStore = NULL;
}
iFsSession.Close();
}

2.6  创建数据
一旦数据库打开,就可以在数据库中保存数据。需要指出的是,无需为添加和取回数据定义索引。
下列代码段展示了表方法(参见3.1.2节,“表行集”)是如何向2.3节创建的表,“用数据库定义表”中插入数据的。本例中假定数据库是开启的。
...
_LIT(KBooksTable, "Books");
_LIT(KBooksAuthorCol, "Author");
_LIT(KBooksTitleCol, "Title");
_LIT(KBooksDescriptionCol, "Description");
...
private: //数据成员
RDbStoreDatabase iBookDb;
... 
void CBookDb::AddBookWithCppApiL(const TDesC& aAuthor,
const TDesC& aTitle,
const TDesC& aDescription)
{
// 创建一个更新表数据库对象,假定数据库开启.
RDbTable table;
User::LeaveIfError(table.Open(iBookDb,
KBooksTable, table.EUpdatable));
CleanupClosePushL(table); // 记忆弹出与关闭
//建立 CdbColSet,使用其查询列成员
CDbColSet* booksColSet = table.ColSetL();
CleanupStack::PushL(booksColSet);
table.Reset();
table.InsertL(); // 插入空行
//为每一行设定作者与名称
table.SetColL(booksColSet->ColNo(KBooksAuthorCol), aAuthor);
table.SetColL(booksColSet->ColNo(KBooksTitleCol), aTitle);
//设定说明。使用长列流
RDbColWriteStream writeStream;
writeStream.OpenLC(table,
booksColSet->ColNo(KBooksDescriptionCol));
writeStream.WriteL(aDescription);
writeStream.Close();
CleanupStack::Pop(); // 写流
CleanupStack::PopAndDestroy(booksColSet);
table.PutL(); // 完成插入
CleanupStack::Pop() // 表
table.Close();
}
Books表首先以更细模式打开。booksColSet在寻找列成员——作者列,名称列,说明列时初始化。

为数据库添加一个新行包括:首先插入一个空行,更新行值,最后完成对数据库的插入。table.SetCol(…)设定了作者与名称。说明列比较长,需要使用RDbColWriteStream。像获得更多长文本列的信息,可以参考2.6.1节,“数据库中的长列数据”。其创建工作由PutL()完成。最后,表被关闭。

2.6.1 数据库中的长列数据
通过使用RDbColReadStream,可以获取带有一个EDbColLongText DBMS列的文本数据;相似地,RDbCoWriteStream被用来为行集设定其列类型的内容。
对流式读取来说,Symbian系统的DBMS在同一时刻仅支持行集内的一个列。当流打开时,在同一个行集对象中不能使用RDbColWriteStream来定义列。
...
_LIT(KBooksTable, "Books");
_LIT(KBooksDescriptionCol, "Description");
...
private: //数据成员
RDbStoreDatabase iBookDb;
...
TBuf<128> description; // 读结果并放置于此
RDbTable table;
User::LeaveIfError(
table.Open(iBookDb, KBooksTable, table.EReadOnly));
table.Reset();
// 找到“说明”列的列数量
CDbColSet* colSet = table.ColSetL();
TDbColNo descrColNo = colSet->ColNo(KBooksDescriptionCol);
delete colSet;
table.FirstL(); // 为第一行设定游标,检查是否成功
table.GetL(); // 获取第一行的操作
// 读所有的字母,确定没有128个字母的
RDbColReadStream readStream;
readStream.OpenLC(table, descrColNo);
readStream.ReadL(description, table.ColLength(descrColNo));
readStream.Close();
CleanupStack::Pop(); //读流
CleanupStack::PopAndDestroy(colSet);
table.Close();
...
长列数据通常使用RDbColWriteStream来添加或更新。(完整的插入长列数据代码可从2.6小节,“创建数据”获得)。
RDbTable table;
// Open table in updatable mode
User::LeaveIfError(
table.Open(iBookDb, KBooksTable, table.EUpdatable);
CDbColSet* booksColSet = table.ColSetL();
CleanupStack::PushL(booksColSet);
... // Find or create a row here and retrieve it for operation
// Use a stream to read data from the long column
RDbColWriteStream writeStream;
writeStream.OpenLC(table,
booksColSet->ColNo(KBooksDescriptionCol));
writeStream.WriteL(aDescription);
writeStream.Close();
CleanupStack::Pop(); // writeStream
...
CleanupStack::PopAndDestroy(booksColSet);
table.Close();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值