Bootstrap

一个好用的C++数据库操作库:OTL

目录

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_stringotl_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++开发中使用。

;