目录
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_ORA8Iint 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 tracing0x2 | // 2nd level of tracing0x4 | // 3rd level of tracing0x8 | // 4th level of tracing0x10; // 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 objectstd::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 ODBCm_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 ODBCm_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 variabledb.set_max_long_size(1024*1024); // set maximum long string size for connect objectotl_stream pRecordSet(1, // buffer size needs to be set to 1szSQL.c_str(),// SELECT statementdb // connect object); // create select streamint 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 0if (info.m_nOperType == 3)sprintf_s(szSQL, sizeof(szSQL),"delete from tbSyncKoalaPerson where nID=%d OR nOperType=3", info.m_nID);elsesprintf_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;
#elseotl_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 1szSQL,// SELECT statementdb // connect object); // create select streamif (!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++开发中使用。