数据库访问常用技术
DAO(Data Access Object)
底层是 JET 引擎,主要用来提供对 ACCESS 数据库的访问,比较新的版本也支持访问其他数据库,不过对于其他数据库,需经过 JET 的中间层,访问速度比较差。
在所有对 ACCESS 数据库的访问方法中, JET是最快的。最新的 JET Engine版本为4.0,对应的 DAO 版本为3.6,可以访问 ACCESS 2000 的数据库。
RDO(Remote Data Object)
底层是 ODBC,RDO仅仅是对ODBC API的一个薄包装层,封装的比较简单,因此也具有较快的速度,在ADO出现以前,是访问MS SQL Server最快的方法。有好几年没有更新了,原因是MS早已经决定将其淘汰。
ADO(ActiveX Data Object)
底层是 OLE DB,不仅能访问关系型数据库,也可以访问非关系型数据库,是现在最快速的数据库访问中间层啊!ADO对OLE DB的包装相当成功,对象模型简明扼要,没有一点多余的东西,功能还远超DAO、RDO。
ADO编程简介
引用说明
在相应头文件里,需要加上下面的代码:
#import "C:/Program Files/Common Files/System/ADO/msado15.dll" no_namespace rename("EOF", " adoEOF ") |
这行代码的作用是,告诉编译器去哪里找ADO的库文件(可能各个机器上路径有所不同),然后说明不用namespace,并且将 EOF 更名为 adoEOF,避免常量冲突。
COM库的初始化
ADO 以 COM 为基础,主要部分都是COM组件。初始化COM库这项工作通常在类的构造函数中完成。
HRESULT hr = S_OK; hr=::CoInitialize(NULL); //初始化COM库 if (!SUCCEEDED(hr)) //失败处理语句 |
主要对象
Connection用于建立数据库连接,执行不返回任何结果集的SQL语句。
Command用于返回结果集,并提供简单的方法执行存储过程或者任何返回结果集的SQL语句。
Recordset就是结果集,可进行数据的存取、滚动操作。
创建Connection对象并连接数据库
_ConnectionPtr是一个Connection的接口,在程序里创建它的实例,通过某个OLE DB provider指向一个数据源,并开启连接。
首先我们需要添加一个指向Connection对象的指针
_ConnectionPtr m_pConnection; |
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
try { //创建Connection对象 hr = m_pConnection.CreateInstance("ADODB.Connection"); if(SUCCEEDED(hr)) { hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=test.mdb", "","",adModeUnknown); } } catch(_com_error e)//捕捉异常 { //异常处理程序 } |
Connection对象的Open方法
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) |
ConnectionString为连接字串,UserID是用户名,Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权。Options可以是如下几个常量:
adModeUnknown:缺省。当前的许可权未设置
adModeRead:只读
adModeWrite:只写
adModeReadWrite:可以读写
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
adModeShareExclusive:阻止其它Connection对象以读写权限打开连接
adModeShareDenyNone:阻止其它Connection对象以任何权限打开连接
Connection对象中两个有用的属性
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如:
m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); |
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
if(m_pConnection->State) m_pConnection->Close(); ///如果已经打开了连接则关闭它 |
执行SQL命令并取得结果记录集
为了取得结果记录集,我们定义一个指向Recordset对象的指针:
_RecordsetPtr m_pRecordset; |
并为其创建Recordset对象的实例:
m_pRecordset.CreateInstance("ADODB.Recordset"); |
利用Connection对象的Execute方法执行SQL命令
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) |
CommandText是命令字串,通常是SQL命令,参数RecorRecordsAffected是操作完成后所影响的行数,参数Options表示CommandText中内容的类型。Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。
_variant_t RecordsAffected; //执行SQL命令:CREATE TABLE创建表格users,users包含四个字段: //整形ID,字符串username,整形old,日期型birthday m_pConnection->Execute( "CREATE TABLE users(ID INTEGER,username TEXT, old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); //往表格里面添加记录 m_pConnection->Execute( "INSERT INTO users(ID,username,old,birthday) VALUES (1, 'Washington',25,'1970/1/1')",&RecordsAffected,adCmdText); //将所有记录old字段的值加一 m_pConnection->Execute( "UPDATE users SET old = old+1", &RecordsAffected,adCmdText); //执行SQL统计命令得到包含记录条数的记录集 m_pRecordset = m_pConnection->Execute( "SELECT COUNT(*) FROM users", &RecordsAffected,adCmdText); _variant_t vIndex = (long)0; //取得第一个字段的值放入vCount变量 _variant_t vCount = m_pRecordset->GetCollect(vIndex); m_pRecordset->Close();///关闭记录集 |
利用Command对象来执行SQL命令
_CommandPtr m_pCommand; m_pCommand.CreateInstance("ADODB.Command"); _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 //非常关键的一句,将建立的连接赋值给它 m_pCommand->ActiveConnection = m_pConnection; m_pCommand->CommandText = “SELECT * FROM users”;///命令字串 //执行命令,取得记录集 m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText); |
直接用Recordset对象进行查询取得记录集
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) |
Source是数据查询字符串,ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象),CursorType是光标类型,LockType是锁定类型。LockType可以是以下几个常量:
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只读记录集
adLockPessimistic = 2,///悲观锁定方式。数据在更新时锁定其它所有动作
adLockOptimistic = 3,///乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
adLockBatchOptimistic = 4,//乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
数据集的遍历和更新
(MoveNext,MovePrev,MoveFirst,MoveLast,Update,AddNew,Delete)
Update方法有下面三种方法调用:给某个Field对象(或某些个)的Value属性赋值,然后调用Update方法;将字段名和字段值作为参数传给Update方法;将字段名和字段值的数组作为参数传给Update方法。
AddNew方法如下调用:直接调用,然后同Update调用方法一;将字段名数组、字段值数组作为参数传给AddNew方法。
Delete方法最简单,直接调用就行了,删除当前记录!
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday。以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
_variant_t vUsername,vBirthday,vID,vOld; _RecordsetPtr m_pRecordset; m_pRecordset.CreateInstance("ADODB.Recordset"); m_pRecordset->Open( "SELECT * FROM users", _variant_t((IDispatch*)m_pConnection,true), adOpenStatic,adLockOptimistic,adCmdText); while(!m_pRecordset->adoEOF) { //取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 vID = m_pRecordset->GetCollect(_variant_t((long)0)); vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 vOld = m_pRecordset->GetCollect("old"); vBirthday = m_pRecordset->GetCollect("birthday"); m_pRecordset->MoveNext();///移到下一条记录 } m_pRecordset->MoveFirst();///移到首条记录 m_pRecordset->Delete(adAffectCurrent);///删除当前记录 //添加三条新记录并赋值 for(int i=0;i<3;i++) { m_pRecordset->AddNew();///添加新记录 m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); m_pRecordset->PutCollect("username",_variant_t("叶利钦")); m_pRecordset->PutCollect("old",_variant_t((long)71)); m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); } //从第一条记录往下移动一条记录,即移动到第二条记录处 m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst)); //修改其年龄 m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45)); m_pRecordset->Update();///保存到库中 |
关闭记录集与连接
记录集或连接都可以用Close方法来关闭
m_pRecordset->Close();///关闭记录集 m_pConnection->Close();///关闭连接 |
_variant_t 和 _bstr_t
因为COM必须设计成跨平台,它需要一种更普遍的方式来处理字符串以及其他数据。这就是VARIANT数据类型的来历,还有BSTR类型。VARIANT就是一个巨大的 union,包含了你能想得到的所有的数据类型。
_variant_t是一个类,包装了VARIANT数据类型,并允许我们简单的对之进行强制类型转换,_bstr_t对BSTR干了同样的事情。
_variant_t Holder; Holder = MySet->GetCollect("FIELD_1"); m_List.AddString((char*)_bstr_t(Holder)); |
BLOB数据的保存
在实际的开发过程中我们常常需要存储较大的二进制数据对象,比如:图像、音频文件、或其它二进制数据,这些数据我们称之为二进制大对象BLOB(Binary Large Object),其存取的方式与普通数据有所区别。
BLOB类型的数据无法用普通的方式进行存储,我们需要使用AppendChunk函数。
AppendChunk包含在Field对象中,原型如下:
HRESULT AppendChunk (const _variant_t & Data ); |
从函数原型中可以看到关键的问题是我们需把二进制数据赋值给VARIANT类型的变量,下面我们给出具体的代码并作简单的分析:
首先我们建立一张名为userinfo的表,包含三个字段:id,username,old,photo,其中photo是一个可以存储二进制数据的字段。
//假设m_pBMPBuffer指针指向一块长度为m_nFileLen的二进制数据, //并且已经成功打开了记录集对象m_pRecordset char *pBuf = m_pBMPBuffer; VARIANT varBLOB; SAFEARRAY *psa; SAFEARRAYBOUND rgsabound[1]; m_pRecordset->AddNew();///添加新记录 m_pRecordset->PutCollect("username",_variant_t("小李"));///填充username字段 m_pRecordset->PutCollect("old",_variant_t((long)28);///填充old字段 if(pBuf) { rgsabound[0].lLbound = 0; rgsabound[0].cElements = m_nFileLen; psa = SafeArrayCreate(VT_UI1, 1, rgsabound);///创建SAFEARRAY对象 for (long i = 0; i < (long)m_nFileLen; i++) //将pBuf指向的二进制数据保存到SAFEARRAY对象psa中 SafeArrayPutElement (psa, &i, pBuf++); varBLOB.vt = VT_ARRAY | VT_UI1;///将varBLOB的类型设置为BYTE类型的数组 varBLOB.parray = psa;///为varBLOB变量赋值 //加入BLOB类型的数据 m_pRecordset->GetFields()->GetItem("photo")->AppendChunk(varBLOB); } m_pRecordset->Update();///保存我们的数据到库中 |
BLOB数据的读取
对应于保存数据时我们所使用的AppendChunk函数,读取数据应该使用GetChunk函数。GetChunk的原型如下:
_variant_t GetChunk (long Length ); |
给出数据的长度后GetChunk将返回包含数据的VARIANT类型变量,然后我们可以利用SafeArrayAccessData函数得到VARIANT变量中指向数据的char *类型的指针,以方便我们的处理,具体代码如下:
//得到数据的长度 long lDataSize = m_pRecordset->GetFields()->GetItem("photo")->ActualSize; if(lDataSize > 0) { _variant_t varBLOB; varBLOB = m_pRecordset->GetFields()->GetItem("photo")->GetChunk(lDataSize); if(varBLOB.vt == (VT_ARRAY | VT_UI1)) ///判断数据类型是否正确 { char *pBuf = NULL; ///得到指向数据的指针 SafeArrayAccessData(varBLOB.parray,(void **)&pBuf); /*****在这里我们可以对pBuf中的数据进行处理*****/ SafeArrayUnaccessData (varBLOB.parray); } } |
访问表:
MovieSet->Open("Movie ", m_conn.GetInterfacePtr(),
adOpenKeyset, adLockOptimistic, adCmdTable);