现代数据库系统除了支持一些标准的通用数据类型以外,大多数还支持一种称之为BLOB型的数据。

BLOB全称为big large object bytes, 大二进制对象类型,这种类型的数据通常用于存储文档、图片、音频等文件,这些文件一般体积较大,保存这些文件可以很方便的管理和检索这类信息。在MS SQLSERVER中常见的BLOB数据类型有text、ntext(n表示unicode)、image、nvarchar、varchar、varbinary等。其中image基本可以用来保存一切二进制文件,比如word、Excel、音频、视频等等类型。

针对BLOB型数据,OLEDB也提供了对它的支持

使用BLOB型数据的利弊

一般数据库对BLOB型数据有特殊的处理方式,比如压缩等等,在数据库中存储BLOB数据可以方便的进行检索,展示,备份等操作。但是由于BLOB型数据本身比较大,存储量太大时数据量太大容易拖慢数据库性能,所以一般的说法都是尽量不要在数据库中存储这类信息。特别是图片,音视频。针对这类文件一般的做法是将其保存在系统的某个路径钟中,而在数据库中存储对应的路径

操作BLOB型数据的一般方法

一般针对BLOB不能像普通数据那样操作,而需要一些特殊的操作,在OLEDB中通过设置绑定结构中的一些特殊值最终指定获取BLOB型数据的一个ISequentialStream接口指针,最终会通过这个接口来进行BLOB型数据的读写操作

判断一个列是否是BLOB型数据

判断某个列是否是BLOB型数据一般通过如下两个条件:

  1. pColumnInfo[i].wType == DBTYPE_IUNKNOW : 包含当列信息的DBCOLUMNSINFO 结构体对象的wType值为DBTYPE_IUNKNOW,该列的类型为DBTYPE_IUNKNOW,该条件也被称为列类型判定
  2. pColumnInfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG :当列信息中的dwFlag值为DBCOLUMNFLAGS_ISLONG,也就是说该列的标识中包含DBCOLUMNFLAGS_ISLONG属性,该判定条件也被称之为列标识判定

    当这两个条件之一成立之时,我们就可以断定这列为BLOB型数据

BLOG型数据的绑定

在进行BLOB型数据的绑定也有特殊要求,主要体现在下面几点:

  1. 绑定结构的cbMaxLength 需要设置为0
  2. 绑定结构的wType设置为DBTYPE_IUNKNOW
  3. 为结构的pObject指针分配内存,大小等于DBOBJECT结构的大小
  4. 指定pObject的成员

    pObject->iid = IID_ISequentialStream

    pObject->dwFlags = STGM_READ
  5. 为行缓冲长度加上一个IStream指针的长度,此时数据源不再提供查询到的数据而提供一个接口指针,后续对BLOB数据的操作都使用该指针进行

    最后使用完后记得释放pObject所指向的内存空间

读取BLOB数据

根据前面所说的创建绑定结构,并为绑定结构赋值,最终可以从结果集中获取到一个ISequentialStream接口指针。调用接口的Read方法可以读取到BLOB列中的数据,而BLOB数据的长度存储在绑定时指定的数据长度内存偏移处,这与普通列的长度存放返回方式是一样的,一般BLOB数据都比较长,这个时候就需要分段读取。

在使用ISequentialStream接口操作BLOB型数据时需要注意的一个问题是,有的数据库不支持在一个访问器中访问多个BLOB数据列。一般BLOB数据列及其的消耗资源,并且数据库鼓励我们在设计数据库表结构的时候做到一行只有一列BLOB数据,因此很多数据库并不支持在一个访问器中读取多个BLOB数据。

要判断数据库是否支持在一个访问器中读取多个BLOB数据,可以获取DBPROP_MULTIPLESTORAGEOBJECTS属性,该属性属于属性集DBPROPSET_ROWSET,它是一个只读属性,如果该属性的值为TRUE表示支持,为FALSE表示不支持。

下面是一个读取BLOB型数据的例子,数据库中的表结构为:id(int)、text(image)、png(image)、jpg(image)

void ReadBLOB(IRowset *pIRowset)
{
COM_DECLARE_INTERFACE(IColumnsInfo);
COM_DECLARE_INTERFACE(IAccessor); DBORDINAL cColumns = 0;
DBCOLUMNINFO* rgColumnsInfo = NULL;
LPOLESTR lpszColumnsName = NULL;
DBBINDING* rgBindings = NULL;
DBBINDING** ppBindings = NULL; //绑定结构数组
DWORD *puDataLen = NULL; //当前访问器所需内存大小
DWORD *pulColCnt = NULL; //当前访问器中包含的项
ULONG ulBindCnt = 0; //访问器的数量
ULONG uBlob = 0; //当前有多少blob数据
HACCESSOR* phAccessor = NULL;
HROW* hRow = NULL;
DBCOUNTITEM ulGetRows = 0;
ULONG uCols = 0; PVOID pData1 = NULL; //第1个访问器中数据的缓冲
PVOID pData2 = NULL; //第2个访问器中数据的缓冲
PVOID pData3 = NULL; //第3个访问器中数据的缓冲 HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo);
COM_SUCCESS(hRes, _T("查询接口pIColumnsInfo失败,错误码为:%08x\n"), hRes);
hRes = pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnsInfo, &lpszColumnsName);
COM_SUCCESS(hRes, _T("获取结果集列信息失败,错误码为:%08x\n"), hRes); ppBindings = (DBBINDING**)COM_ALLOC(sizeof(DBBINDING*));
rgBindings = (DBBINDING*)COM_ALLOC(sizeof(DBBINDING) * cColumns);
pulColCnt = (DWORD*)COM_ALLOC(sizeof(DWORD));
puDataLen = (DWORD*)COM_ALLOC(sizeof(DWORD));
for (int i = 0; i < cColumns; i++)
{
//如果当前访问器对应的绑定结构的数组的首地址为空,将当前绑定结构指针作为绑定结构数组的首地址
if (NULL == ppBindings[ulBindCnt])
{
ppBindings[ulBindCnt] = &rgBindings[i];
} ++pulColCnt[ulBindCnt];
rgBindings[i].bPrecision = rgColumnsInfo[i].bPrecision;
rgBindings[i].bScale = rgBindings[i].bScale;
rgBindings[i].cbMaxLen = 10 * sizeof(WCHAR);
rgBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBindings[i].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE;
rgBindings[i].eParamIO = DBPARAMIO_NOTPARAM;
rgBindings[i].iOrdinal = rgColumnsInfo[i].iOrdinal;
rgBindings[i].obStatus = puDataLen[ulBindCnt];
rgBindings[i].obLength = puDataLen[ulBindCnt] + sizeof(DBSTATUS);
rgBindings[i].obValue = rgBindings[i].obLength + sizeof(ULONG);
rgBindings[i].wType = DBTYPE_WSTR; if (rgColumnsInfo[i].wType == DBTYPE_IUNKNOWN ||
rgColumnsInfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG)
{
rgBindings[i].cbMaxLen = 0;
rgBindings[i].wType = DBTYPE_IUNKNOWN;
rgBindings[i].pObject = (DBOBJECT*)COM_ALLOC(sizeof(DBOBJECT));
rgBindings[i].pObject->iid = IID_ISequentialStream;
rgBindings[i].pObject->dwFlags = STGM_READ;
uBlob++;
} //记录下每个访问器所需内存的大小
puDataLen[ulBindCnt] = rgBindings[i].obValue + rgBindings[i].cbMaxLen;
if (rgBindings[i].wType == DBTYPE_IUNKNOWN)
{
puDataLen[ulBindCnt] = rgBindings[i].obValue + sizeof(ISequentialStream*);
} puDataLen[ulBindCnt] = UPGROUND(puDataLen[ulBindCnt]);
//判断当前是否需要创建单独的访问器
if ((uBlob || rgBindings[i].iOrdinal == 0))
{
ulBindCnt++;
ppBindings = (DBBINDING**)COM_REALLOC(ppBindings, sizeof(DBBINDING*) * (ulBindCnt + 1));
puDataLen = (DWORD*)COM_REALLOC(puDataLen, sizeof(DWORD) * (ulBindCnt + 1));
pulColCnt = (DWORD*)COM_REALLOC(pulColCnt, sizeof(DWORD) * (ulBindCnt + 1));
}
} //创建访问器
phAccessor = (HACCESSOR*)COM_ALLOC( (ulBindCnt + 1) * sizeof(HACCESSOR));
hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
COM_SUCCESS(hRes, _T("查询IAccessor接口失败,错误码为:%08x\n"), hRes);
for (int i = 0; i < ulBindCnt; i++)
{
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, pulColCnt[i], ppBindings[i], 0, &phAccessor[i], NULL);
COM_SUCCESS(hRes, _T("创建访问器失败,错误码为:%08x\n"), hRes);
} //读取其中的一行数据
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &ulGetRows, &hRow);
COM_SUCCESS(hRes, _T("读取行数据失败,错误码为:%08x\n"), hRes); //读取第一个绑定结构中的信息
pData1 = COM_ALLOC(puDataLen[0]);
hRes = pIRowset->GetData(hRow[0], phAccessor[0], pData1);
for(int i = 0; i < pulColCnt[0]; i++)
{
if (ppBindings[0][i].wType == DBTYPE_IUNKNOWN)
{
DBSTATUS dbStatus = *(DBSTATUS*)((BYTE*)pData1 + ppBindings[0][i].obStatus);
if (dbStatus == DBSTATUS_S_OK)
{
ULONG uFileLen = *(ULONG*)((BYTE*)pData1 + ppBindings[0][i].obLength);
if (uFileLen > 0)
{
DWORD dwReaded = 0;
PVOID pFileData = COM_ALLOC(uFileLen);
ZeroMemory(pFileData, uFileLen);
ISequentialStream *pSeqStream = *(ISequentialStream**)((BYTE*)pData1 + ppBindings[0][i].obValue);
pSeqStream->Read(pFileData, uFileLen, &dwReaded); WriteFileData(_T("1.txt"), pFileData, dwReaded);
}
}
}
} //后续的部分就不再写出来了,写法与上面的代码类似
pIRowset->ReleaseRows(1, hRow, NULL, NULL, NULL);
__CLEAR_UP:
//后面是清理的代码

由于我们事先知道数据表的结构,它有3个BLOB型数据,所以这里直接定义了3个缓冲用来接收3个BLOB型数据。为了方便检测,我们另外写了一个的函数,将读取出来的BLOB数据写入到文件中,事后以文件显示是否正确来测试这段代码

首先还是与以前一样,获取数据表的结构,然后进行绑定,注意这里由于使用的是SQL Server,它不支持一个访问器中访问多个BLOB,所以这里没有判断直接绑定不同的访问器。

在绑定的时候使用ulBindCnt作为当前访问器的数量,在循环里面有一个判断当(uBlob || rgBindings[i].iOrdinal == 0) && (ulBindCnt != cColumns - 1)条件成立时将访问器的数量加1,该条件表示之前已经有blob型数据(之前SQL不支持一个访问器访问多个BLOB,如果之前已经有BLOB数据了,就需要另外创建访问器)或者当前是第0行(因为第0行只允许读,所以将其作为与BLOB型数据一样处理),当这些条件成立时会新增一个访问器,而随着访问器的增加,需要改变ppBindings数组中的元素,该数组存储的是访问器对应的绑定结构开始的指针。数组puDataLen表示的是当前访问器所需内存的大小,pulColCnt表示当前访问器中共有多少列,针对这个表最终这些结构的内容大致如下图:

绑定完成之后,后面就是根据数组中的内容创建对应的访问器,然后绑定、读取数据,针对BLOB数据,我们还是一样从对应缓冲的obValue偏移处得到接口指针,然后调用接口的Read方法读取,最后写入文件

BLOB数据的写入:

要写入BLOB型数据也需要使用ISequentialStream接口,但是它不像之前可以直接使用接口的Write方法,写入的对象必须要自己从ISequentialStream接口派生,并指定一段内存作为缓冲,以便供OLEDB组件调用写方法时作为数据缓冲。这段缓冲必须要保证分配在COM堆上,也就是要使用CoTaskMemory分配内存。

这里涉及到的对象主要有IStream、ISequentialStream、IStorage、ILockBytes,同样,并不是所有数据源都支持这4类对象,具体支持哪些可以查询DBPROPSET_DATASOURCEINFO属性集中的DBPROP_STRUCTUREDSTORAGE属性来判定,目前SQL Server中支持ISequentialStream接口。

虽然我们可以使用这种方式来实现读写BLOB,但是每种数据源支持的程度不同,而且有的数据源甚至不支持这种方式,为了查询对读写BLOB数据支持到何种程度,可以查询DBPROPSET_DATASOURCEINFO属性集合的DBPROP_OLEOBJECTS属性来判定

通常有以下几种支持方式(DBPROP_OLEOBJECTS属性的值,按位设置):

  1. DBPROPVAL_OO_BLOB: 就是之前介绍的接口方式,使用接口的方式来读写BLOB数据

    DBPROPVAL_OO_DIRECTBIND: 可以直接绑定在行中,通过行访问器像普通列一样访问,也就是说它不需要获取专门的指针来操作,他可以就像操作普通数据那样,分配对应内存就可以访问,但是要注意分配内存的大小,每行中对应列中BLOB的数据长度差别可能会很明显,比如有的可能是一部长达2小时的电影文件,而有的可能是一部短视频,它们之间的差距可能会达到上G,而按照最小的来可能会发生截断,按最大的分配可能会发生多达好几个G的内存浪费

    DBPROPVAL_OO_IPERSIST:通过IPersistStream, IPersistStreamInit, or IPersistStorage三个接口的Persist对象访问

    DBPROPVAL_OO_ROWOBJECT: 支持整行作为一个对象来访问,通过结果集对象的IGetRow接口来获得行对象,但是这种模式会破坏第三范式,所以一般数据库都不支持

    DBPROPVAL_OO_SCOPED: 通过IScopedOperations接口来暴露行对象,通过这个接口可以暴露一个树形的结果集对象

    DBPROPVAL_OO_SINGLETON: 直接通过ICommand::Execute和IOpenRowset::OpenRowset来打开行对象

    下面是插入BLOB数据的一个实例
//自定义一个
class CSeqStream : public ISequentialStream
{
public:
// Constructors
CSeqStream();
virtual ~CSeqStream();
public:
virtual BOOL Seek(ULONG iPos); //将当前内存指针偏移到指定位置
virtual BOOL CompareData(void* pBuffer); //比较两段内存中的值
virtual ULONG Length()
{
return m_cBufSize;
};
virtual operator void* const()
{
return m_pBuffer;
};
public:
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv); //读写内存的操作,这些是必须实现的函数
STDMETHODIMP Read(
/* [out] */ void __RPC_FAR *pv,
/* [in] */ ULONG cb,
/* [out] */ ULONG __RPC_FAR *pcbRead); STDMETHODIMP Write(
/* [in] */ const void __RPC_FAR *pv,
/* [in] */ ULONG cb,
/* [out]*/ ULONG __RPC_FAR *pcbWritten);
private:
ULONG m_cRef; // reference count
void* m_pBuffer; // buffer
ULONG m_cBufSize; // buffer size
ULONG m_iPos; // current index position in the buffer
};
//插入数据第一列BLOB数据
//这里由于已经事先知道每列的数据结构,因此采用偷懒的方法,一行行的插入
pData1 = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pdwDataLen[nCol]);
for(int i = 0; i < pulColCnt[nCol]; i++)
{
if (DBTYPE_IUNKNOWN == ppBindings[nCol][i].wType)
{
*(DBSTATUS*)((BYTE*)pData1 + ppBindings[nCol][i].obStatus) = DBSTATUS_S_OK;
CSeqStream *pSeqStream = new CSeqStream();
GetFileData(_T("test.txt"), dwFileLen, pFileData);
pSeqStream->Write(pFileData, dwFileLen, &dwWritten);
pSeqStream->Seek(0); //写这个操作将缓存的指针偏移到了最后,需要调整一下,以便OLEDB组件在插入BLOB数据时从缓存中读取
HeapFree(GetProcessHeap(), 0, pFileData);
*(ULONG*)((BYTE*)pData1 + ppBindings[nCol][i].obLength) = dwFileLen;
*(ISequentialStream**)((BYTE*)pData1 + ppBindings[nCol][i].obValue) = pSeqStream;
//此处不用release pSeqStream,COM组件会自动释放
}else
{
//根据数据库定义,此处应该为ID
*(ULONG*)((BYTE*)pData1 + ppBindings[nCol][i].obLength) = 10;
if (DBTYPE_WSTR == ppBindings[nCol][i].wType)
{
StringCchCopy((LPOLESTR)((BYTE*)pData1 + ppBindings[nCol][i].obValue), 10, SysAllocString(OLESTR("1")));
}
}
} hRes = pIRowsetChange->InsertRow(DB_NULL_HCHAPTER, phAccessor[nCol], pData1, &hNewRow);
COM_SUCCESS(hRes, _T("插入第1列BLOB数据失败,错误码为:%08x\n"), hRes);

在上面的代码中首先定义一个派生类,用来进行BLOB数据的读写,然后在后面的代码中演示了如何使用它

在后面的一段代码中,基本步骤和之前一样,经过连接数据源、创建回话对象,打开表,然后绑定,获取行访问器,这里由于代码基本不变,为了节约篇幅所以省略它们,只贴出最重要的部分。

在插入的代码中,首先查找访问器中的各个列的属性,如果是BLOB数据就采用BLOB数据的插入办法,否则用一般数据的插入办法。插入BLOB数据时,首先创建一个派生类的对象,注意此处由于后续要交给OLEDB组件调用,所以不能用栈内存。我们先调用类的Write方法将内存写入对应的缓冲中,然后调用Seek函数将内存指针偏移到缓冲的首地址,这个指针的作用就相当于文件的文件指针,COM组件在调用对应函数将它插入数据库时会采用这个内存的指针,所以必须将其置到首地址处。让后将对象的指针放入到对应的obvalues偏移中,设置对应的数据大小为BLOB数据的大小,最后只要像普通数据类型那样调用对应的更新方法即可实现BLOB数据的插入

最后贴上两个例子的详细代码地址

示例1:BLOB数据的读取

示例2:BLOB数据的插入

OLEDB存取BLOB型数据的更多相关文章

  1. mysql存取blob类型数据

    参考网址:http://www.cnblogs.com/jway1101/p/5815658.html 首先是建表语句,需要实现将表建立好. CREATE TABLE `blobtest` ( `pr ...

  2. XML Schema格式的"日期型数据”数据库存取

    对于XML Schema格式的"日期型数据"在数据库中存于datetime字段的时候,出现错误 mysql> select @@sql_mode; +------------ ...

  3. mysql之整型数据int

    mysql数据库设计,其中,对于数据性能优化,字段类型考虑很重要,mysql整型bigint.int.mediumint.smallint 和 tinyint的语法介绍,如下:1.bigint 从 - ...

  4. 【机器学习实战】第8章 预测数值型数据:回归(Regression)

    第8章 预测数值型数据:回归 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/ ...

  5. 【C语言入门教程】2.3 整型数据

    没有小数位或指数的数据类型被称为整型数据,根据使用方法的分类,整型数据可分为整型常量和整型变量.根据定义或显示的数制分类,可分为十进制.八进制和十六进制. 2.3.1 整型常量 整型常量是在运算中不可 ...

  6. 2016年11月3日JS脚本简介数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6.布尔型数据:bool 7.对象类型:object 8.二进制:binary 语言类型: 1.强类型语言:c++ c c# java 2.弱类型语

    数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6 ...

  7. C#使用oledb方式将excel数据导入到datagridview后数据被截断为 255 个字符

    问题描述:在使用oledb方式将excel数据导入到datagridview中,在datagridview单元格中的数据没有显示全,似乎只截取了数据源中的一段 解决方案:1.关于该问题,微软官方答案: ...

  8. HotSpot关联规则算法(2)-- 挖掘连续型和离散型数据

    本篇代码可在 http://download.csdn.net/detail/fansy1990/8502323下载. 前篇<HotSpot关联规则算法(1)-- 挖掘离散型数据>分析了离 ...

  9. jmeter从外部文件取值问题,如果文件中的参数值为纯数字形式的,jmeter会默认将其识别成int型数据

    如果你通过CSV Data Set Config或者_StringFromFile函数来参数化你的请求,需要特别注意当参数为纯数字时,jmeter会默认将其识别成int型数据,说明jmeter并不是默 ...

随机推荐

  1. P3615 如厕计划

    $ \color{#0066ff}{ 题目描述 }$ 竞赛比完之后,水箱里充满水的选手们鱼贯而出.凡华中学的厕所规划的很糟,只有两个厕位,于是厕所门前排起了长长的队伍. 厕所有两个,一个是女生专用厕所 ...

  2. 「模拟赛20190327」 第二题 DP+决策单调性优化

    题目描述 小火车虽然很穷,但是他还是得送礼物给妹子,所以他前往了二次元寻找不需要钱的礼物. 小火车准备玩玩二次元的游戏,游戏当然是在一个二维网格中展开的,网格大小是\(n\times m\)的,某些格 ...

  3. win10+anaconda环境下pyqt5+qt tools+eric6.18安装及汉化过程

    最近需要用python编写一个小程序的界面,选择了pyqt5+eric6的配套组合,安装过程中遇到一些坑,特此记录.参考书籍是电子工业出版社的<PyQt5快速开发与实战>. 因为我使用an ...

  4. 并查集简述 (HDU-1213-How Many Tables)

    并查集主要解决集合的有关运算,主要操作是查找操作和并操作. 1.集合的储存方式. 为便于查找,集合通常以树结构储存,每个元素分 数据域和指针域,可以用链式储存,也可以用结构数组储存,用根节点来表示一个 ...

  5. Java 实现邮件的发送

                                             Java 实现邮件的发送 开发邮箱发送功能必须看邮箱方面的资料 改一些东西  (我的是qq 邮箱哟   开通 POP3 ...

  6. Django - Xadmin 组件(一)

    Django - Xadmin 组件(一) Web 应用中离不开的就是后台管理, Django 自带的 admin 组件提供了一部分内容,但往往现实项目中会有更多的需求,所以自定义自己的后台管理就十分 ...

  7. css 之 BFC

    1,定义 BFC为块级格式化上下文,也就是一块区域内的封闭空间,里面元素无论怎么样,都不会影响外部元素. 2,触发条件 html 根元素 display的值为 inline-block.table-c ...

  8. Apache Shiro(六)-基于URL配置权限

    数据库 先准备数据库啦. DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE sh ...

  9. v-for遍历对象

    如果数据是这样的: userInformation:{ 'aa':{ user_name:'ddd123', icon:'', pic:'', addTime:'2018-3-21 11:21', c ...

  10. web 页面 验证码 实现

    1. 前台页面代码:  页面刷新时会自动请求 ${pageContext.request.contextPath}/yanzheng?yz=&time=-1111 这个action <f ...