问题提出

之前使用的是的ODBC连接sql server数据库,有需要可参考我的这篇文章:

https://blog.csdn.net/lishenluo/article/details/107045918

相比ODBC网上说ADO是要好用一些,然后ODBC需要单独配置ODBC数据源,有点傻,不利于运维。所以改写程序,使用ADO实现。

实现

找对msado.dll库

本电脑应该就有,搜索下,然后复制出来放到工程的固定位置中。

代码实现

写代码实现数据库连接,选择操作,执行别的sql语句等通用实现。

.h

#pragma once
#include <Utils/ExportDefine.h>
#include <Utils/Config.h>

//收下引入命令会导致4146警告,通过些命令去除
//#pragma warning(disable:4146)

//引号里面的路径,可绝对的,可相对的
//重命名EOF与BOF以免与其他命名空间冲突
#ifdef _DEBUG
#import "../../_runtime/x64.Debug/msado15.dll" named_guids rename("EOF", "adoEOF"), rename("BOF", "adoBOF")
#else
#import "../../_runtime/x64.Release/msado15.dll" named_guids rename("EOF", "adoEOF"), rename("BOF", "adoBOF")
#endif

using namespace ADODB;

namespace adosql
{
    class UTILS_EXPORT AdoCenter
    {
    public:
        AdoCenter(const string& configFile);
        ~AdoCenter();
        //select数据
        int selectData(const string& sql, vector<string>& results);
        //执行delete和insert等操作
        int executeSql(const string& sql);
    private:
        bool connect();
    private:
        _ConnectionPtr m_pConn;
        _RecordsetPtr m_pRecordset;
        string m_connStr;
        string m_userId;
        string m_pwd;
    };
}

.cpp

#include<utils\AdoSql.h>
#include <Utils/LogUtil.h>
using namespace adosql;
using namespace utils;
AdoCenter::AdoCenter(const string& configFile)
{
    Configuration cfg(configFile);
    cfg.load();
    m_connStr = cfg.read("ado", "connectStr"); //连接串获取
    m_userId = cfg.read("ado", "userName");
    m_pwd = cfg.read("ado", "password");
    HRESULT hr = CoInitialize(NULL);
    if (SUCCEEDED(hr)) //返回值可判断初始化COM是否成功,请用SUCCEEDED来判断
    {
        connect();
    }
    else
    {
        LLogError("COM CoInitialize HRESULT error");
    }
}

AdoCenter::~AdoCenter()
{
    if (NULL != m_pRecordset)
    {
        m_pRecordset->Close();
    }
    if (NULL != m_pConn)
    {
        m_pConn->Close();
    }
    CoUninitialize();
}

bool AdoCenter::connect()
{
    if (NULL == m_pConn || adStateClosed == m_pConn->State)
    {
        try
        {  //Connecting

            if (!FAILED(m_pConn.CreateInstance(_uuidof(Connection))))  //设置连接超时时间
            {
                m_pConn->CommandTimeout = 30;                  //设置连接超时值,单位为秒

                if (!FAILED(m_pConn->Open((_bstr_t)(m_connStr.c_str()), "", "", adModeUnknown)))
                {
                    return true;
                }
            }
        }
        catch (_com_error e) 
        {
            LLogError("connect to db error:"<<(char*)(e.Description()));
        }
    }
    return false;
}

//改方法后文有改进版本
int AdoCenter::selectData(const string& sql, vector<string>& results)
{
    int ret = -1;
    if (NULL == m_pConn || adStateClosed == m_pConn->State)
    {
        connect();
    }

    try
    {
        //这里每次调用都创建实例比较慢   
        if (!FAILED(m_pRecordset.CreateInstance(__uuidof(Recordset))))
        {
            m_pRecordset->Open((_bstr_t)sql.c_str(), _variant_t((IDispatch*)m_pConn, true),
                adOpenKeyset, adLockOptimistic, adCmdText);
            long line = 0;
            while (NULL != m_pRecordset && !m_pRecordset->adoEOF && adStateClosed != m_pRecordset->State)
            {
                long count = m_pRecordset->Fields->Count;
                ++line;
                stringstream ss;
                ss << line;
                for (long i = 0; i < count; ++i)
                {
                    //按名称获取数据
                    //_variant_t Column("lastUpdDt");
                     //_variant_t RusultGet = m_pRecordset->Fields->GetItem(Column)->Value;

                    //按列序号,从0开始
                    _variant_t rusultGet = m_pRecordset->Fields->GetItem(long(i))->Value;
                   
                    char midData[MAXCHAR] = { 0 };
                    rusultGet.ChangeType(VT_BSTR);//统一转成字符串,否则下面有可能出错
                    WideCharToMultiByte(CP_ACP, 0, rusultGet.bstrVal, -1, midData, MAXCHAR, NULL, NULL);
                    ss << "," << midData;
                }
                results.emplace_back(ss.str());
                m_pRecordset->MoveNext();
            }
            m_pRecordset->Close();
            m_pRecordset = NULL;
            ret = 0;
        }
        else
        {
            LLogError("create m_pRecordset failed");
        }
    }
    catch (_com_error e)
    {
        LLogError("Execute sql error:" << (char*)(e.Description()) << " sql=" << sql);
    }
    return ret;
}

int AdoCenter::executeSql(const string& sql)
{
    int ret = -1;

    if (sql.size() > 0) 
    {
        if (NULL == m_pConn || adStateClosed == m_pConn->State)
        {
            connect();
        }

        try
        {
            _variant_t RefreshNum;
            m_pConn->Execute(_bstr_t(sql.c_str()), &RefreshNum, adCmdText);
            //ret = RefreshNum.lVal; //更新行数
            ret = 0;
        }
        catch (_com_error e)
        {
            LLogError("Execute sql error:"<< (char*)(e.Description())<<", sql:" << sql);
        }
    }
    return ret;
}


结论

网上说ADO比ODBC快,我实际应用好似是慢一些的,没有实际统计过。
上面的例子是我实际项目中的使用。多了一些配置文件读取,日志输出,项目导出UTILS_EXPORT设置,把这些删去就能编译过。简单好用。

参考

1.ADO编程详解(C++),连接串connectStr怎么获取参考这里。
2.ADO编程详解(C++),是1的转载和一些修改,排版好一些。不过例子有问题。 _RecordsetPtr 和 _RecordPtr 混用了。且实现得很繁琐,弄那么多方法做啥子。

[DBNETLIB][ConnectionRead (recv()).]一般性网络错误。请检查网络文档,问题处理

需要长时间运行程序,存数据库,如一两天,但是发现运行一段时间后,中间存库的时候报上面的异常。网络错误。查找了一通,都不行。直接用程序解决,重建建立数据库连接。

修改如下:

int AdoCenter::executeSql(const string& sql)
{
    int ret = -1;

    if (sql.size() > 0) 
    {
        if (NULL == m_pConn || adStateClosed == m_pConn->State)
        {
            reConnect();
        }

        try
        {
            _variant_t RefreshNum;
            m_pConn->Execute(_bstr_t(sql.c_str()), &RefreshNum, adCmdText);
            //ret = RefreshNum.lVal; //更新行数
            ret = 0;
        }
        catch (_com_error e)
        {
            LLogWarn("Execute sql error,retry,"<< (char*)(e.Description())<<" sql:" << sql);
            //偶发连接错误处理
            //报:[DBNETLIB][ConnectionRead (recv()).]一般性网络错误。请检查网络文档
            //处理 lsl-20200828
            //网上说的是Data Source=10.237.103.28,1433,后面要加上端口号
            string errorInfo(e.Description());
            if (errorInfo.find("DBNETLIB") != string::npos)
            {
                if (reConnect())
                {
                    ret = executeSql(sql);//再次执行
                }
                if (ret != 0)
                {
                    LLogError("retry Execute error,error:" << (char*)(e.Description()) << " sql:" << sql);
                }
            }
        }
    }
    return ret;
}

重连方法实现:

bool AdoCenter::reConnect()
{
    long  reConnectTimes = 0;
    while (true)
    {
        ++reConnectTimes;
        LLogWarn("re connect times:" << reConnectTimes);
        Sleep(2000);
        m_pConn = NULL;
        if (connect())//直到成功
        {
            return true;
        }
    }
    return false;
}

这里我用的是不成功不退出循环,你可以根据需要修改,如连接10次不成功就算了。

20200908 ado查询效率问题,改进

原来int AdoCenter::selectData(const string& sql, vector& results)方法实现有问题,每次都创建_RecordsetPtr实例,然后open,close。直接改用Execute函数,返回_RecordsetPtr就行。
代码如下:

int AdoCenter::selectData(const string& sql, vector<string>& results)
{
    int ret = -1;

    if (sql.size() > 0)
    {
        if (NULL == m_pConn || adStateClosed == m_pConn->State)
        {
            reConnect();
        }

        try
        {
            _variant_t RefreshNum;
            //改进,直接用Execute,返回_RecordsetPtr就很快,很棒!
            _RecordsetPtr pRecordset = m_pConn->Execute(_bstr_t(sql.c_str()), &RefreshNum, adCmdText);
            long line = 0;
            while (NULL != pRecordset && !pRecordset->adoEOF && adStateClosed != pRecordset->State)
            {
                long count = pRecordset->Fields->Count;
                ++line;
                stringstream ss;
                ss << line;
                for (long i = 0; i < count; ++i)
                {
                    //按列序号,从0开始
                    _variant_t rusultGet = pRecordset->Fields->GetItem(long(i))->Value;

                    char midData[MAXCHAR] = { 0 };
                    rusultGet.ChangeType(VT_BSTR);//统一转成字符串,否则下面有可能出错
                    WideCharToMultiByte(CP_ACP, 0, rusultGet.bstrVal, -1, midData, MAXCHAR, NULL, NULL);
                    ss << "," << midData;
                }
                results.emplace_back(ss.str());
                pRecordset->MoveNext();
            }
            pRecordset = NULL;
            ret = 0;
        }
        catch (_com_error e)
        {
            LLogWarn("Execute sql error,retry," << (char*)(e.Description()) << " sql:" << sql);
            //偶发连接错误处理
            //报:[DBNETLIB][ConnectionRead (recv()).]一般性网络错误。请检查网络文档
            //处理 lsl-20200828
            //网上说的是Data Source=10.237.103.28,1433,后面要加上端口号
            string errorInfo(e.Description());
            if (errorInfo.find("DBNETLIB") != string::npos)
            {
                if (reConnect())
                {
                    ret = selectData(sql, results);//再次执行
                }
                if (ret != 0)
                {
                    LLogError("retry Execute error,error:" << (char*)(e.Description()) << " sql:" << sql);
                }
            }
        }
    }
    return ret;
}

修改前后对比,快了差不多四倍。
全市场,1.5W次查询。
修改前:
[2020-09-08 11:09:50,052.022] [108716] [INFO] [eqdata::HqData::fillData] calc micro end! total time cost:43.79(minutes), this time cost: 1.34(seconds)

修改后:

[2020-09-08 15:32:55,124.280] [123444] [INFO] [eqdata::HqData::fillData] calc micro end! total time cost:12.45(minutes), this time cost: 0.38(seconds)

更多推荐