高性能Web Service数据库编程

简介:... 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万,好极了。性能感觉非常好。

你可能感兴趣的:(web Service)