在VC中使用OTL访问Oracle和程序发布
在VC中访问Oracle,可以使用ADO或ODBC,如果你比较强大,也可以直接使用OCI API,但我个人认为OTL是最佳选择,它是一套数据库访问C++模板库,全部代码都在otlv4.h头文件中,通过OTL不但可以访问Oracle数据库(使用OCI API),还可以访问DB2,或者使用ODBC连接字符串访问其他数据库。
本文是总结作者在使用OTL访问Oracle过程中遇到的问题和解决办法,方便以后查阅,如果你正准备在项目中使用OTL,也希望对你有所帮助。
如何使用OTL
整个OTL只的一个otlv4.h头文件(对于OTL V4.xx)版本,使用OTL只需要include该头文件就可以,但要编译则需要OCI的头文件和Lib文件。
#define OTL_ORA9I // Compile OTL 4.0/OCI9i
#include "otlv4.h"
以上声明表示使用Oracle 9i的OCI,需要从安装有Oracle 9i的机器上拷贝oci的头文件和Lib文件,并将路径加入到项目工程中。
初始化OTL
在使用OTL连接Oracle服务器之前,需要对OTL进行初始化,主要是初始化OCI库。
otl_connect m_db;
_putenv(const_cast<char*>("NLS_LANG=SIMPLIFIEDCHINESE_CHINA.ZHS16GBK"));
m_db.otl_initialize(TRUE);// 以线程安全模式初始化OCI环境
_putenv()函数用于设置Oracle的NLS_LANG环境变量,这样可以避免读取Oracle的中文数据记录时出现乱码。在网上的许多例子代码中,都是使用db.otl_initialize()无参数方式进行初始化的,因为这些例子都是写在控制台程序的main()函数中,以单线程方式运行,这样是没有问题的。可能你的连接是多线程的,或者创建了多个Oracle连接,这样的代码无法正常工作。给出TRUE参数强制OTL以多线程安全方式初始化OCI库,才可以在多线程环境下正常工作。
使用连接字符串
OTL完成初始化后,就可以连接Oracle数据库了,连接Oracle数据库需要为rlogon()函数提供一个连接字符串,一般是这样的:
[User Name]/[Password]@[TNS Alias]
这里的TNS Alias指在tnsnames.ora中配置TNS名,比如我的tnsnames.ora里有以下的配置
# Generated by Oracleconfiguration tools.
myora =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.1)(PORT= 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = ora)
)
)
哪么TNS Alias就是myora。
CString szConnectString = _T("user/xxx@myora");
try
{
m_db.rlogon(szConnectString,1);
}
catch (otl_exception& p)
{
TRACE(_T("Oracle connecterror(msg:%s, stm_text: %s, sqlstate: %s, var_info: %s)"), p.msg, p.stm_text, p.sqlstate, p.var_info);
}
使用该方式连接Oracle需要用户配置tnsnames.ora,比较麻烦并且容易出错。还有一种更简洁的方法是直接使用tnsnames.ora中myora等号后面的字符串作为TNS Alias,这样可以使得Oracle连接配置也可以像MySQL哪样,直接指定Oracle服务IP地址、端口号、服务名、用户和密码就可以连接。为此我写了如下的函数来实现对两种连接字符串方式的支持:
BOOL COCIConnect::Connect(LPCTSTRlpszUserName, LPCTSTRlpszPassword, BOOLbUseTNS/* = TRUE*/,LPCTSTR lpszTNSAlias/* = NULL*/,
LPCTSTR lpszHost/* =_T("127.0.0.1")*/, WORD wPort/* = 1521*/,BOOL bAutoCommit/* = TRUE*/)
{
m_bAutoCommit= bAutoCommit;
CString szConnectString;
if(!lpszTNSAlias || !*lpszTNSAlias)
szConnectString.Format(_T("%s/%s"), lpszUserName,lpszPassword);
else{
CStringszTNSAlias;
if(bUseTNS)
szTNSAllas = lpszTNSAlias;
else
szTNSAllas.Format(_T("(DESCRIPTION= (ADDRESS = (PROTOCOL = TCP)(HOST = %s)(PORT = %d)) (CONNECT_DATA = (SERVER =DEDICATED) (SERVICE_NAME = %s)))"), lpszHost,(int)wPort, lpszTNSAlias);
szConnectString.Format(_T("%s/%s@%s"), lpszUserName,lpszPassword, szTNSAlias);
}
try
{
m_db.rlogon(szConnectString,(int)bAutoCommit);
returnIsConnected();
}
catch(otl_exception& p)
{
TRACE(_T("COCIConnect::Connect error(msg:%s, stm_text: %s, sqlstate: %s, var_info: %s)"), p.msg, p.stm_text, p.sqlstate, p.var_info);
return FALSE;
}
catch(...)
{
TRACE(_T("COCIConnect::Connect error: Unknown error"));
return FALSE;
}
}
执行无返回数据集的SQL
DELETE、UPDATE操作都是无返回数据集的操作,好像没什么好说的,直接上代码:
BOOL COCIConnect::ExecSQL(LPCTSTRlpszSQL, int*pRowsAffected/* =NULL*/)
{
long lret = otl_cursor::direct_exec(
m_db, // OCI连接器
lpszSQL, // SQL语句
otl_exception::disabled // 禁止OTL异常
);
if(pRowsAffected)
*pRowsAffected= lret;
return(lret >= 0);
}
这里的pRowsAffected可以返回SQL执行所影响的记录数,比如执行DELETE时返回删除了多少行,执行UPDATE操作时返回更新了多少行。
流化读取数据记录
OTL的亮点就在于可以像C++的Stream操作那样读写数据记录,以下代码演示如何执行参数化的查询语句,并使用流化方式读取返回的数据记录集:
CString szSQL = _T("SELECTid,user,age FROM users WHERE age >= :age1<LONG> AND age <=:age2<LONG>");
try
{
otl_stream o(1000, szSQL,m_db);
o<< 25 << 45;
longlID, lAge;
charszUser[80];
while(!o.eof())
{
o>> lID >> szUser >> lAge;
//...
}
return TRUE;
}
catch (otl_exception& p)
{
TRACE(_T("SELECT error(msg:%s, stm_text: %s, sqlstate: %s, var_info: %s)"), p.msg, p.stm_text, p.sqlstate, p.var_info);
return FALSE;
}
catch(...)
{
TRACE(_T("SELECT error:Unknown error"));
return FALSE;
}
代码就贴到这里了,快到年关了,人有点懒,还有其他比如参数化执行SQL语句,读写BLOB数据等调用方法可以google到许多。如果还有问题请联系我。我们接着说下一个问题。
程序发布
由于OTL访问Oracle实际是使用OCI API,所以一般来说需要用户安装Oracle Client,这里提供更简洁的发布方案,用户不需要另行安装OracleClient,而是直接将Oracle Client随你的程序一起发布。为此,你需要到Oracle网站上下载一份InstantClient,我下载的是10.2.0.4 版的,只需要oci.dll和oraociei10.dll动态库文件,为了保证oci.dll在所有的计算机上可以使用,还需要MSVCR71.DLL动态库,这三个文件和你的应用程序放在同一个目录下即可。
如果想使用tnsnames.ora配置Oracle数据库连接,即可以将该文件和oci.dll动态库放在同一目录下,用户可以手工编辑该文件来配置Oracle连接。
当OCI访问异常时,会在当前目录生成一个sqlnet.log日志文件,而且这个文件会一直增长,变得非常庞大。我们可以在oci.dll动态库文件所在目录配置一个sqlnet.ora文件,内容如下:
# Generated by Oracleconfiguration tools.
LOG_DIRECTORY_CLIENT = NULL
SQLNET.AUTHENTICATION_SERVICES=(NTS)
NAMES.DIRECTORY_PATH=(TNSNAMES, ONAMES, HOSTNAME)
以上红色部分用于禁止输出sqlnet.log日志文件。我们总结一下,发布我们的应用程序需要额外为Oracle Client发布以下的文件:
- oci.dll
- oraociei10.dll
- MSVCR71.DLL
- tnsnames.ora
- sqlnet.ora
将这些文件和你的应用程序一起打成安装包,就可以实现真正的All in one了。