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

    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提供了两个特化版本。如下:

 

 

  1. struct SQLite_APIBase 
  2. {
  3.     static int Close(sqlite3* pDb)
  4.     {
  5.         return sqlite3_close(pDb);
  6.     }
  7.     static int Prepare(sqlite3* db, wchar_t const* pszSQL, int nBytes, sqlite3_stmt **ppStmt, const void** pzTail)
  8.     {
  9.           return sqlite3_prepare16(db, pszSQL, nBytes, ppStmt, pzTail);
  10.     }
  11.     static int Step(sqlite3_stmt* pStmt)
  12.     {
  13.         return sqlite3_step(pStmt);
  14.     }
  15.     static int Finalize(sqlite3_stmt *pStmt)
  16.     {
  17.         return sqlite3_finalize(pStmt);
  18.     }
  19.     //set operations
  20.     static int BindInt(sqlite3_stmt *pStmt, int iIndex, int iValue)
  21.     {
  22.         return sqlite3_bind_int(pStmt, iIndex, iValue);
  23.     }
  24.     static int BindDouble(sqlite3_stmt *pStmt, int iIndex, double fValue)
  25.     {
  26.         return sqlite3_bind_double(pStmt, iIndex, fValue);
  27.     }
  28.     static int BindBlob(sqlite3_stmt *pStmt, int iIndex, void const* pData, int iSize)
  29.     {
  30.         return sqlite3_bind_blob(pStmt, iIndex, pData, iSize, NULL);
  31.     }
  32.     ....
  33. };
  34. templateclass TChar >
  35. struct SQLite_APITraits;
  36. template<>
  37. struct SQLite_APITraits<char> : public SQLite_APIBase
  38. {
  39.     static int Open( char const* filename, sqlite3** ppDb)
  40.     {
  41.         return sqlite3_open(filename, ppDb);
  42.     }
  43.     static int BindText(sqlite3_stmt *pStmt, int iIndex, char const* pText, int iSize)
  44.     {
  45.         return sqlite3_bind_text(pStmt, iIndex, pText, iSize, NULL);
  46.     }
  47.     static char const* ErrorMsg(sqlite3* pDB)
  48.     {
  49.         return sqlite3_errmsg(pDB);
  50.     }
  51.     ...........
  52. };
  53. template<>
  54. struct SQLite_APITraits<wchar_t> : public SQLite_APIBase
  55. {
  56.     static int Open( wchar_t const* filename, sqlite3** ppDb)
  57.     {
  58.         return sqlite3_open16(filename, ppDb);
  59.     }
  60.     static int BindText(sqlite3_stmt *pStmt, int iIndex, wchar_t const* pText, int iSize)
  61.     {
  62.         return sqlite3_bind_text16(pStmt, iIndex, pText, iSize, NULL);
  63.     }
  64.     static wchar_t const* ErrorMsg(sqlite3* pDB)
  65.     {
  66.         return sqlite3_errmsg16(pDB);
  67.     }
  68.     ............
  69. };

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

    

    2.数据库层的封装

先来看一下数据库的接口

 

 

  1. class CDatabase  
  2. {
  3. public:
  4.  CDatabase();
  5.  virtual ~CDatabase();
  6. public:
  7.  virtual bool Open(TCHAR const* pszFileName) = 0;
  8.  virtual bool Close() = 0;
  9.  virtual bool Execute(TCHAR const* pszSQLStatement) = 0;
  10. };

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

 

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

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

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

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

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

 

  1. template<class T, class TDBTraits = DBTraitsbool t_Managed = true>
  2. class CDBConnection
  3. {
  4. public:
  5.  typedef TDBTraits::DBHandle DBHandle;
  6.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  7. public:
  8.  CDBConnection():m_pStatement(NULL)
  9.  {
  10.  }
  11.  ~CDBConnection()
  12.  {
  13.   if (m_pStatement != NULL)
  14.   {
  15.    delete m_pStatement;
  16.   }
  17.   
  18.   if (t_Managed)
  19.   {
  20.    bool bRet = Close();
  21.    assert(bRet);
  22.   }
  23.  }
  24. public:
  25.  bool Open(TCHAR const* pszFileName)
  26.  {
  27.   return TDBTraits::Open(pszFileName);
  28.  }
  29.  bool Close()
  30.  {
  31.   return TDBTraits::Close();
  32.  }
  33.  CDBStatement* FetchStatement()
  34.  {
  35.   if (m_pStatement == NULL)
  36.   {
  37.    m_pStatement = new CDBStatement;
  38.   }
  39.   assert(m_pStatement != NULL);
  40.   return m_pStatement;
  41.  }
  42. private:
  43.  CDBStatement* m_pStatement;
  44. };
  45. template<class T, class TDBTraits = DBTraits >
  46. class CDBStatement
  47. {
  48. public:
  49.  typedef TDBTraits::DBHandle DBHandle;
  50.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  51.  typedef CDBRowSet CResultSet;
  52. public:
  53.  CDBStatement():m_pHandle(NULL),m_pResult(NULL)
  54.  {
  55.  }
  56.  ~CDBStatement()
  57.  {
  58.   if (m_pResult != NULL)
  59.   {
  60.    delete m_pResult;
  61.   }
  62.  }
  63. public:
  64.  bool Execute(TCHAR const* pszSQLStatement)
  65.  {
  66.   Reset();
  67.   
  68.   bool bRet = TDBTraits::Execute(pszSQLStatement);
  69.   if (!bRet)
  70.   {
  71.    return false;
  72.   }
  73.   
  74.   m_pHandle = TDBTraits::GetStatementHandle();
  75.   
  76.   if (TDBTraits::HasReturnValue())
  77.   {
  78.    m_pResult = new CResultSet();
  79.    assert(m_pResult != NULL);
  80.   }
  81.   return true;
  82.  }
  83.  template<class TValue>
  84.  bool BindValue(int iIndex, TValue Value, int iSize)
  85.  {
  86.   return TDBTraits::BindVariant(iIndex, Value, iSize);
  87.  }
  88.  CResultSet* FetchResult() const
  89.  {
  90.   return m_pResult;
  91.  }
  92.  /*implicit*/operator SQLStatementHandle() const
  93.  {
  94.   return m_pHandle
  95.  }
  96. private:
  97.  void Reset()
  98.  {
  99.   // it is OK to delete NULL
  100.   if (m_pResult != NULL)
  101.   {
  102.    delete m_pResult;
  103.    m_pResult = NULL;
  104.   }
  105.  }
  106. private:
  107.  CResultSet* m_pResult;
  108.  SQLStatementHandle m_pHandle;
  109. };
  110. template<class T, class TDBTraits = DBTraits >
  111. class CDBIterator
  112. {
  113. public:
  114.  typedef TDBTraits::DBHandle DBHandle;
  115.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  116.  typedef CDBRow RowType;
  117. public:
  118.  CDBIterator():m_bIsDone(false)
  119.  {
  120.  }
  121.  ~CDBIterator(){}
  122. public:
  123.  //++ operator
  124.  //forward increment
  125.  CDBIterator& operator++()
  126.  {
  127.   m_bIsDone = !(TDBTraits::Forward() );
  128.   return *this;
  129.  }
  130.  //backwards increment
  131.  CDBIterator operator++(int)
  132.  {
  133.   CDBIterator tmp(*this);
  134.   
  135.   m_bIsDone = !(TDBTraits::Forward() );
  136.   return tmp;
  137.  }
  138.  //-- operator
  139.  //no backwards operation
  140. public:
  141.  RowType& operator*()
  142.  {
  143.   return m_CurrentRow;
  144.  }
  145.  RowType* operator&()
  146.  {
  147.   return &m_CurrentRow;
  148.  }
  149.  RowType* operator->()
  150.  {
  151.   return &m_CurrentRow;
  152.  }
  153. public:
  154.  bool IsDone() const
  155.  {
  156.   return m_bIsDone;
  157.  }
  158. private:
  159.  bool m_bIsDone;
  160.  RowType m_CurrentRow;
  161. };
  162. template<class T, class TDBTraits = DBTraits >
  163. class CDBRow
  164. {
  165. public:
  166.  typedef TDBTraits::DBHandle DBHandle;
  167.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  168. public:
  169.  CDBRow(){}
  170.  ~CDBRow(){}
  171. public:
  172.  templateclass TValue >
  173.  bool FetchValue(int iColumn, TValue pRetValue)
  174.  {
  175.   SQLStatementHandle pStmt = TDBTraits::GetStatementHandle();
  176.   return TDBTraits::FetchValue(pStmt, iColumn, pRetValue);
  177.  }
  178.  int FetchBytes(int iColumn)
  179.  {
  180.   SQLStatementHandle pStmt = TDBTraits::GetStatementHandle();
  181.   
  182.   return TDBTraits::FetchColumnBytes(pStmt, iColumn);
  183.  }
  184. };
  185. template<class T, class TDBTraits = DBTraits >
  186. class CDBRowSet
  187. {
  188. public:
  189.  typedef TDBTraits::DBHandle DBHandle;
  190.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  191.  typedef CDBIterator iterator;
  192. public:
  193.  CDBRowSet()
  194.  :m_pIterator(NULL)
  195.  {
  196.  }
  197.  ~CDBRowSet()
  198.  {
  199.   if (m_pIterator != NULL)
  200.   {
  201.    delete m_pIterator;
  202.   }
  203.  }
  204. public:
  205.  bool IsNULL() const
  206.  {
  207.   return TDBTraits::HasReturnValue();
  208.  }
  209.  //for iterator
  210.  iterator& CreateIterator()
  211.  {
  212.   if (m_pIterator == NULL)
  213.   {
  214.    m_pIterator = new iterator;
  215.   }
  216.   
  217.   return *m_pIterator;
  218.  }
  219. private:
  220.  iterator* m_pIterator;
  221. };

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

//空类,留着扩展

 

 

  1. templateclass TBase >
  2. class CDatabaseImplRoot : public TBase
  3. {
  4. public:
  5.  CDatabaseImplRoot(){}
  6.  virtual ~CDatabaseImplRoot(){}
  7. public:
  8. //interface
  9. private
  10. //implementations
  11. };
  12. templateclass T, class TBase = CDatabase,class TDBTraits = DBTraits,  bool t_Managed = true >
  13. class CDatabaseImpl : public CDatabaseImplRoot
  14. {
  15. public:
  16.  typedef TDBTraits::DBHandle DBHandle;
  17.  typedef TDBTraits::SQLStatementHandle SQLStatementHandle;
  18.  typedef CDBIterator iterator;
  19.  typedef CDBStatement::CResultSet ResultSet;
  20. public:
  21.  CDatabaseImpl(){}
  22.  virtual ~CDatabaseImpl(){}
  23. protected:
  24.  CDBConnection< T, TDBTraits >* GetConnection()
  25.  {
  26.   return &m_Connection;
  27.  }
  28. private:
  29.  CDBConnection< T, TDBTraits, t_Managed> m_Connection;
  30.  CDBStatement< T, TDBTraits > m_pStatement;
  31. };

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

 

最终的CSQLiteDatabase是这样的:

 

  1. class CSQLiteDatabase : public CDatabaseImpl  
  2. {
  3. public:
  4.  typedef CDatabaseImpl Base;
  5.  typedef Base::iterator iterator;
  6.  typedef Base::ResultSet ResultSet;
  7. public:
  8.  CSQLiteDatabase();
  9.  virtual ~CSQLiteDatabase();
  10. public:
  11.  virtual bool Open(TCHAR const* pszFileName);
  12.  virtual bool Close();
  13.  virtual bool Execute(TCHAR const* pszSQLStatement);
  14. public:
  15.  template<class T>
  16.  bool BindValue(int iIndex, T Value, int iSize)
  17.  {
  18.   return GetConnection()->FetchStatement()->BindValue(iIndex,Value, iSize);
  19.  }
  20.  ResultSet* GetResult();
  21. private:
  22. };
  23. CSQLiteDatabase::CSQLiteDatabase()
  24. {
  25. }
  26. CSQLiteDatabase::~CSQLiteDatabase()
  27. {
  28. }
  29. bool CSQLiteDatabase::Open(TCHAR const* pszFileName)
  30. {
  31.  return GetConnection()->Open(pszFileName);
  32. }
  33. bool CSQLiteDatabase::Close()
  34. {
  35.  return GetConnection()->Close();
  36. }
  37. bool CSQLiteDatabase::Execute(TCHAR const* pszSQLStatement)
  38. {
  39.  return GetConnection()->FetchStatement()->Execute(pszSQLStatement);
  40. }
  41. CSQLiteDatabase::ResultSet* CSQLiteDatabase::GetResult()
  42. {
  43.  return GetConnection()->FetchStatement()->FetchResult();
  44. }

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

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

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

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

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

 

 

  1.  CSQLiteDatabase* pDatabase = GetDatabase();
  2.  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"));
  3.  if (bRet)
  4.  {
  5.   CSQLiteDatabase::ResultSet* pSet = pDatabase->GetResult();
  6.   //如果不是查询语句,则pSet == NULL
  7.   if (pSet != NULL)
  8.   {  
  9.    for (CSQLiteDatabase::iterator i = pSet->CreateIterator(); !i.IsDone() ; ++i)
  10.    {
  11.     int id;
  12.     tchar strChinese[MAX];
  13.     tchar strMath[MAX];
  14.     tchar strEnglish[MAX];
  15.     int iIndex = 0;
  16.     bRet = (*i).FetchValue(iIndex++,id);
  17.     bRet = (*i).FetchValue(iIndex++,strChinese);
  18.     bRet = (*i).FetchValue(iIndex++,strMath);
  19.     bRet = (*i).FetchValue(iIndex++,strEnglish);
  20.    }
  21.   }
  22.  }

怎么样很简单吧.

 

附 DBTraits 源代码:

  1. template< class TDatabase >
  2. struct DBTraits;
  3. template<>
  4. struct DBTraits
  5. {
  6.  typedef sqlite3* DBHandle;
  7.  typedef sqlite3_stmt* SQLStatementHandle;
  8. // typedef struct  
  9.  static bool Open(TCHAR const* pszFileName)
  10.  {
  11.   if (!Close())
  12.   {
  13.    return false;
  14.   }
  15.   assert(m_pDatabase == NULL);
  16.   
  17.   int iRet = SQLite_APITraits::Open(pszFileName,&m_pDatabase);
  18.   if (iRet != SQLITE_OK)
  19.   {
  20.    return false;
  21.   }
  22.   
  23.   assert(m_pDatabase != NULL);
  24.   
  25.   return true;
  26.  }
  27.  static bool Close()
  28.  {
  29.   if (m_pStatement != NULL)
  30.   {
  31.    int iRet = SQLite_APITraits::Finalize(m_pStatement);
  32.    if (iRet != SQLITE_OK)
  33.    {
  34.     return false;
  35.    }
  36.    m_pStatement = NULL;
  37.   }
  38.   
  39.   if (m_pDatabase != NULL)
  40.   {
  41.    int iRet = SQLite_APITraits::Close(m_pDatabase);
  42.    if (iRet == SQLITE_BUSY)
  43.    {
  44.     return false;
  45.    }
  46.    
  47.    m_pDatabase = NULL;
  48.   }
  49.   
  50.   return true;
  51.  }
  52.  static bool Execute(TCHAR const* pszSQLStatement)
  53.  {
  54.   if (m_pDatabase == NULL/* || m_pStatement == NULL*/)
  55.   {
  56.    return false;
  57.   }
  58.   
  59.   ResetStatement();
  60.   
  61.   int iRet = SQLite_APITraits::Prepare(m_pDatabase, pszSQLStatement, -1, &m_pStatement, NULL);
  62.   if (iRet != SQLITE_OK)
  63.   {
  64.    return false;
  65.   }
  66.   
  67.   assert(m_pStatement != NULL);
  68.   //BindValue(sqlite3_stmt *pStmt, int iIndex, T Value, int iSize = 0);
  69.   for (std::list >::iterator i = m_listBindValue.begin(); i != m_listBindValue.end(); ++i)
  70.   {
  71.    DBVariant Value= (*i);
  72.    if (Value.m_iType == DBVariant::VALUE_INT)
  73.    {
  74.     SQLite_APITraits::BindValue(m_pStatement, Value.m_iIndex, Value.m_iIntValue, Value.m_iSize);
  75.    }
  76.    else if (Value.m_iType == DBVariant::VALUE_DOUBLE)
  77.    {
  78.     SQLite_APITraits::BindValue(m_pStatement, Value.m_iIndex, Value.m_fDoubleValue, Value.m_iSize);
  79.    }
  80.    else if (Value.m_iType == DBVariant::VALUE_TEXT)
  81.    {
  82.     SQLite_APITraits::BindValue(m_pStatement, Value.m_iIndex, Value.m_pTextValue, Value.m_iSize);
  83.    }
  84.    else if (Value.m_iType == DBVariant::VALUE_BLOB)
  85.    {
  86.     SQLite_APITraits::BindValue(m_pStatement, Value.m_iIndex, Value.m_pBlobValue, Value.m_iSize);
  87.    }
  88.    else
  89.    {
  90.     assert(false);
  91.    }
  92.   }
  93.   m_listBindValue.clear();
  94.   
  95.   iRet = SQLite_APITraits::Step(m_pStatement);
  96.   if (iRet != SQLITE_DONE && iRet != SQLITE_ROW)
  97.   {
  98.    return false;
  99.   }
  100.   
  101.   if (iRet == SQLITE_ROW)
  102.   {
  103.    m_bHasReturnValue = true;
  104.   }
  105.   
  106. //  iRet = SQLite_APITraits::Finalize(m_pStatement);
  107. //  if (iRet != SQLITE_OK)
  108. //  {
  109. //   return false;
  110. //  }
  111.   return true;
  112.  }
  113.  static DBHandle& GetDBHandle()
  114.  {
  115.   return m_pDatabase;
  116.  }
  117.  static SQLStatementHandle& GetStatementHandle()
  118.  {
  119.   return m_pStatement;
  120.  }
  121.  static void ResetStatement()
  122.  {
  123.   if (m_pStatement != NULL)
  124.   {
  125.    int iRet = SQLite_APITraits::Finalize(m_pStatement);
  126.    if (iRet != SQLITE_OK)
  127.    {
  128.     assert(false);
  129.    }
  130.    m_pStatement = NULL;
  131.   }
  132.  }
  133.  static bool HasReturnValue()
  134.  {
  135.   return m_bHasReturnValue;
  136.  }
  137.  static bool Forward()
  138.  {
  139.   int iRet = SQLite_APITraits::Step(m_pStatement);
  140.   if (iRet == SQLITE_DONE)
  141.   {
  142.    ResetStatement();
  143.   }
  144.   
  145.   m_bHasReturnValue = (iRet == SQLITE_ROW);
  146.   return m_bHasReturnValue;
  147.  }
  148.  static int FetchColumnCount(SQLStatementHandle pStmt)
  149.  {
  150.   return SQLite_APITraits::ColumnCount(pStmt);
  151.  }
  152.  static int FetchColumnType(SQLStatementHandle pStmt, int iColumn)
  153.  {
  154.   return SQLite_APITraits::ColumnType(pStmt, iColumn);
  155.  }
  156.  static wchar_t const* FetchColumnName(SQLStatementHandle pStmt, int iColumn)
  157.  {
  158.   return SQLite_APITraits::ColumnName(pStmt, iColumn);
  159.  }
  160.  static int FetchColumnBytes(SQLStatementHandle pStmt, int iColumn)
  161.  {
  162.   return SQLite_APITraits::ColumnBytes(pStmt, iColumn);
  163.  }
  164.  template< class T >
  165.  static bool FetchValue(SQLStatementHandle pStmt, int iColumn, T pRetValue)
  166.  {
  167.   return SQLite_APITraits::FetchColumnValue(pStmt, iColumn, pRetValue);
  168.  }
  169. // template< class T >
  170. // static int BindValue(SQLStatementHandle pStmt, int iIndex, T Value, int iSize)
  171. // {
  172. //  return SQLite_APITraits::BindValue(pStmt, iColumn, Value, iSize);
  173. // }
  174.  template< class T >
  175.  static bool BindVariant(int iIndex, T Value, int iSize/* = 0*/)
  176.  {
  177.   return false;
  178.  }
  179.  template<>
  180.  static bool BindVariant<int>(int iIndex, int Value, int iSize/* = 0*/)
  181.  {
  182.   DBVariant Var;
  183.   Var.m_iType = DBVariant::VALUE_INT;
  184.   Var.m_iIndex = iIndex;
  185.   Var.m_iIntValue = Value;
  186.   Var.m_iSize = iSize;
  187.   m_listBindValue.push_back(Var);
  188.   return true;
  189.  }
  190.  template<>
  191.  static bool BindVariant<double>(int iIndex, double Value, int iSize/* = 0*/)
  192.  {
  193.   DBVariant Var;
  194.   Var.m_iType = DBVariant::VALUE_DOUBLE;
  195.   Var.m_iIndex = iIndex;
  196.   Var.m_fDoubleValue = Value;
  197.   Var.m_iSize = iSize;
  198.   m_listBindValue.push_back(Var);
  199.   
  200.   return true;
  201.  }
  202.  template<>
  203.  static bool BindVariantconst*>(int iIndex, TCHAR const* Value, int iSize)
  204.  {
  205.   DBVariant Var;
  206.   Var.m_iType = DBVariant::VALUE_TEXT;
  207.   Var.m_iIndex = iIndex;
  208.   Var.m_pTextValue = Value;
  209.   Var.m_iSize = iSize;
  210.   m_listBindValue.push_back(Var);
  211.   
  212.   return true;
  213.  }
  214.  template<>
  215.  static bool BindVariant<void const*>(int iIndex, void const* pData, int iSize)
  216.  {
  217.   DBVariant Var;
  218.   Var.m_iType = DBVariant::VALUE_BLOB;
  219.   Var.m_iIndex = iIndex;
  220.   Var.m_pBlobValue = pData;
  221.   Var.m_iSize = iSize;
  222.   m_listBindValue.push_back(Var);
  223.   
  224.   return true;
  225.  }
  226. private:
  227.  static DBHandle m_pDatabase;
  228.  static SQLStatementHandle m_pStatement;
  229.  static bool  m_bHasReturnValue;
  230.  static std::list > m_listBindValue;
  231. };

你可能感兴趣的:(C++)