WM/WinCE 下访问Sqlite的Native C++封装 (CppSQLite3U的使用)

原文做者:林永坚Jake Linhtml

原文地址:http://www.cnblogs.com/procoder/archive/2009/10/19/1585733.htmlandroid

原文标题:Windows Mobile下访问SqliteNative C++封装web

背景sql

  当前移动设备开发领域,在本地数据存储方面,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数据库

简介api

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

Sqlite源码iphone

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

SqliteC++封装单元测试

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

1. CppSQLite3DB 数据库类,用于新建数据库,打开关闭连接,执行DDLDML

2. CppSQLite3Statement用于执行参数化的SQLCppSQLite3DB能够执行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.");
}

  调用CppSQLite3DBopen()函数的时候若是发现没有数据库文件就会新建一个数据库文件。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.");
}

  执行CppSQLite3DBexecDML()函数能够执行DDLSqlite3的数据类型定义和其余常见关系型数据库有很大区别,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());    
    }
}

  CppSQLite3DBexecDML()函数不只能够执行DDL,并且能够执行DML。同理UpdateDelete语句同样。
执行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."); 
}

  CppSQLite3DBexecScalar()函数模仿ADO.NETSqlCommand.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(¤tTime);            
            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上事务的使用很是简单。经过CppSQLite3DBexecDML()函数来打开,提交和回退事务。Sqlite在事务处理上,语法层面上和MS SQL Server有点相似,默认是自动事务(AutoCommit),关于事务处理我以前写过一篇文章,有兴趣能够参考下MS SQL ServerOracle对数据库事务处理的差别性。也能够参考BEGIN TRANSACTION。 从测试结果看,批量处理数据,显式使用事务和自动事务在处理时间上差异很大。

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

  项目开发中使用了TDD,关于Unit Test能够参考WinceWindows Mobilenative C++的单元测试 和Windows Mobile下使用CppUnitLite输出测试结果 。
关于项目
  我把项目hostcodeplex了,项目主页连接以下:

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

  检查和下载最新版本连接以下

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