β

C++通用跨数据库访问方案之一: 基础组件cdbc

Pansunyou's Weblog 518 阅读

C++读写具体的数据库是件简单的事情,比如你想要访问mysql可以链接mysqlclient.so,访问IBM DB2可以使用db2cli.so,访问Oracle可以使用oci.so,都有现成的API可以使用。麻烦的事情在于,如果你想要让你的程序能同时在各种数据库上跑,而你又不太愿意针对每种数据库写一遍代码,并且最好数据库切换只要改改配置文件就能完成。这个时候,就要看看有没有通用的封装好的数据库组件可以使用了。

PHP的世界里有什么?

java的世界有jdbc

jdbc是java database connectivity的缩写,它几乎是所有java程序访问数据库的基础组件,各种数据库厂商按照jdbc的接口规范实现驱动,程序员可以基本无差别地实现数据的增删改查,这使得java程序访问数据库即便对新手都从来不是问题。

C++有什么现成的可以用?

C++的世界里也有不少的东西可以拿来使用,我们先看看先驱们都有啥成果可借鉴的吧。

  1. unixODBC
  2. Poco.Data
  3. SOCI - The C++ Database Access Library
  4. OTL - Oracle, Odbc and DB2-CLI Template Library
  5. Visual C++ ADO Programming
  6. Apache Portable Runtime DBD API

这些类库都比较好地对不同数据库接口做了封装,部分屏蔽了差异,并且其中unixODBC和apr-db还实现了插件机制。除此之外,部分数据库厂家还做了夹杂SQL和C/C++的中间语言:eSQL(embeded SQL),或者称为proC。我对eSQL的排斥主要来自于几次对历史遗留eSQL代码的维护,线程安全在不同厂家有不同实现,而底层细节又不可知,出错了调试困难重重,这让我有心做一份纯C/C++的数据库访问组件。

我的方案: cdbc - C++ database connectivity

即出于学习的目的,也觉得自己写的代码能更好地实现自己的想法,我也搞出了自己的数据库读写方案。我的做法是尽量模仿jdbc的风格,定制一套C++的API,然后以动态库形式编写不同数据库的适配层,使用时通过字符串参数加载合适的驱动。

我实现了以下函数:

类名 备注
Driver 包含驱动信息,比如驱动名称,版本号,动态库路径等
Connection 对应一个真实的数据库连接
DatabaseMetaData 数据库信息,比如版本号,字符集等
Statement SQL执行对象,用于查询、执行动态绑定等
ResultSet 查询结果集
ResultSetMetaData 主要存储查询结果的字段名、字段类型等信息
AnyValue 存储字段值

最终使用形式:

文件名 备注
cdbc.hpp 应用程序编程接口
libABC.db.so 应用程序和驱动的接口
libABC.db.db2.so 基于IBM DB2 cli编写的"驱动"
libABC.db.oracle.so 基于Oracle occi编写的"驱动"
libABC.db.mysql.so 基于mysqlclient编写的"驱动"
libABC.db.sqlite.so 基于sqlite编写的"驱动"
libABC.db.freetds.so Linux/unix下的MS SQLServer的"驱动"

应用层调用示例

// 引用头文件
#include <cdbc.hpp>
void test()
{
  string url = ";dbDriver=db2;dbName=db1;dbHost=127.0.0.1;dbPort=50000;";
  string user = "user";
  string passwd = "passwd";
  Connection *pConn = NULL;
  Statement* pStm = NULL; 
  try
  {
    // 新建连接, 自动动态加载驱动
    pConn = DriverManager::connect(url, user, passwd);
    // 创建SQL执行环境
    pStm = pConn->createStatement();
    // 执行单个语句
    int affectCount = pStm->executeUpdate("UPDATE A SET B=C ");
    // 提交事务
    pConn->commit();
    // 执行查询
    {
      ResultSet& rs = pStm->executeQuery("SELECT * FROM A");
      // 结果集列信息
      ResultSetMetaData& rsmd = rs.getMetaData();
      for (int i=0; i<rsmd.getColumnCount(); i++)
      {
         printf("%s | ", rsmd.getColumnName(i).c_str());
      }
      for (;rs.next();)
      {
        int columnIndex = 0;
        printf("字段%s=[%d]\n", rsmd.getColumnName(columnIndex).c_str(), rs.getInt(columnIndex) );
        // ...
      }
    }
    // 执行存储过程
    {
      string sql = "CALL DBTEST_SP(?,?,?,?)";  // db2 style
      //string sql = "BEGIN DBTEST_SP(:1, :2, :3, :4); END;"; // oracle style
      Statement* pStm1 = pConn->prepareStatement(sql);
      // 或者也可以直接重用pStm
      // pStm->setSQL(sql);
      // 输入参数通过set***设置
      pStm1->setInt(0, 1234);
      pStm1->setString(1, "输入参数");
      // 输出参数通过registerOutParam设置
      pStm1->registerOutParam(2, eINTEGER);
      pStm1->registerOutParam(3, eVARCHAR, 255);
      // 执行语句
      pStm1->executeUpdate();
      // 提取输出参数: get***
      printf("OUT Var1=%lld\n", pStm1->getInt64(2));
      printf("OUT Var2=%s\n", pStm1->getString(3).c_str());
      pConn->freeStatement(pStm1);
    }
  }
  catch (SQLException& e) // 或者 catch (Exception& e)
  {
    // ...
  }
  pConn->freeStatement(pStm);
  delete pConn;
}

cdbc的优点

  1. 概念来自于jdbc易于理解,易于编写新数据库驱动;
  2. 数据库驱动插件化,使得应用程序的编译及链接不依赖具体数据库的头文件和库文件(运行时当然是要的);

cdbc的不足

  1. 对数据库底层驱动做了层包装,有部分性能损失;
  2. 只实现了部分jdbc的规范;
作者:Pansunyou's Weblog
一个C/C++程序员的日常记录
原文地址:C++通用跨数据库访问方案之一: 基础组件cdbc, 感谢原作者分享。

发表评论