Windows Mobile下访问Sqlite的Native C++封装

背景

当前移动设备开发领域,在本地数据存储方面,Sqlite几乎成了事实标准,Andriod (android.database.sqlite),iPhone (SQLite for iPhone SDKFMDB for iPhone),Palm WebOS (webOS SQL Tutorial),新版本的Symbian也直接built-in Sqlite了(20 million Symbian smartphones shipped in Q3 2007 Newer versions of the SymbianOS have SQLite built in.)。那么作为移动设备领域的重要一员Windows Mobile怎么可能错过Sqlite呢。

 

简介

Sqlite几乎成立移动设备开发领域数据存储方面的事实标准。Sqlite已经广泛被使用到Andriod,iPhone,WebOS以及Symbian等平台了,本文讲述在Windows Mobile平台下如何使用Native C++访问Sqlite,同时讲述一个封装类的实现和使用。

 

Sqlite源码

Sqlite源码可以到 SQLite Download Page 下载,我为了省事直接使用了sqlite.phxsoftware.com的在Windows Mobile下的build工程。

 

Sqlite的C++封装

封装我使用了Tyushkov Nikolay的封装CppSQLite3U。这里感谢egmkang的推荐。CppSQLite3U封装是对Sqlite原有纯C的api进行OO的C++的封装。主要封装以下几个类:

1. CppSQLite3DB 数据库类,用于新建数据库,打开关闭链接,执行DDL和DML。

2. CppSQLite3Statement 用于执行参数化的SQL。CppSQLite3DB 可以执行SQL但是不支持参数化。

3. CppSQLite3Query 用于读出执行Select后的查询结果。

4. CppSQLite3Exception 用于捕捉异常。

简单明了的封装了Sqlite。

 

封装类的使用

使用方法源自于我对CppSQLite3U类的单元测试。见源文件的SqliteHelperTest.h。

 

创建数据库文件

TEST(SqliteHelper, CreateDatabase)
{
try
{
CppSQLite3DB db;
DeleteFile(DB_FILE_NAME);
db.open(DB_FILE_NAME);
db.close();
}
catch(CppSQLite3Exception e)
{
FAIL(ToString(e.errorMessage()).c_str());
}
TRACE("Create database successful.");
}

调用CppSQLite3DB 的open()函数的时候如果发现没有数据库文件就会新建一个数据库文件。Sqlite的源代码如下(见sqlite3.c):

rc = openDatabase(zFilename8, ppDb,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);

 

执行DDL

TEST(SqliteHelper, CreateTable)
{
try
{
CppSQLite3DB db;
db.open(DB_FILE_NAME);
db.execDML(L"create table T1(F1 int, F2 char(20), F3 char(20));");

db.close();
}
catch(CppSQLite3Exception e)
{
FAIL(ToString(e.errorMessage()).c_str());
}
TRACE("Create table successful.");
}

执行CppSQLite3DB 的execDML()函数可以执行DDL。Sqlite3的数据类型定义和其他常见关系型数据库有很大区别,Sqlite3数据类型定义信息是和具体的数据绑定的而不是和字段定义绑定,也就是动态的,同一个字段的不同记录可以存储不同的数据类型的数据。所以在定义表的时候定义字段类型不是必须的,具体可以参考 Datatypes In SQLite Version 3

 

执行DML

TEST(SqliteHelper, InsertTable)
{
try
{
CppSQLite3DB db;
db.open(DB_FILE_NAME);

CString sqlStr;
time_t tmStart, tmEnd;
tmStart = time(0);
for(int i=0; i<max; ++i)
{
SYSTEMTIME currentTime;
GetLocalTime(&currentTime);
sqlStr.Format(L"INSERT INTO T1 (F1, F2, F3) VALUES(%d, 'STR%d', '%d-%d-%d %d:%d:%d')",
i, i, currentTime.wYear, currentTime.wMonth, currentTime.wDay, currentTime.wHour, currentTime.wMinute, currentTime.wSecond);
db.execDML(sqlStr);
}
tmEnd = time(0);
char ch[255];
sprintf(ch, "Insert table successful in %d seconds", tmEnd-tmStart);
TRACE(ch);
db.close();
}
catch(CppSQLite3Exception e)
{
FAIL(ToString(e.errorMessage()).c_str());
}
}

CppSQLite3DB 的execDML()函数不仅可以执行DDL,而且可以执行DML。同理Update和Delete语句一样。

 

执行Scalar

TEST(SqliteHelper, SelectScalarBeforeInsert)
{
try
{
CppSQLite3DB db;
db.open(DB_FILE_NAME);

int count = db.execScalar(L"SELECT COUNT(*) FROM T1;");
char ch[255];
sprintf(ch, "%d rows in T1 table", count);
TRACE(ch);
db.close();
}
catch(CppSQLite3Exception e)
{
FAIL(ToString(e.errorMessage()).c_str());
}
TRACE("Select scalar before insert successful.");
}

CppSQLite3DB 的execScalar()函数模仿ADO.NET的SqlCommand.ExecuteScalar 用于取第一条记录的一个字段的值,一般用于聚集函数的查询。

 

执行查询

TEST(SqliteHelper, SelectAfterInsert)
{
try
{
CppSQLite3DB db;
db.open(DB_FILE_NAME);
CppSQLite3Query q = db.execQuery(L"SELECT * FROM T1;");

std::string str;
char ch[255];
while (!q.eof())
{
sprintf(ch, "F1=%d, F2=%S, F3=%S\n", q.getIntField(0), q.getStringField(1), q.getStringField(2));
str += ch;
q.nextRow();
}
TRACE(str.c_str());
db.close();
}
catch(CppSQLite3Exception e)
{
FAIL(ToString(e.errorMessage()).c_str());
}
}

查询需要借助CppSQLite3Query 来取出查询的结果。eof()函数判断是否结束。nextRow()移动到下一条记录。getIntField()函数和getStringField()函数为读取当前记录的特定字段的值。

 

使用事务

TEST(SqliteHelper, InsertTableWithTransaction)
{
try
{
CppSQLite3DB db;
db.open(DB_FILE_NAME);

CString sqlStr;
time_t tmStart, tmEnd;
tmStart = time(0);
db.execDML(L"begin transaction;");
for(int i=0; i<max; ++i)
{
SYSTEMTIME currentTime;
GetLocalTime(&currentTime);
sqlStr.Format(L"INSERT INTO T1 (F1, F2, F3) VALUES(%d, 'STR%d', '%d-%d-%d %d:%d:%d')",
i, i, currentTime.wYear, currentTime.wMonth, currentTime.wDay, currentTime.wHour, currentTime.wMinute, currentTime.wSecond);
db.execDML(sqlStr);
}
db.execDML(L"commit transaction;");
tmEnd = time(0);
char ch[255];
sprintf(ch, "Insert table successful in %d seconds", tmEnd-tmStart);
TRACE(ch);
db.close();
}
catch(CppSQLite3Exception e)
{
db.execDML(L"rollback transaction;");
FAIL(ToString(e.errorMessage()).c_str());
}
}

在Sqlite上事务的使用非常简单。通过CppSQLite3DB 的execDML()函数来打开,提交和回退事务。Sqlite在事务处理上,语法层面上和MS SQL Server有点类似,默认是自动事务(AutoCommit),关于事务处理我之前写过一篇文章,有兴趣可以参考下 MS SQL Server和Oracle对数据库事务处理的差异性。也可以参考

BEGIN TRANSACTION。 从测试结果看,批量处理数据,显式使用事务和自动事务在处理时间上差别很大。

sqlite1

在insert 100条数据时,显式使用事务小于1秒钟完成,而使用自动事务的话需要4秒钟。为什么有这么大的差别,我没有仔细研究Sqlite的源码,我从通用数据库的概念来讲述,事务其中一个特性是持久性(Durability),也就是凡是提交了的事务的数据都需要持久化,需要持久化就需要写硬盘,在移动设备是flash,写永久存储设备的速度是远远慢于写内存的速度的,所以速度差异点在IO。

 

Unit Test

项目开发中使用了TDD,关于Unit Test可以参考Wince和Windows Mobile下native C++的单元测试Windows Mobile下使用CppUnitLite输出测试结果

 

关于项目

我把项目host到codeplex了,项目主页链接如下:

SqliteHelper - Native C++ wrapper class for Sqlite on Windows Mobile & Wince

检查和下载最新版本链接如下

http://sqlitehelper.codeplex.com/SourceControl/ListDownloadableCommits.aspx

 

如果你喜欢这个项目,如果这些代码能帮助你,请回复,上次我做了一个SqlCeHelper,参考 Windows Mobile下Native C++访问SqlCe的封装。某一天我收到一个消息,一个人告诉我他google了一个星期也没一个搞定SqlCe,看了我的文章搞定了,我还是很高兴能帮到别人。

作者:Jake LinJake's Blog on 博客园
出处:http://procoder.cnblogs.com

作品Jake Lin创作,采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。 任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请给我留言
posted @ 2009-10-19 10:58 Jake Lin 阅读(3593) 评论(25) 编辑 收藏

 回复 引用 查看   
#1楼 2009-10-19 09:59 施炯      
SQLite以嵌入式设备上用的不错
以前用过Pocket Access,觉得比较慢。

 回复 引用 查看   
#2楼[楼主] 2009-10-19 10:15 Jake Lin      
@施炯
我没有用过Pocket Access,都是用Sqlce多。

 回复 引用 查看   
#3楼 2009-10-19 10:42 AlphaWu      
请问博主,你的是WM什么版本?看着UI很漂亮啊,还有,T9输入法能共享给大家么?我想要^_^。
好像CODEPROJECT上有这个样的东东。忘记了叫啥了。
 回复 引用 查看   
#5楼 2009-10-19 11:41 egmkang      
SQLite的锁是靠OS的文件锁实现的,所以效率肯定是一个问题.
phxsoftware的文档也推荐多行操作用事务,我觉得效率应该是一个理由.

 回复 引用 查看   
#6楼 2009-10-19 11:42 egmkang      
@AlphaWu
UI貌似是WM 6.5 的UI.

引用AlphaWu:请问博主,你的是WM什么版本?看着UI很漂亮啊,还有,T9输入法能共享给大家么?我想要^_^。


应该刷到 6.5了,我刚刷了一个。很不错

 回复 引用 查看   
#9楼 2009-10-19 12:28 answer      
 回复 引用 查看   
#10楼[楼主] 2009-10-19 13:00 Jake Lin      
@AlphaWu
Windows Mobile 6.5也就是Windows Phone,A9是默认的输入法,不是单独安装的。

 回复 引用 查看   
#11楼[楼主] 2009-10-19 13:00 Jake Lin      
@别爱上哥,哥只是个传说!
是的,那个不支持unicode。

 回复 引用 查看   
#12楼[楼主] 2009-10-19 13:01 Jake Lin      
@answer
谢谢分享。

 回复 引用 查看   
#13楼[楼主] 2009-10-19 13:03 Jake Lin      
@egmkang
6.5最新的build,开始菜单才会在下面显示,但是我发现有问题了,很多只有 “X” 按钮退出的程序,这个shell不支持。

 回复 引用 查看   
#14楼 2009-10-19 13:45 egmkang      
@Jake Lin
模拟器显示还是比较丑的...

 回复 引用 查看   
#15楼 2009-10-19 17:16 AlphaWu      
引用Jake Lin:
@AlphaWu
Windows Mobile 6.5也就是Windows Phone,A9是默认的输入法,不是单独安装的。

是A9么?我还以为是T9.眼花了.^_^

 回复 引用 查看   
#16楼[楼主] 2009-10-19 17:51 Jake Lin      
@AlphaWu
我看了一下,叫做xt9.samsung自带的。

 回复 引用 查看   
#17楼 2009-10-19 20:19 王克伟      
好,开发到了来参考一下
 回复 引用 查看   
#18楼 2009-11-14 11:40 zhou1xp      
这个封装我也在使用,不过它的异常太多,有的功能还是要自己在封装中添加,不过,总体来说还是不错的
 回复 引用 查看   
#19楼[楼主] 2009-11-14 16:00 Jake Lin      
@zhou1xp
hi,我也在用,现在没有发现异常,请问能分享你的修改吗?加入到这个封装中去,谢谢!

 回复 引用   
#20楼 2009-12-17 10:48 笨熊[未注册用户]
感谢分享,学习了,希望能帮上我的忙。
 回复 引用 查看   
#21楼[楼主] 2009-12-18 07:54 Jake Lin      
@笨熊
有人用过,可以的。

 回复 引用 查看   
#22楼 2010-03-11 01:13 Johnny.3      
LZ,你好,我下了那个CPPSqlite3U了,应该还需要sqlite的源码是吧?用windows版本下的还是???你说你用的是sqlite.phxsoftware.com的在Windows Mobile下的build工程,我是菜鸟,没找到,能给我一份吗?还有我用vs2008,文件引用需要注意哪些方面?谢谢
 回复 引用 查看   
#23楼[楼主] 2010-03-11 06:51 Jake Lin      
@Johnny.3
你下载在codeplex的项目来编译就ok了,这里包含了sqlite的源码 http://sqlitehelper.codeplex.com/

 回复 引用 查看   
#24楼 2011-01-13 17:01 摇滚诗人      
终于跑通了博主的sqlitehelper程序,代码还要继续看,正在看这篇文章的童鞋请注意:编译时SqliteHelper和CppUnitLite要在目标机环境下编译,比如Windows Mobile 6 Professional。而SQLite.Interop需要在PPC2003下编译,并且把编译的目标(SqLite.DLL)拷贝到目标机,不然程序运行不了。一开始没有拷贝这个动态链接库运行不成功卡了好久:)
还是对非托管代码调用托管代码不熟啊……
博主的例子很好,包含了很多实用的技术,测试驱动开发,非托管代码调用托管代码。
再次谢谢博主。

 回复 引用 查看   
#25楼[楼主] 2011-01-14 06:00 Jake Lin      
引用摇滚诗人:
终于跑通了博主的sqlitehelper程序,代码还要继续看,正在看这篇文章的童鞋请注意:编译时SqliteHelper和CppUnitLite要在目标机环境下编译,比如Windows Mobile 6 Professional。而SQLite.Interop需要在PPC2003下编译,并且把编译的目标(SqLite.DLL)拷贝到目标机,不然程序运行不了。一开始没有拷贝这个动态链接库运行不成功卡了好久:)
还是对非托管代码调用托管代码不熟啊……
博主的例子很好,包含了很多实用的技术,测试驱动开发,非托管代码调用托管代码。
再次谢谢博主。

谢谢呀,我自己都没有注意这些细节,编译的时候过了就没管了。