高性能Web Service数据库编程

来源:互联网 发布:淘宝客模式加活动 编辑:程序博客网 时间:2024/06/03 18:04
 

简介:... 1

启用ATL ServerData Source Cache支持... 1

实现数据库的交互... 2

创建ATL OLEDB使用者类... 2

提供插入记录的能力... 2

创建数据源连接对象... 3

使用UDL文件代替连接字符串... 4

Web Service调试... 4

性能评测... 5

 

简介:

       ATL Server是性能很高的Web ApplicationWeb Service的开发类库。到目前为止,我认为如果应用程序要和数据库交互,性能最高的办法是使用OLE DB,同时要启用ATL ServerData Source Cache

      

启用ATL ServerData Source Cache支持

       先来创建一个名为HighPerformance的工程,该工程将访问SQL Server数据库。在服务器选项中,选择“数据源缓存”。向导为我们创建了两个工程,一个是HighPerformance,一个是HighPerformanceIsapiHighPerformance是我们的应用程序逻辑所在之处,我们所有的Web Service的可公开函数都在这里。HighPerformanceIsapi是我们的ISAP扩展服务工程,IIS将在第一次Web Service被调用期间加载它,并且一直缓存,直到很长一段时间没有人使用我们的Web Service才释放。因此我们可以利用这个特性将需要缓存的全局变量保存到HighPerformanceIsapi工程中,然后每个应用程序线程(HighPerformance工程)可以通过调用QueryService来获得缓存的全局变量,并且调用全局变量的成员函数。

       HighPerformanceIsapi工程内部维护了一个线程池,每个线程对象内部都拥有自己的数据源缓存对象,请看代码:

// 逐线程数据源缓存

     typedef CDataSourceCache<> ds_cache_type;

     CComObjectGlobal<ds_cache_type> m_dsCache;

CComObjectGlobal用于保存在m_dsCache对象的生存期内,HighPerformanceIsapi服务器不会被卸载掉。CDataSourceCache类实现了IDataSourceCache接口。

HighPerformance工程中的应用程序逻辑通过QueryService请求全局对象时,只要传递

__uuidof(IDataSourceCache)即可。QueryService的实现代码如下:

HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService,

              REFIID riid, void** ppvObject)

     {

         if (InlineIsEqualGUID(guidService, __uuidof(IDataSourceCache)))

         {

              CIsapiWorker *pWorker = GetThreadWorker();

              if (pWorker)

              {

                   CDataSourceCache<> *pCache = NULL;

                   if (pWorker->GetWorkerData(_DATASOURCE_CACHE, (void **)&pCache))

                   {

                       *ppvObject = static_cast<IDataSourceCache *>(pCache);

                       return S_OK;

                   }

              }

         }

         return baseISAPI::QueryService(guidService, riid, ppvObject);

     }

 

实现数据库的交互

创建ATL OLEDB使用者类

       我们使用向导连接SQL SERVER (服务器名为GAO、用户名和密码为sa、数据库名为测试数据库、数据表为学生表),并支持属性化,生成支持写操作的OLE DB命令类。具体向导使用这里不叙述,请参考MSDN

       我们的OLE DB类名为CDataRowset。属性db_source是代表了连接信息,后面我将通过udl文件替代之。db_command属性中的SQL语句代表了查询表的操作,我们也可以使用Update或者insert语句代替之。

注意,我们的CDataRowset类创建在HighPerformance工程中,HighPerformanceIsapi工程中应该保存我们的全局数据源对象。

提供插入记录的能力

       我们需要修改db_command属性中绑定的SQL语句,以便我们插入一条语句。db_command(L"INSERT INTO [学生表] ([姓名],[年龄],[性别],[地址]) VALUES (?,?,?,?)")

     db_column属性修改为db_param属性,顺序请对应INSERT语句的四个?号。

     为了使我们的Web Service有更好的性能,我在工程中设置了使用UNICODE编码。

[

     db_command(L"INSERT INTO [学生表] ([姓名],[年龄],[性别],[地址]) VALUES (?,?,?,?)")

]

class CDataRowset

{

public:

     [ db_param(1,DBPARAMIO_INPUT)] TCHAR m_column0[20];

     [ db_param(2,DBPARAMIO_INPUT)] TCHAR m_column1[20];

     [ db_param(3,DBPARAMIO_INPUT)] TCHAR m_column2[20];

     [ db_param(4,DBPARAMIO_INPUT)] TCHAR m_column3[20];

 

     void GetRowsetProperties(CDBPropSet* pPropSet)

     {

         pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);

         pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);

         pPropSet->AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL);

         pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);

     }

};

     编写CAddRecord类,该类从CDataRowset类派生,这里只能使用继承而不是直接修改CDataRowset类,是因为db_command属性导致了CDataRowset类引入了一些隐藏类,这些隐藏类会导致一些意外行为。

     CAddRecord类将提供设置成员变量和清除成员变量的方法。代码如下:

void CAddRecord::Clear(void)

{

     //set member to zero

     m_column0[0]=0;

     m_column1[0]=0;

     m_column2[0]=0;

     m_column3[0]=0;

}

 

void CAddRecord::SetStudentInfo(BSTR Name,BSTR Age,BSTR Gender,BSTR Address)

{

     wcscpy(m_column0,Name);

     wcscpy(m_column1,Age);

     wcscpy(m_column2,Gender);

     wcscpy(m_column3,Address);

}

创建数据源连接对象

       删除CDataRowset类的db_source属性,该属性帮助我们创建CDataSource对象,我们这里不需要。

     定义连接字符串宏

#define CONN_STRINGW L"Provider=SQLOLEDB.1;Password=sa;Persist Security Info=True;User ID=sa;Initial Catalog=/x6d4b/x8bd5/x6570/x636e/x5e93;Data Source=GAO;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=FREEBIRD;Use Encryption for Data=False;Tag with column collation when possible=False"

     修改CHighPerformance类的代码:

HTTP_CODE InitializeHandler(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider)

{

         if (HTTP_SUCCESS != CSoapHandler<CHighPerformanceService>::InitializeHandler(pRequestInfo, pProvider))

              return HTTP_FAIL;

         if (S_OK != GetDataSource(  pProvider,

                                          CONN_STRINGW,

                                          CONN_STRINGW,

                                          &m_dc ))

              return HTTP_FAIL;

         return HTTP_SUCCESS;

}   

m_dc是该类的私有成员变量,类型为CDataConnection

[ soap_method ]

     HRESULT AddNewStudent(BSTR Name,BSTR Age,BSTR Gender,BSTR Address)

     {

         m_rec.SetStudentInfo(Name,Age,Gender,Address);

         HRESULT hr=m_rec.OpenRowset(m_dc);

         return hr;

     }

     m_rec定义为CAddRecord m_rec;

 

使用UDL文件代替连接字符串

       UDL文件中获取连接字符串,需要创建MSDAINITIALIZE对象,并调用IDataInitialize接口的LoadStringFromStorage方法才行。这种动作我并不想放到InitializeHandler函数中反复做。唯一的办法是放到ISAPI扩展DLL中。

    我们应该在类CHighPerformanceExtension中维护一个成员变量,当我们在应用程序DLL中通过QueryService查询某个接口(这里名叫IUdl)时,我们可以获得该变量的实现的IUdl接口,通过IUdl的方法GetConnectString获取连接字符串。这样我们就可以将该连接字符串作为参数传递给GetDataSource函数,从而取代宏。

     实现IUdl接口的对象将在创建时读取c:/gao.udl文件,并将连接字符串保存,IUdl::GetConnectString方法将返回连接字符串。

这里涉及到几个知识点,如何在ATL SERVER中开发一个COM-LIKE 接口,并且将接口暴露为服务;如通过OLE DB组件读取UDL文件。源代码如下,请参考:

#pragma once

 

//interface IUdl

__interface __declspec(uuid("F4FA35D6-D26D-4b5a-9544-A69F66E927B8"))

IUdl:public IUnknown

{

     HRESULT GetConnectString(BSTR* pConString);

};

 

#pragma once

 

//#include <stdafx.h>

 

#include "../CommonServiceDefinition.h"

#include <fstream>

using namespace std;

class CUdl :

     public IUdl

{

public:

     CUdl()

     {

         CComPtr<IDataInitialize> spDataInitialize;

        HRESULT hr = spDataInitialize.CoCreateInstance( __uuidof(MSDAINITIALIZE));

        if (SUCCEEDED(hr))

              hr=spDataInitialize->LoadStringFromStorage(CComBSTR("c://gao.udl"), &m_ConnString);

 

     }

     ULONG        STDMETHODCALLTYPE  AddRef()        {return 1;}

     ULONG        STDMETHODCALLTYPE Release()        {return 1;}

     HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject)

     {

         if( !ppvObject )

              return E_POINTER;

         if( IsEqualIID( riid, IID_IUnknown))

              *ppvObject = static_cast<IUnknown*>(this);

         else if( IsEqualIID( riid, __uuidof(IUdl)))

              *ppvObject = static_cast<IUdl*>(this);

         else

              return E_NOINTERFACE;

         AddRef();

         return S_OK;

     }

     HRESULT GetConnectString(BSTR* pConString)

     {

         if( pConString==NULL)

         {

              return E_INVALIDARG;

         }

 

         CComBSTR bstr(m_ConnString);

         *pConString=bstr.Detach();

         return S_OK;

     }

private:

     CComHeapPtr<OLECHAR> m_ConnString;

 

};

 

CHighPerformanceExtension中声明变量CUdl m_udl;并且改写QueryService方法

HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService,

              REFIID riid, void** ppvObject)

     {

         if (InlineIsEqualGUID(guidService, __uuidof(IDataSourceCache)))

         {

              CIsapiWorker *pWorker = GetThreadWorker();

              if (pWorker)

              {

                   CDataSourceCache<> *pCache = NULL;

                   if (pWorker->GetWorkerData(_DATASOURCE_CACHE, (void **)&pCache))

                   {

                       *ppvObject = static_cast<IDataSourceCache *>(pCache);

                       return S_OK;

                   }

              }

         }

         if(InlineIsEqualGUID(guidService,__uuidof(IUdl)) &&

              InlineIsEqualGUID(riid,__uuidof(IUdl)) )

         {

              return m_udl.QueryInterface(riid,ppvObject);

         }

         return baseISAPI::QueryService(guidService, riid, ppvObject);

}

 

我们的应用程序dll中的代码如下:

// HighPerformance.h : 定义 ATL Server 请求处理程序类

//

#pragma once

#include "addrecord.h"

#include "../CommonServiceDefinition.h"

namespace HighPerformanceService

{

// webservice 的所有 structenum typedefs 应进入命名空间

 

// IHighPerformanceService - Web 服务接口声明

//

[

     uuid("EECB2E3E-9CA8-4E07-8DE9-81A21E99E707"),

     object

]

__interface IHighPerformanceService

{

     [id(1)] HRESULT AddNewStudent([in]BSTR Name,[in]BSTR Age,[in]BSTR Gender,[in]BSTR Address);

};

 

 

// HighPerformanceService - Web 服务实现

//

[

     request_handler(name="Default", sdl="GenHighPerformanceWSDL"),

     soap_handler(

         name="HighPerformanceService",

         namespace="urn:HighPerformanceService",

         protocol="soap"

     )

]

class CHighPerformanceService :

     public IHighPerformanceService

{

public:

     HTTP_CODE InitializeHandler(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider)

     {

        

         if (HTTP_SUCCESS != CSoapHandler<CHighPerformanceService>::InitializeHandler(pRequestInfo, pProvider))

              return HTTP_FAIL;

 

         CComPtr<IUdl> spUdl;

         HRESULT hr=pProvider->QueryService(__uuidof(IUdl),&spUdl);

         if(hr!=S_OK)

              return HTTP_FAIL;

        

         CComBSTR connectstring;

         hr=spUdl->GetConnectString(&connectstring);

         if(hr!=S_OK)

         {

              return HTTP_FAIL;

         }

         if (S_OK != GetDataSource(  pProvider,

                                          connectstring,

                                          connectstring,

                                          &m_dc ))

         {

              return HTTP_FAIL;

         }

         return HTTP_SUCCESS;

     }   

 

     [ soap_method ]

     HRESULT AddNewStudent(BSTR Name,BSTR Age,BSTR Gender,BSTR Address)

     {

         m_rec.SetStudentInfo(Name,Age,Gender,Address);

         HRESULT hr=m_rec.OpenRowset(m_dc);

         return hr;

     }

private:

     CDataConnection m_dc;

     CAddRecord m_rec;

     // TODO: 在此添加其他 Web 服务方法

}; // CHighPerformanceService

 

} // 命名空间 HighPerformanceService

 

Web Service调试

MSDN提供一个例子---SOAPDebugApp 示例:在客户端内存空间中调试 XML Web services

地址:ms-help://MS.MSDNQTR.2003FEB.2052/vcsample/html/vcsamsoapdebugappsample.htm

       这种方式不同真正的WEB调用,而是通过直接调用应用程序DLL,这种方式很好,但是有局限性,由于我们的InitializeHandler要在每次调用WEB方法之前执行,并且每次都要从ISAPI扩展DLL线程池中打交道,而这种模拟调用不会导致IIS加载ISAPI扩展DLL。所以,InitializeHandler会失败,我们就没有办法对WEB 方法进行断点跟踪。解决之道有两种,一是用文件输出的方式,而是先不用扩展DLL缓存,当确信应用程序DLL已经没有问题后,再进行修改。但这两种方法都比较麻烦,不知道还有没有跟好的办法?

     另外,调试的时候IIS容易出现不正常,请用iisreset命令重启。

性能评测

我编写客户端调用AddNewSutdent方法,一切成功。三个客户端同时各自写入2万笔纪录,总数6万,好极了。性能感觉非常好。