您的位置:首页 > 编程语言 > C语言/C++

使用C++模板封装SQLite(完整版)

2008-11-24 18:36 627 查看
SQLite 是一款开源的嵌入式数据库,由于本身小巧玲珑,比较适合作为应用软件的一部分嵌入到程序中,SQLite提供了一套简单易用的C API供应用程序调用, 但由于API的使用比较繁杂,并且考虑到将来移植到不同数据库的需求,应当避免直接使用SQLite API,应对其适当的封装,增加代码的灵活性。本文就项目中的使用经验提供了一种封装的方法。

1.API层的封装

由于SQLite支持ansi && unicode,为同时支持char, wchar_t,一些API同时提供了两个版本,如sqlite3_open,sqlite3_open16,另外一些API则只有一个版本,基于这两点实现了两个简单的类别:SQLite_APIBase和SQLite_APITraits,,SQLite_APIBase包含char, wchar_t无关的API,SQLite_APITraits模板从SQLite_APIBase派生,并针对char, wchar_t提供了两个特化版本。如下:

struct SQLite_APIBase

{

static int Close(sqlite3* pDb)

{

return sqlite3_close(pDb);

}

static int Prepare(sqlite3* db, wchar_t const* pszSQL, int nBytes, sqlite3_stmt **ppStmt, const void** pzTail)

{

return sqlite3_prepare16(db, pszSQL, nBytes, ppStmt, pzTail);

}

static int Step(sqlite3_stmt* pStmt)

{

return sqlite3_step(pStmt);

}

static int Finalize(sqlite3_stmt *pStmt)

{

return sqlite3_finalize(pStmt);

}

//set operations

static int BindInt(sqlite3_stmt *pStmt, int iIndex, int iValue)

{

return sqlite3_bind_int(pStmt, iIndex, iValue);

}

static int BindDouble(sqlite3_stmt *pStmt, int iIndex, double fValue)

{

return sqlite3_bind_double(pStmt, iIndex, fValue);

}

static int BindBlob(sqlite3_stmt *pStmt, int iIndex, void const* pData, int iSize)

{

return sqlite3_bind_blob(pStmt, iIndex, pData, iSize, NULL);

}

....

};

template< class TChar >

struct SQLite_APITraits;

template<>

struct SQLite_APITraits<char> : public SQLite_APIBase

{

static int Open( char const* filename, sqlite3** ppDb)

{

return sqlite3_open(filename, ppDb);

}

static int BindText(sqlite3_stmt *pStmt, int iIndex, char const* pText, int iSize)

{

return sqlite3_bind_text(pStmt, iIndex, pText, iSize, NULL);

}

static char const* ErrorMsg(sqlite3* pDB)

{

return sqlite3_errmsg(pDB);

}

...........

};

template<>

struct SQLite_APITraits<wchar_t> : public SQLite_APIBase

{

static int Open( wchar_t const* filename, sqlite3** ppDb)

{

return sqlite3_open16(filename, ppDb);

}

static int BindText(sqlite3_stmt *pStmt, int iIndex, wchar_t const* pText, int iSize)

{

return sqlite3_bind_text16(pStmt, iIndex, pText, iSize, NULL);

}

static wchar_t const* ErrorMsg(sqlite3* pDB)

{

return sqlite3_errmsg16(pDB);

}

............

};

这样以来,使用SQLite API时方便多了,不必针对char wchar_t调用不同的函数了,现在只需这样SQLite_APITraits<TCHAR>::some_func.

2.数据库层的封装

先来看一下数据库的接口

class CDatabase

{

public:

CDatabase();

virtual ~CDatabase();

public:

virtual bool Open(TCHAR const* pszFileName) = 0;

virtual bool Close() = 0;

virtual bool Execute(TCHAR const* pszSQLStatement) = 0;

};

CDatabase 提供了3个接口,Open打开数据库文件,Close关闭数据库,Execute执行制定的SQL语句.

这些接口的实现主要用到了下面的一些组件CDBConnection类,CDBStatement类,CResultSet类:

其中,CDBConnection负责管理数据库连接,CDBStatement负责运行SQL语句并生成执行结果,最终通过CResultSet查询结果。

为了使得这些组件能在不同的数据库间移植,需要把不同数据库的具体实现能分离出来,这里我用DBTraits模板来分离,为每种数据库实现一个特化版本,关于Traits用法,请参考侯捷老大一篇文章Traits: 類型的else-if-then機製.

(为了不影响代码的连贯性,DBTraits模板的代码附在最后)

下面是各个组件的实现,中间用到了iterator,类似于STL的迭代器但稍稍不同,你将会看到iterator将使得结果查询变得多么简单优雅

template<class T, class TDBTraits = DBTraits<T>, bool t_Managed = true>

class CDBConnection

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

public:

CDBConnection():m_pStatement(NULL)

{

}

~CDBConnection()

{

if (m_pStatement != NULL)

{

delete m_pStatement;

}

if (t_Managed)

{

bool bRet = Close();

assert(bRet);

}

}

public:

bool Open(TCHAR const* pszFileName)

{

return TDBTraits::Open(pszFileName);

}

bool Close()

{

return TDBTraits::Close();

}

CDBStatement<T, TDBTraits >* FetchStatement()

{

if (m_pStatement == NULL)

{

m_pStatement = new CDBStatement<T, TDBTraits >;

}

assert(m_pStatement != NULL);

return m_pStatement;

}

private:

CDBStatement<T, TDBTraits >* m_pStatement;

};

template<class T, class TDBTraits = DBTraits<T> >

class CDBStatement

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

typedef CDBRowSet<T, TDBTraits > CResultSet;

public:

CDBStatement():m_pHandle(NULL),m_pResult(NULL)

{

}

~CDBStatement()

{

if (m_pResult != NULL)

{

delete m_pResult;

}

}

public:

bool Execute(TCHAR const* pszSQLStatement)

{

Reset();

bool bRet = TDBTraits::Execute(pszSQLStatement);

if (!bRet)

{

return false;

}

m_pHandle = TDBTraits::GetStatementHandle();

if (TDBTraits::HasReturnValue())

{

m_pResult = new CResultSet();

assert(m_pResult != NULL);

}

return true;

}

template<class TValue>

bool BindValue(int iIndex, TValue Value, int iSize)

{

return TDBTraits::BindVariant(iIndex, Value, iSize);

}

CResultSet* FetchResult() const

{

return m_pResult;

}

/*implicit*/operator SQLStatementHandle() const

{

return m_pHandle

}

private:

void Reset()

{

// it is OK to delete NULL

if (m_pResult != NULL)

{

delete m_pResult;

m_pResult = NULL;

}

}

private:

CResultSet* m_pResult;

SQLStatementHandle m_pHandle;

};

template<class T, class TDBTraits = DBTraits<T> >

class CDBIterator

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

typedef CDBRow<T, TDBTraits> RowType;

public:

CDBIterator():m_bIsDone(false)

{

}

~CDBIterator(){}

public:

//++ operator

//forward increment

CDBIterator& operator++()

{

m_bIsDone = !(TDBTraits::Forward() );

return *this;

}

//backwards increment

CDBIterator operator++(int)

{

CDBIterator tmp(*this);

m_bIsDone = !(TDBTraits::Forward() );

return tmp;

}

//-- operator

//no backwards operation

public:

RowType& operator*()

{

return m_CurrentRow;

}

RowType* operator&()

{

return &m_CurrentRow;

}

RowType* operator->()

{

return &m_CurrentRow;

}

public:

bool IsDone() const

{

return m_bIsDone;

}

private:

bool m_bIsDone;

RowType m_CurrentRow;

};

template<class T, class TDBTraits = DBTraits<T> >

class CDBRow

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

public:

CDBRow(){}

~CDBRow(){}

public:

template< class TValue >

bool FetchValue(int iColumn, TValue pRetValue)

{

SQLStatementHandle pStmt = TDBTraits::GetStatementHandle();

return TDBTraits::FetchValue(pStmt, iColumn, pRetValue);

}

int FetchBytes(int iColumn)

{

SQLStatementHandle pStmt = TDBTraits::GetStatementHandle();

return TDBTraits::FetchColumnBytes(pStmt, iColumn);

}

};

template<class T, class TDBTraits = DBTraits<T> >

class CDBRowSet

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

typedef CDBIterator<T, TDBTraits> iterator;

public:

CDBRowSet()

:m_pIterator(NULL)

{

}

~CDBRowSet()

{

if (m_pIterator != NULL)

{

delete m_pIterator;

}

}

public:

bool IsNULL() const

{

return TDBTraits::HasReturnValue();

}

//for iterator

iterator& CreateIterator()

{

if (m_pIterator == NULL)

{

m_pIterator = new iterator;

}

return *m_pIterator;

}

private:

iterator* m_pIterator;

};

下面是将上面3个组件黏合起来的代码,风格类似WTL

//空类,留着扩展

template< class TBase >

class CDatabaseImplRoot : public TBase

{

public:

CDatabaseImplRoot(){}

virtual ~CDatabaseImplRoot(){}

public:

//interface

private:

//implementations

};

template< class T, class TBase = CDatabase,class TDBTraits = DBTraits<T>, bool t_Managed = true >

class CDatabaseImpl : public CDatabaseImplRoot<TBase>

{

public:

typedef TDBTraits::DBHandle DBHandle;

typedef TDBTraits::SQLStatementHandle SQLStatementHandle;

typedef CDBIterator<T, TDBTraits> iterator;

typedef CDBStatement<T, TDBTraits>::CResultSet ResultSet;

public:

CDatabaseImpl(){}

virtual ~CDatabaseImpl(){}

protected:

CDBConnection< T, TDBTraits >* GetConnection()

{

return &m_Connection;

}

private:

CDBConnection< T, TDBTraits, t_Managed> m_Connection;

CDBStatement< T, TDBTraits > m_pStatement;

};

好了,最终的CDatabaseImpl就是所有数据库类的共同基类,每种数据库具体实现全在DBTraits里,CDatabaseImpl是数据库无关的.

最终的CSQLiteDatabase是这样的:

class CSQLiteDatabase : public CDatabaseImpl<CSQLiteDatabase, CDatabase>

{

public:

typedef CDatabaseImpl<CSQLiteDatabase, CDatabase> Base;

typedef Base::iterator iterator;

typedef Base::ResultSet ResultSet;

public:

CSQLiteDatabase();

virtual ~CSQLiteDatabase();

public:

virtual bool Open(TCHAR const* pszFileName);

virtual bool Close();

virtual bool Execute(TCHAR const* pszSQLStatement);

public:

template<class T>

bool BindValue(int iIndex, T Value, int iSize)

{

return GetConnection()->FetchStatement()->BindValue(iIndex,Value, iSize);

}

ResultSet* GetResult();

private:

};

CSQLiteDatabase::CSQLiteDatabase()

{

}

CSQLiteDatabase::~CSQLiteDatabase()

{

}

bool CSQLiteDatabase::Open(TCHAR const* pszFileName)

{

return GetConnection()->Open(pszFileName);

}

bool CSQLiteDatabase::Close()

{

return GetConnection()->Close();

}

bool CSQLiteDatabase::Execute(TCHAR const* pszSQLStatement)

{

return GetConnection()->FetchStatement()->Execute(pszSQLStatement);

}

CSQLiteDatabase::ResultSet* CSQLiteDatabase::GetResult()

{

return GetConnection()->FetchStatement()->FetchResult();

}

CSQLiteDatabase实现了CDatabase定义的接口,现在可以使用了

现在有两个数据表(我不太懂数据库设计,可能有问题)

tbl_student: id, name存储学生的学号和姓名

tbl_score: id,chinese,math,english存储学生的学号和语文,数学,英语成绩.

现在假设查询王小明的成绩:

CSQLiteDatabase* pDatabase = GetDatabase();

bool bRet = pDatabase->Execute(_T("select a.id, b.chinese, b.math, b.english from tbl_student a, tbl_score b where a.name = 王小明 and a. id ==b.id"));

if (bRet)

{

CSQLiteDatabase::ResultSet* pSet = pDatabase->GetResult();

//如果不是查询语句,则pSet == NULL

if (pSet != NULL)

{

for (CSQLiteDatabase::iterator i = pSet->CreateIterator(); !i.IsDone() ; ++i)

{

int id;

tchar strChinese[MAX];

tchar strMath[MAX];

tchar strEnglish[MAX];

int iIndex = 0;

bRet = (*i).FetchValue(iIndex++,id);

bRet = (*i).FetchValue(iIndex++,strChinese);

bRet = (*i).FetchValue(iIndex++,strMath);

bRet = (*i).FetchValue(iIndex++,strEnglish);

}

}

}

怎么样很简单吧.

附 DBTraits 源代码:

template< class TDatabase >

struct DBTraits;

template<>

struct DBTraits<CSQLiteDatabase>

{

typedef sqlite3* DBHandle;

typedef sqlite3_stmt* SQLStatementHandle;

// typedef struct

static bool Open(TCHAR const* pszFileName)

{

if (!Close())

{

return false;

}

assert(m_pDatabase == NULL);

int iRet = SQLite_APITraits<TCHAR>::Open(pszFileName,&m_pDatabase);

if (iRet != SQLITE_OK)

{

return false;

}

assert(m_pDatabase != NULL);

return true;

}

static bool Close()

{

if (m_pStatement != NULL)

{

int iRet = SQLite_APITraits<TCHAR>::Finalize(m_pStatement);

if (iRet != SQLITE_OK)

{

return false;

}

m_pStatement = NULL;

}

if (m_pDatabase != NULL)

{

int iRet = SQLite_APITraits<TCHAR>::Close(m_pDatabase);

if (iRet == SQLITE_BUSY)

{

return false;

}

m_pDatabase = NULL;

}

return true;

}

static bool Execute(TCHAR const* pszSQLStatement)

{

if (m_pDatabase == NULL/* || m_pStatement == NULL*/)

{

return false;

}

ResetStatement();

int iRet = SQLite_APITraits<TCHAR>::Prepare(m_pDatabase, pszSQLStatement, -1, &m_pStatement, NULL);

if (iRet != SQLITE_OK)

{

return false;

}

assert(m_pStatement != NULL);

//BindValue(sqlite3_stmt *pStmt, int iIndex, T Value, int iSize = 0);

for (std::list<DBVariant<TCHAR> >::iterator i = m_listBindValue.begin(); i != m_listBindValue.end(); ++i)

{

DBVariant<TCHAR> Value= (*i);

if (Value.m_iType == DBVariant<TCHAR>::VALUE_INT)

{

SQLite_APITraits<TCHAR>::BindValue(m_pStatement, Value.m_iIndex, Value.m_iIntValue, Value.m_iSize);

}

else if (Value.m_iType == DBVariant<TCHAR>::VALUE_DOUBLE)

{

SQLite_APITraits<TCHAR>::BindValue(m_pStatement, Value.m_iIndex, Value.m_fDoubleValue, Value.m_iSize);

}

else if (Value.m_iType == DBVariant<TCHAR>::VALUE_TEXT)

{

SQLite_APITraits<TCHAR>::BindValue(m_pStatement, Value.m_iIndex, Value.m_pTextValue, Value.m_iSize);

}

else if (Value.m_iType == DBVariant<TCHAR>::VALUE_BLOB)

{

SQLite_APITraits<TCHAR>::BindValue(m_pStatement, Value.m_iIndex, Value.m_pBlobValue, Value.m_iSize);

}

else

{

assert(false);

}

}

m_listBindValue.clear();

iRet = SQLite_APITraits<TCHAR>::Step(m_pStatement);

if (iRet != SQLITE_DONE && iRet != SQLITE_ROW)

{

return false;

}

if (iRet == SQLITE_ROW)

{

m_bHasReturnValue = true;

}

// iRet = SQLite_APITraits<TCHAR>::Finalize(m_pStatement);

// if (iRet != SQLITE_OK)

// {

// return false;

// }

return true;

}

static DBHandle& GetDBHandle()

{

return m_pDatabase;

}

static SQLStatementHandle& GetStatementHandle()

{

return m_pStatement;

}

static void ResetStatement()

{

if (m_pStatement != NULL)

{

int iRet = SQLite_APITraits<TCHAR>::Finalize(m_pStatement);

if (iRet != SQLITE_OK)

{

assert(false);

}

m_pStatement = NULL;

}

}

static bool HasReturnValue()

{

return m_bHasReturnValue;

}

static bool Forward()

{

int iRet = SQLite_APITraits<TCHAR>::Step(m_pStatement);

if (iRet == SQLITE_DONE)

{

ResetStatement();

}

m_bHasReturnValue = (iRet == SQLITE_ROW);

return m_bHasReturnValue;

}

static int FetchColumnCount(SQLStatementHandle pStmt)

{

return SQLite_APITraits<TCHAR>::ColumnCount(pStmt);

}

static int FetchColumnType(SQLStatementHandle pStmt, int iColumn)

{

return SQLite_APITraits<TCHAR>::ColumnType(pStmt, iColumn);

}

static wchar_t const* FetchColumnName(SQLStatementHandle pStmt, int iColumn)

{

return SQLite_APITraits<TCHAR>::ColumnName(pStmt, iColumn);

}

static int FetchColumnBytes(SQLStatementHandle pStmt, int iColumn)

{

return SQLite_APITraits<TCHAR>::ColumnBytes(pStmt, iColumn);

}

template< class T >

static bool FetchValue(SQLStatementHandle pStmt, int iColumn, T pRetValue)

{

return SQLite_APITraits<TCHAR>::FetchColumnValue(pStmt, iColumn, pRetValue);

}

// template< class T >

// static int BindValue(SQLStatementHandle pStmt, int iIndex, T Value, int iSize)

// {

// return SQLite_APITraits<TCHAR>::BindValue(pStmt, iColumn, Value, iSize);

// }

template< class T >

static bool BindVariant(int iIndex, T Value, int iSize/* = 0*/)

{

return false;

}

template<>

static bool BindVariant<int>(int iIndex, int Value, int iSize/* = 0*/)

{

DBVariant<TCHAR> Var;

Var.m_iType = DBVariant<TCHAR>::VALUE_INT;

Var.m_iIndex = iIndex;

Var.m_iIntValue = Value;

Var.m_iSize = iSize;

m_listBindValue.push_back(Var);

return true;

}

template<>

static bool BindVariant<double>(int iIndex, double Value, int iSize/* = 0*/)

{

DBVariant<TCHAR> Var;

Var.m_iType = DBVariant<TCHAR>::VALUE_DOUBLE;

Var.m_iIndex = iIndex;

Var.m_fDoubleValue = Value;

Var.m_iSize = iSize;

m_listBindValue.push_back(Var);

return true;

}

template<>

static bool BindVariant<TCHAR const*>(int iIndex, TCHAR const* Value, int iSize)

{

DBVariant<TCHAR> Var;

Var.m_iType = DBVariant<TCHAR>::VALUE_TEXT;

Var.m_iIndex = iIndex;

Var.m_pTextValue = Value;

Var.m_iSize = iSize;

m_listBindValue.push_back(Var);

return true;

}

template<>

static bool BindVariant<void const*>(int iIndex, void const* pData, int iSize)

{

DBVariant<TCHAR> Var;

Var.m_iType = DBVariant<TCHAR>::VALUE_BLOB;

Var.m_iIndex = iIndex;

Var.m_pBlobValue = pData;

Var.m_iSize = iSize;

m_listBindValue.push_back(Var);

return true;

}

private:

static DBHandle m_pDatabase;

static SQLStatementHandle m_pStatement;

static bool m_bHasReturnValue;

static std::list<DBVariant<TCHAR> > m_listBindValue;

};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: