65.9K
CodeProject 正在变化。 阅读更多。
Home

EpOraLibrary - Oracle OCI 库的轻量级 C++ 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (13投票s)

2012 年 7 月 15 日

MIT

8分钟阅读

viewsIcon

58121

downloadIcon

840

这是“OraLib - Oracle OCI 库的轻量级 C++ 包装器”的替代项目。

目录   

  1. 引言
  2. 以下主题的注意事项 
  3. 连接处理 
  4. 执行命令 
    1. 数据定义语言 (DDL)
    2. 存储数据
    3. 事务
    4. 检索数据
    5. 执行 PL/SQL 块
  5. 用例示例
    1. 插入表行并检索序列值
    2. 检索特定表行
    3. 调用存储过程
    4. 调用包中的函数
  6. 我为什么要重写 Bobi B. 的 OraLib?
  7. 结论
  8. 参考  

简介

许多人经历了 Oracle OCCI 库的不兼容性和问题,在许多情况下,例如

  • Unicode 环境。
  • 与 MFC 库链接的 Visual C++ 项目。
  • 与其它多个库链接的 Visual C++ 项目。
  • 等等 

EpOraLibrary 解决了这些问题,使 Visual C++ 开发人员更容易在其开发项目中使用 Oracle 数据库操作。EpOraLibrary 是 Oracle OCI 库的自定义包装器库,它使用 Visual C++ 2008 开发。  

原始作品(OraLib)由 Bobi B. 完成,我使用 Visual Studio 2008 创建了新项目,并按如下方式修改了源代码 

  • 为支持 Unicode、DLL 等多种环境而进行的修改。
  • 对源代码编码约定的修改。
  • EpLibrary 2.0 链接,以统一源代码并更好地管理对象。

OraLib 在数据库操作功能上没有太大区别,因此本文将重点介绍由于修改而导致的与 OraLib 用法上的差异。 

(如果您对原始作品的更多细节感兴趣,请参阅 Bobi B. 的文章或他的详细文档) 

以下主题的注意事项 

以下主题最初来自 Bobi B. 编写的 OraLib 文章。唯一更改的是与他的库的差异(例如示例代码),大部分信息和描述都来自他的文章。我只是展示了来自他文章的信息,以便人们可以轻松访问。因此,所有的辛勤工作和贡献都应该归功于 Bobi B. 

他还慷慨地批准了在 EpOraLibrary 开发和发布过程中,自由引用他的 OraLib,没有任何限制。   

“关于资源所有权的一个注意事项:几乎所有类都有 `release` 方法,当用户代码不再需要对象实例时,期望调用该方法。在某些情况下,`release` 方法体为空,但这在将来可能会改变。在下面的所有代码示例中,都没有调用 `release` 方法。”(Bobi B.,OraLib)。但对于 EpOraLibrary,除了 `Connection` 类之外,大多数类还包含 `ReleaseObj` 方法来释放分配。这是通过从 EpLibrary 2.0 继承“SmartObject”类来完成的(请参阅此处以了解SmartObject 的用法)。然而,并非所有类都要求调用 `ReleaseObj` 方法,只有“`Statement`”和“`ResultSet`”对象在不再需要时需要调用“`ReleaseObj`”方法,以避免内存泄漏。 

连接处理  

OraLib 类似,EpOraLibrary 也有两种连接 Oracle 服务器的方式:将服务器主机名、登录 ID 和密码作为参数传递给 *"Connection"* 对象的构造函数,或者通过创建一个空白对象实例并在之后调用 *Open* 方法。无论哪种情况,如果连接失败,都将抛出 *Error*。 

参数的区别在于,现在使用 TCHAR 代替 char,以便支持 ASCII 和 UNICODE 环境项目。 

// OracleServer Hostname : 11.222.33.44
epol::Connection conn (_T("11.222.33.44"), _T("LoginID"), _T("Password") );
...
conn.Close();
 
// Or
 
epol::Connection conn();
conn.Open(
_T("11.222.33.44"), _T("LoginID"), _T("Password") );
... 
conn.Close(); 

可以通过调用 `close` 方法显式关闭连接,也可以在对象被删除或超出作用域时隐式关闭连接。在第一种情况下,对象实例可以在以后重复使用以连接到相同或不同的 Oracle 实例。

执行命令

数据定义语言 (DDL)  

DDL 命令的执行是最简单的情况。可以显式调用 `Connection` 对象的 `Execute` 方法,并传入要执行的 SQL 字符串。 

conn.Execute(_T("create table a (id, nemeric, name varchar2 (10))"));
...
conn.Execute(_T("drop table a") ); 

存储数据  

在 Oracle 数据库中存储数据最简单的方法是使用 SQL `insert` 语句。更复杂的情况是调用存储过程,但通常有两种情况:(1)要存储的数据可以包含在 SQL 语句中——作为文本——以及(2)要存储的数据可以通过绑定变量(或参数)传递。两种方法都有优缺点。在第一种情况下,您应该构建一个包含 SQL `insert` 语句(或存储过程名称)以及完全格式化数据值的文本字符串——例如通过调用 `epl::System::STprintf`。第二种方法要求 SQL `insert` 语句只包含绑定变量名称(否则它将始终是常量),并手动绑定命名变量并设置其值,而无需担心格式。以下是两者的示例: 

// common for both
long id_column;
TCHAR *name_column;
 
// 1st approach
TCHAR sql [100];
epl::System::STprintf (sql, _T("insert into a (id, name) values (%d, '%s')"),  id_column, name_column);
conn.Execute(sql); 
 
// 2nd approach
Statement &st = *conn.Prepare(_T("insert into a values (:n, :s)"));
st.Bind (_T(":n")) = id_column;
st.Bind (_T(":s")) = name_column;
st.Execute ();
st.ReleaseObj();  

OraLib 不同,现在支持包含 Unicode 文本的 SQL 语句,因此在 Unicode 文本支持下,第一种和第二种方法之间没有区别。然而,对于多个 SQL `insert` 语句(仅 `insert` 值不同),第二种方法仍然更好,可以顺序执行(与第一种方法相比,这也要快得多)。 

Statement &st = *conn.Prepare(_T("insert into a (id) values (:n)")); 
Parameter &p = st.Bind (_T(":n")); 
for (long i=0; i<1000; i++) 
{ 
    p = i; 
    st.Execute (); 
} 

事务 

通常在 Oracle 中,第一个与数据相关的 SQL 语句会创建一个隐式事务。例如,“`insert into a (id) values (1)`”会创建一个事务,该事务应显式关闭(提交或回滚),否则当连接关闭时它将隐式关闭。在事务关闭之前,所做的更改仅在该连接内部可见,并且在某些情况下其他连接可能会被阻塞。 

`Connection` 对象提供了两种事务处理方法:`Commit` 和 `Rollback`。如果您阅读源代码,您会发现它们只不过是对 `Connection.Execute` 的简单调用。无论如何,您应该考虑尽快关闭事务,因为可能会发生争用——无论是通过调用 `Connection.Commit`/`Connection.Rollback` 中的一个,还是通过在您的存储过程中包含 `Commit` / `Rollback`。

检索数据 

当需要检索数据时,有两种选择。选择取决于您希望检索多少数据。例如,当所需数据是一个标志或计数时,可以使用命名变量。但如果您需要获取数据行,则应使用游标 (`ResultSet`)。

使用命名变量进行数据检索与使用它们存储数据类似 

Statement &st = *conn.Prepare ( 
    _T("begin select count (id) into :n from a; end;") ); 
st.Bind (_T(":n") ); 
st.Execute (); 
num_rows = st [_T(":n")]; 

此方法适用于您希望将相同的命名变量用于输入和输出的情况。

要从显式 SQL `select` 语句中获取数据,请调用 `Connection.Select` 或 `Statement.Select`,具体取决于您是否需要提供一些输入数据(例如 `select` 条件)。 

// Connection.Select case
 
ResultSet &rs = *conn.Select (_T("select name from a"));
if (!rs.IsEod ())
    do
        cout << rs[_T("NAME")].ToString().c_str() << _T('\n');
    while (++rs);
rs.ReleaseObj();
 
// Statement.Select case
 
Statement &st = *conn.Prepare (_T("select id, name from a where id = :n") );
st.Bind (_T(":n")) = id_required;
ResultSet &rs1 = *st.Select ();
cout << _T('#') << rs1[_T("ID")].ToLong() << _T(' ') << rs1[2].ToString().c_str();
rs1.ReleaseObj();  

当 SQL `select` 语句执行并返回 `ResultSet` 对象时,可以通过两种方式访问列:(1)按名称(区分大小写)和(2)按索引(索引是基于 0 还是基于 1 在 *epOraDefines.h* 中配置)。

如果您需要执行多个 SQL `select` 语句,则应使用游标绑定变量(在下一节中描述)。 

执行 PL/SQL 块  

Oracle 数据库的强大功能之一是 PL/SQL。通过使用 EpOraLibrary,您可以执行 PL/SQL 块、传递输入参数并接收输出参数。输出参数甚至可以是 `ResultSet`(在 Oracle 文档中为 `cursor`)。以下示例将执行两个 SQL `select` 语句,并使用游标命名变量获取行: 

Statement &st = *conn.Prepare (
    _T("begin\nopen :c1 for select id, name from a;\nopen :c2 for select * from a;\nend;"));
st.Bind (_T(":c1"));
st.Bind (_T(":c2"));
st.Execute();
 
ResultSet &rs = st[_T(":c1")]; // id and name columns
Column &id_column = st[_T(":c1")].ToResultSet()[_T("ID")];
Column &name_column = rs [_T("NAME")];
if (!rs.IsEod ())
    do
        cout << _T('#') << id_column.ToLong() << _T(' ')
            << name_column.ToString().c_str() << _T('\n');
    while (++rs);
rs.ReleaseObj ();
...  

`ResultSet` 列可以通过每次需要列值时请求 `ResultSet` 来访问,也可以将其缓存到 `Column` 对象中。当然,第二种方法更快,但由于使用了后期绑定,语句应首先执行。 

用例示例

插入表行并检索序列值 

Oracle 使用“序列”概念来允许同时“插入”到单个表中(Microsoft SQL Server 使用“自动编号”列)。因为几乎每个现代系统都是由多个用户同时使用的,所以“`select max (id) from a_table`”这种方式绝对是错误的。但实际上检索新创建行的 id 列很简单: 

Statement &st = *conn.Prepare (
    _T("begin\ninsert into a (id, name) values (a_seq.nextval, :s);\n:n := a_seq.currval;\ncommit;\nend;"));
st.Bind (":s") = name_column;
st.Bind (":n");
st.Execute ();
cout << _T("newly created row's id = ") << st [":n"].ToLong();
st.ReleaseObj();  

当然,这应该放到存储过程中。

检索特定表行  

Statement &st = *conn.Prepare (
    _T("select col1, col2, col3 from table_name where id = :n") );
st.Bind (_T(":n") ) = id_we_re_looking_for;
ResultSet &rs = *st.Select ();
...
rs.ReleaseObj();
st.ReleaseObj();  

调用存储过程  

Statement &st = *conn.Prepare (
    _T("begin sp_name (:param1, :param2, :param3); end;"));
st.Bind (":param1", DT_TYPE) = param1_value;
st.Bind (":param2", DT_TYPE) = param2_value;
st.Bind (":param3", DT_TYPE) = param3_value;
st.Execute ();
...
st.ReleaseObj(); 

调用包中的函数  

Statement &st = *conn.Prepare (
    _T("begin :result := package_name.function_name (:param1, :param2, :param3); end;") );
st.Bind (":param1", DT_TYPE) = param1_value;
st.Bind (":param2", DT_TYPE) = param2_value;
st.Bind (":param3", DT_TYPE) = param3_value;
st.Bind (":result", DT_TYPE);
st.Execute ();
    // use st [":result"] here
...
st.ReleaseObj ();  

我为什么要重写 Bobi B. 的 OraLib?  

我曾遇到这样的情况:我不得不在 Unicode 环境中使用 Visual C++ 进行 Oracle 数据库操作,但是 Oracle 提供的 OCCI 库在使用 MFC 库时存在链接错误问题,并且未能正确支持 Unicode 环境。由于 Oracle 的 OCCI 也没有提供源代码,我无法解决这个问题。所以我尝试寻找提供源代码的自定义库。然后我找到了 Bobi B. 的 

OraLib。Bobi B. 的 OraLib 本身已经很好了,但我想让它完全支持 Unicode(OraLib 只有部分支持 Unicode)和所有不同的环境(动态链接库、静态链接库、Unicode、ASCII)。因此,EpOraLibrary 项目应运而生。 

结论  

EpOraLibrary 项目和本文的大部分功能和工作都源自 OraLib,因此所有的辛勤工作和贡献都应该归功于 Bobi B.。我之所以展示这些,是希望它能对有需要的人有所帮助。 

参考

历史

  • 2014 年 5 月 31 日:- 源文件已更新,包含 Alexander Khot 提供的错误修复
  • 2013 年 8 月 22 日:- 源文件已更新。 
  • 2013 年 8 月 20 日:- 根据 MIT 许可证重新分发  
  • 2012 年 1 月 24 日:- 下载链接已更新。
  • 2012 年 8 月 10 日:- 目录已更新。
  • 2012 年 7 月 23 日:- 源文件已更新。
  • 2012 年 7 月 15 日:- 提交文章。 
© . All rights reserved.