目录
1.简介
2.OTL库的核心类
3.OTL使用
4.使用OTL时注意事项
4.1.多线程初始化
4.2.OTL支持连接池
4.3.大字段的读取方式
4.4.指定数据库类型
4.5.异常处理
5.下载地址
6.总结
1.简介
OTL(Oracle, ODBC and DB2-CLI Template Library)是一个专为C++开发者设计的通用数据库操作模板库,它支持时下流行的大多数数据库,例如:Oracle、Mysql、PostgreSql、Sybase、Sqllite、MS ACCESS、Firebird等。并且它有跨平台的特性,使用起来也非常简单,在Windows、Linux、MacOS上都可以使用。
OTL是C++写的,based on templates, 只有一个头文件,大小只有800K+。使用方便,性能也很不错。它简化了数据库编程,提供了一种面向对象的方式来处理SQL语句和数据库连接。OTL库的核心是一个单一的头文件(如otlv4.h
),只需在代码中包含该文件,就能利用其提供的功能。此外,OTL库还提供了丰富的功能,包括对多种数据库的支持、流的概念、对大型对象的处理以及国际化支持等。
它的特点有:
1)跨平台性:OTL库是纯C++编写的,因此可以在多种操作系统上运行,如Windows、Linux、Unix和MacOSX等。
2)高效性:OTL库通过模板和底层数据库API的封装,提供了近乎直接调用数据库API的性能。
3)用性:OTL库提供了简洁的接口和面向对象的设计,使得数据库操作变得更加直观和高效。
4)丰富的功能:OTL库支持多种数据库类型、流的概念、对大型对象的处理、国际化支持等高级特性。
2.OTL库的核心类
1.otl_connect类:用于建立和管理数据库连接。
2.otl_stream类:是OTL库的核心流对象,用于执行SQL命令和绑定变量。它支持读写操作,可以方便地与STL容器配合使用。
3.otl_exception类:用于处理OTL库中的异常。当OTL流操作可能抛掷异常时,必须使用try/catch块来包裹OTL流的使用代码。
4.otl_long_string和otl_long_unicode_string类:用于存储和操作大型对象(LOBs),如BLOBs和CLOBs。
3.OTL使用
otl使用简单,只有一个头文件otlv4.h,通常我们在使用的时候只需要在项目头文件中包含:#include <otlv4.h>。
otl是根据宏定义来判断使用的是什么数据库驱动处理数据,例如我们需要连接的是postgepSql时,需要使用宏定义:
OTL_ODBC_POSTGRESQL或是OTL_ODBC(ODBC 为异构数据库访问提供统一接口)。
以下是一个简单的使用OTL库进行数据库操作的示例:
#include "otlv4.h"
// 指定数据库类型,例如连接Oracle数据库
#define OTL_ORA8I
int main() {
try {
// 创建数据库连接
otl_connect db;
db.rlogon("user", "password", "database");
// 插入数据
otl_stream o(1, "insert into test_tab values(:f1<int>, :f2<char[31]>)", db);
int i = 1;
char str[32] = "Test";
o << i << str;
// 执行查询
otl_stream si(50, "select * from test_tab", db);
while (!si.eof()) {
int id;
char name[32];
si >> id >> name;
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
// 断开数据库连接
db.logoff();
} catch (otl_exception& e) {
std::cerr << "OTL exception: " << e.msg << std::endl;
std::cerr << "SQL that caused the error: " << e.stm_text << std::endl;
}
return 0;
}
从上面示例可以看到,使用OTL跟其它库操作数据库的步骤是差不多的,先连接数据库,然后对数据库进行操作,最后关闭数据库连接;但是在实际的项目中,用到数据库的地方比较多,还需要对数据库的操作进行封装。
以下是实际开发项目上的封装的类,拿过去就可以用。
连接类:COTLWrapper
OTLConnect.h
#ifndef _OTL_CONNECT_H_
#define _OTL_CONNECT_H_
#define OTL_ODBC // Compile OTL 4/ODBC, MS SQL 2008
#define OTL_STL // Turn on STL features
#include "DBCommon.h"
#include <string>
#include <iostream>
#include <stdio.h>
//#include "Log.h"
using namespace std;
/*
unsigned int my_trace_level=
0x1 | // 1st level of tracing
0x2 | // 2nd level of tracing
0x4 | // 3rd level of tracing
0x8 | // 4th level of tracing
0x10; // 5th level of tracing
// each level of tracing is represented by its own bit,
// so levels of tracing can be combined in an arbitrary order.
#define OTL_TRACE_LEVEL 0x1F
// enables OTL tracing, and uses my_trace_level as a trace control variable.
#define OTL_TRACE_STREAM theLog
// directs all OTL tracing to cerr
#define OTL_TRACE_LINE_PREFIX "MY OTL TRACE ==> "
// redefines the default OTL trace line prefix. This #define is optional
*/
#define OTL_ODBC_MSSQL_2008 // Compile OTL 4/ODBC, MS SQL 2008
//#define OTL_ODBC // Compile OTL 4/ODBC. Uncomment this when used with MS SQL 7.0/ 2000
#include "otlv4.h"
class COTLWrapper
{
friend class IOTLTableBase;
public:
COTLWrapper();
COTLWrapper(const stDatabaseConfig& config);
virtual ~COTLWrapper();
public:
static void InitOTL();
public:
BOOL Connect(const stDatabaseConfig& stSrvConfig);
BOOL IsConnected() const {return m_bConnected;}
std::string GetErrorMsg() const {return m_szError;}
void Close();
protected:
BOOL Connect();
private:
BOOL m_bConnected;
stDatabaseConfig m_stDBConfig;
otl_connect m_db; // connect object
std::string m_szError;
};
#endif
OTLConnect.cpp
#include "stdafx.h"
#include "OTLConnect.h"
//#include <assert.h>
//#include "Log.h"
COTLWrapper::COTLWrapper(const stDatabaseConfig& config)
: m_bConnected(false)
,m_stDBConfig(config)
{
}
COTLWrapper::COTLWrapper()
: m_bConnected(false)
{
}
COTLWrapper::~COTLWrapper()
{
Close();
}
void COTLWrapper::InitOTL()
{
otl_connect::otl_initialize(); // initialize ODBC environment
}
BOOL COTLWrapper::Connect(const stDatabaseConfig& stSrvConfig)
{
m_stDBConfig = stSrvConfig;
return Connect();
}
void COTLWrapper::Close()
{
m_db.logoff(); // disconnect from ODBC
m_bConnected = FALSE;
}
BOOL COTLWrapper::Connect()
{
try
{
char szTemp[255] = { 0 };
sprintf_s(szTemp, sizeof(szTemp), "driver=sql server;server=%s;UID=%s;PWD=%s;database=%s", m_stDBConfig.m_szSrvName,
m_stDBConfig.m_szUserName, m_stDBConfig.m_szPassword,m_stDBConfig.m_szDBName);
m_db.rlogon(szTemp); // connect to ODBC
m_bConnected = TRUE;
//theLog.Logf("系统初始化1,数据库连接成功");
return TRUE;
}
catch(otl_exception& p)
{
m_szError = (char*)p.msg;
//cerr<<p.stm_text<<endl; // print out SQL that caused the error
//cerr<<p.var_info<<endl; // print out the variable that caused the error
//theLog.Logf("系统初始化1,连接数据库失败,%s", p.msg);
return FALSE;
}
}
数据访问基类:IOTLTableBase
class COTLWrapper;
class IOTLTableBase
{
public:
IOTLTableBase(COTLWrapper* pConnection) : m_pConnection(pConnection) {}
virtual ~IOTLTableBase() {}
protected:
BOOL ConnectDatabase() const;
otl_connect& GetConnection() const;
private:
COTLWrapper* m_pConnection;
};
BOOL IOTLTableBase::ConnectDatabase() const
{
if (m_pConnection->IsConnected())
return TRUE;
return m_pConnection->Connect();
}
otl_connect& IOTLTableBase::GetConnection() const
{
return m_pConnection->m_db;
}
数据访问类:COTLPersonOper
class COTLPersonOper : public IOTLTableBase
{
public:
COTLPersonOper(COTLWrapper* pConnection) : IOTLTableBase(pConnection) {}
virtual ~COTLPersonOper() {}
public:
BOOL LoadOneNoAsyncPersonInfo(stKoalaPersonItem& info);
BOOL WriteUploadRemark(const stKoalaPersonItem& info);
BOOL LoadOnePersonInfoFromRecord(stDoorRecordItem& info);
private:
BOOL SaveDataToLocalFile(std::string& szPath, const unsigned char* pData, int nLen) const;
};
///
/**********************************************************************************************
tbSyncKoalaPerson.nKKPersonID 一定要用同步表的人员ID,防止在Persons删除人员的时候,Persons表的
人员ID为, 然后再相机里面就删不掉这个人
***********************************************************************************************/
BOOL COTLPersonOper::LoadOneNoAsyncPersonInfo(stKoalaPersonItem& info)
{
if (!ConnectDatabase())
return FALSE;
std::string szSQL,szTemp;
BOOL bResult = FALSE;
try
{
memset(info.m_szKKIdentifyImage, 0, sizeof(info.m_szKKIdentifyImage));
//szSQL = "SELECT top 1 nID,PersonID,nOperType,Name,Date2,CardID,Photo from tbSyncKoalaPerson \
// LEFT JOIN Persons ON tbSyncKoalaPerson.nKKPersonID = Persons.PersonID where tbSyncKoalaPerson.bIsKoalaUpdate=0";
szSQL = "SELECT top 1 nID, \
ISNULL(tbSyncKoalaPerson.nKKPersonID,0) AS RealPersonID,\
nOperType AS PersonType, \
ISNULL(Name,'') AS PersonName, \
ISNULL(Date1,'') AS PersonStartDate, \
ISNULL(Date2,'') AS PersonEndDate, \
ISNULL(CardID,0) AS PersonCardID, \
Photo AS PersonPhoto \
from tbSyncKoalaPerson \
LEFT JOIN Persons \
ON tbSyncKoalaPerson.nKKPersonID = Persons.PersonID \
where tbSyncKoalaPerson.bIsKoalaUpdate=0";
otl_connect& db = GetConnection();
otl_long_string byPhoto(1024*1024); // define long string variable
db.set_max_long_size(1024*1024); // set maximum long string size for connect object
otl_stream pRecordSet(1, // buffer size needs to be set to 1
szSQL.c_str(),
// SELECT statement
db // connect object
);
// create select stream
int nCardID = 0;
int nPersonID = 0;
if (!pRecordSet.eof()) // while not end-of-data
{
pRecordSet>>info.m_nID>>nPersonID>>info.m_nOperType>>info.m_szPersonName>>info.m_szEntryDate>>info.m_szEndDate>>nCardID>>byPhoto;
info.m_dwKKPersonID = (DWORD)nPersonID;
info.m_dwCardID = (DWORD)nCardID;
if (byPhoto.len() > 0)
{
szTemp.empty();
if (SaveDataToLocalFile(szTemp, &byPhoto[0], byPhoto.len()))
_tcscpy_s(info.m_szKKIdentifyImage, sizeof(info.m_szKKIdentifyImage), szTemp.c_str());
}
bResult = TRUE;
}
return bResult;
}
catch (otl_exception& p)
{
//m_szError = (char*)p.msg;
theLog.Logf("获取最新同步记录失败,%s", p.msg);
return FALSE;
}
}
BOOL COTLPersonOper::SaveDataToLocalFile(std::string& szPath, const unsigned char* pData, int nLen) const
{
std::string szTemp;
if (pData && nLen)
{
szTemp = GetJpgFileName();
if (SaveJpgFile(szTemp, (char*)pData, nLen))
{
szPath = szTemp;
return TRUE;
}
}
return FALSE;
}
BOOL COTLPersonOper::WriteUploadRemark(const stKoalaPersonItem& info)
{
if (!ConnectDatabase())
return FALSE;
char szTime[20] = { 0 };
char szSQL[255] = { 0 };
memset(szTime, 0, sizeof(szTime));
GetSysTime(0, szTime, sizeof(szTime));
try
{
#if 0
if (info.m_nOperType == 3)
sprintf_s(szSQL, sizeof(szSQL),"delete from tbSyncKoalaPerson where nID=%d OR nOperType=3", info.m_nID);
else
sprintf_s(szSQL, sizeof(szSQL),"update tbSyncKoalaPerson set bIsKoalaUpdate=1,nKoalaPersonID=%d,szLastOperTime='%s' where nID=%d AND (nOperType=1 OR nOperType=2)", info.m_dwKoalaPersonID, szTime,info.m_nID);
long lResult = otl_cursor::direct_exec( GetConnection(), szSQL);
//return (1 == lResult);
return TRUE;
#else
otl_connect& db = GetConnection();
if (info.m_nOperType == 3)
{
otl_stream o(1, "delete from tbSyncKoalaPerson where nID=:1<int> OR nOperType=3", db);
o<<info.m_nID;
}
else
{
otl_stream o(1, "update tbSyncKoalaPerson set bIsKoalaUpdate=1,szLastOperTime=:1<char[20]> where nID=:2<int> AND (nOperType=1 OR nOperType=2)", db);
o<<szTime<<info.m_nID;
}
return TRUE;
#endif
}
catch (otl_exception& p)
{
theLog.Logf("更新人员[%d]上传标记失败1,%s", info.m_dwKKPersonID, p.msg);
return FALSE;
}
}
///
BOOL COTLPersonOper::LoadOnePersonInfoFromRecord(stDoorRecordItem& info)
{
if (!ConnectDatabase())
return FALSE;
int nTemp = 0;
char szSQL[512] = {0};
char szSex[10] = {0};
BOOL bResult = FALSE;
try
{
sprintf_s(szSQL, sizeof(szSQL),
"SELECT CardID,CardNum,Sex,DeptID,Dept2Index,ISNULL(WorkNum,'') AS PersonWorkNum,ISNULL(Date1,'') AS PersonEntryDate,ISNULL(duty,'') AS PersonDuty,ISNULL(Phone,'') AS PersonPhoto from Persons where PersonID=%d",
atol(info.m_szPersonID));
otl_connect& db = GetConnection();
otl_stream pRecordSet(1, // buffer size needs to be set to 1
szSQL,
// SELECT statement
db // connect object
);
// create select stream
if (!pRecordSet.eof()) // while not end-of-data
{
pRecordSet>>nTemp>>info.m_szCardNum>>szSex>>info.m_nDeptID>>info.m_nSubDeptID>>info.m_szWorkNO>>info.m_szEntryTime>>info.m_szDuty>>info.m_szTel;
info.m_dwCardID = (DWORD)nTemp;
if (_tcslen(info.m_szCardNum) <= 0)
_tcscpy_s(info.m_szCardNum, sizeof(info.m_szCardNum), "00000000");
info.m_ucGender = (0 == strcmp(szSex, "男"))?0:1;
bResult = TRUE;
}
return bResult;
}
catch (otl_exception& p)
{
theLog.Logf("获取系统人员信息失败1,%s", p.msg);
return FALSE;
}
}
4.使用OTL时注意事项
4.1.多线程初始化
static int otl_connect::otl_initialize(const int threaded_mode=0);
如果在多线程环境下使用,threaded_mode设置为1
注意:即使设置为1并不代表就是线程安全(thread-safe)
实际上OTL并不是线程安全的,一个otl_connect只能同时被一个线程使用,如果在多线程环境下使用OTL,需要自己保证otl_connect对象的线程安全
4.2.OTL支持连接池
一般封装OTL连接池的思路:
1) 建立多条与数据库的连接otl_connect ,并根据需要,将连接信息和连接的句柄封装到数据结构中(存放单条连接);
2) 使用连接管理器管理封装好的连接;
3) Sql操作通过流的方式来使用连接
4) 每次执行sql语句时,首先是从现有的连接中找,有,从现有连接中拿一条,替换流;没有即添加,如果添加连接数量超过最大值,等待已有连接运行结束。
OTL支持stream pool,就是一个池,在一个otl_stream close时,把它放到池中,下次访问时可以从池中获取,实现fast reopen,提高程序性能
注意:otl_stream_pool是otl_connect的一个成员,所以要在otl_connect锁unlock之前执行otl_stream.close,否则会出现死锁),即使不使用otl_stream_pool,也要在otl_connect锁unlock之前执行otl_stream.close。
经验证,OTL在多线程环境可以稳定运行
使用otl_stream_pool可以获得一定的性能提升
4.3.大字段的读取方式
连接的流中需要开启lob_stream模式:set_lob_stream_mode(true)。
otl_lob_stream lob;
otl_long_string long_str(40960);
otl_stream >> lob;
while (!lob.eof())
{
lob>> long_str;
//todo...
}
lob.close();
4.4.指定数据库类型
在使用OTL库时,需要通过预处理器宏定义来确定连接的目标数据库。例如,#define OTL_ORA8I
用于连接Oracle 8i数据库。
4.5.异常处理
OTL流操作可能抛掷异常,因此必须使用try/catch块来包裹OTL流的使用代码,以拦截异常并阻止程序异常终止。
5.下载地址
地址:Oracle, Odbc and DB2-CLI Template Library Programmer's Guide
6.总结
OTL库是一个功能强大、高效且易用的C++数据库访问库。它提供了丰富的功能和简洁的接口,使得C++开发者能够方便地进行数据库操作。可以使用OTL访问基本上所有的数据库,在你更换数据库时不用修改任何业务代码。
强烈推荐在C++开发中使用。