0% acharam este documento útil (0 voto)
19 visualizações

arquivos-em-blobs

O artigo aborda a manipulação de campos BLOB em bancos de dados Firebird usando Delphi, detalhando a criação de um banco de dados e uma aplicação para armazenar e visualizar arquivos. O autor explica como configurar a tabela, inserir arquivos no campo BLOB e exibir seu conteúdo utilizando aplicativos externos ou diretamente no Delphi para imagens. Além disso, o texto inclui exemplos de código para facilitar a implementação das funcionalidades descritas.
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
19 visualizações

arquivos-em-blobs

O artigo aborda a manipulação de campos BLOB em bancos de dados Firebird usando Delphi, detalhando a criação de um banco de dados e uma aplicação para armazenar e visualizar arquivos. O autor explica como configurar a tabela, inserir arquivos no campo BLOB e exibir seu conteúdo utilizando aplicativos externos ou diretamente no Delphi para imagens. Além disso, o texto inclui exemplos de código para facilitar a implementação das funcionalidades descritas.
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 9

1

Campos BLOB
Armazenando e visualizando arquivos no BD
usando o Delphi
POR CARLOS H. CANTU

Acredito que a grande maioria dos programadores já conhece, ou já utilizou em algum dos seus
sistemas, campos do tipo BLOB (ou "Memo", para os mais velhos). Realmente um campo sem
limite de tamanho que possa armazenar qualquer tipo de informação pode ser muito útil,
principalmente em aplicações que envolvem a manipulação de arquivos externos. Nesse artigo
mostro como manipular campos BLOB e tratar o seu conteúdo no Firebird (FB).

Criando o banco de dados

A primeira coisa que precisamos fazer é criar o banco de dados que utilizaremos no exemplo. A
Listagem 1 contém os metadados do banco. Aconselho a utilização de um front-end como o
IBExpert (que por sinal, tem uma versão gratuita/personal e uma versão full que pode ser comprada
com desconto pelo site da FireBase) para rodar o script e gerar o banco de dados de exemplo.

Listagem 1. Script para criação do banco de dados


SET SQL DIALECT 3;

SET NAMES WIN1252;

CREATE DATABASE 'c:\blob\blobs.fdb'


USER 'SYSDBA' PASSWORD 'masterkey'
PAGE_SIZE 4096
DEFAULT CHARACTER SET WIN1252;

/**** Generators ****/


CREATE GENERATOR GEN_TAB_BLOB_ID;

/**** Tabelas ****/


CREATE TABLE TAB_BLOB (
ID INTEGER NOT NULL,
DESCRICAO VARCHAR(50) CHARACTER SET WIN1252 NOT NULL,
DADOS BLOB SUB_TYPE 0 SEGMENT SIZE 512,
FILENAME VARCHAR(100) CHARACTER SET WIN1252
);

/**** Primary Keys ****/


ALTER TABLE TAB_BLOB ADD CONSTRAINT PK_TAB_BLOB PRIMARY KEY (ID);

/**** Triggers ****/


SET TERM ^ ;

/* Trigger: TAB_BLOB_BI_BI */
CREATE TRIGGER TAB_BLOB_BI_BI FOR TAB_BLOB
INACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(GEN_TAB_BLOB_ID,1);
END
^
SET TERM ; ^

A tabela principal, TAB_BLOB, contém um campo de chave primária (ID), um campo varchar
para armazenar a descrição, um BLOB (chamado DADOS) e um campo onde informamos o nome
2

original do arquivo armazenado (FILENAME) – sem este não há como saber o que exatamente está
armazenado no BLOB (veja o quadro "Cabeçalhos").
Observe, no script de criação do banco, que o campo DADOS foi declarado como SUB_TYPE 0,
indicando ao FB que armazenará dados binários, ou seja, qualquer tipo de informação, textual ou
não. Lembre-se que o FB não valida o conteúdo gravado em um BLOB. Isso deve ser feito pelo seu
aplicativo. O parâmetro segment size hoje em dia não tem qualquer utilidade no Firebird – no
passado, ele era utilizado por alguns utilitários para indicar de quantos em quantos bytes o blob
deveria ser recuperado.

Cabeçalhos
Vários tipos de arquivos (mas não todos) contêm um cabeçalho padrão que pode ser usado para identificar
o tipo de informação que armazenam. Imagens como JPG, GIF, BMP etc., por exemplo, podem ter seus
formatos identificados através dos seus cabeçalhos e assim serem exibidas corretamente. Mas como no
exemplo deste artigo iremos armazenar qualquer tipo de arquivo nos BLOBs, optamos por uma solução
mais genérica: verificaremos a extensão do arquivo no campo FILENAME, para determinar o tipo de
informação gravada.

Construindo a aplicação

Na aplicação de exemplo, utilizaremos a tecnologia dbExpress para o acesso a dados, evitando a


instalação de pacotes de terceiros. A aplicação permitirá a inserção de registros no banco de dados,
incluindo o armazenamento de qualquer arquivo externo dentro de um campo BLOB. Será possível
também exibir o conteúdo dos BLOBs no aplicativo apropriado (desde que exista um aplicativo
associado instalado no Windows, é claro).
Inicie uma nova aplicação no Delphi e insira um componente SQLConnection da paleta dbExpress
(para simplificar não usaremos um DataModule). Dê um duplo clique no componente e crie uma
conexão ao InterBase, configurando os parâmetros como a seguir:
o Database: forneça o nome do arquivo para o banco de dados recém-criado
o Username: SYSDBA
o Password: masterkey (ou outra, caso você tenha alterado a senha padrão, claro)
o ServerCharset: WIN1252
o SQLDialect: 3
Escolha OK. Certifique-se que o servidor Firebird ou o InterBase esteja rodando, e configure o
LoginPrompt do SQLConnection1 para False e Connected para True. Para acessar a tabela
TAB_BLOB, usaremos o trio SQLQuery/DatasetProvider/ClientDataset. Adicione esses
componentes, além de um DBGrid e um DBNavigator, e relacione-os como na Figura 1.

Figura 1. Componentes do exemplo e seus relacionamentos

Altere a propriedade SQL do SQLQuery1 para “select * from TAB_BLOB order by ID”. Dê um
duplo clique no SQLQuery1 e adicione a ele todos os campos. Altere a propriedade
ProviderFlags.pfInKey do campo ID para True, e o UpdateMode do DataSetProvider1 para
upWhereKeyOnly (para que seja utilizado somente o campo ID na cláusula where dos comandos de
atualização e exclusão).
3

Ative o ClientDataSet1, dê um duplo clique nele e novamente adicione todos os campos.


Opcionalmente, altere a propriedade DisplayLabel dos TFields para textos mais adequados para
apresentação no DBGrid. Ajuste a propriedade DisplayWidth do campo FILENAME para 30.
Precisamos também fazer com que o ClientDataset1 atualize os dados no banco sempre que um
Post ou Delete for executado, através de uma chamada a ApplyUpdates. Adicione o seguinte código
ao manipulador do evento AfterPost do ClientDataset1:
procedure TForm1.ClientDataSet1AfterPost(DataSet: TDataSet);
begin
if ClientDataSet1.ChangeCount <> 0 then
ClientDataSet1.ApplyUpdates(0);
end;

Associe o evento AfterDelete do ClientDataSet1 ao mesmo método (AfterPost). Coloque no


formulário um Panel e limpe seu Caption. Configure as propriedades Align do DBNavigator1,
Panel1 e DBGrid1 para alTop, alBottom e alClient, respectivamente. Insira também um
OpenDialog, que será utilizado para selecionar o arquivo a ser armazenado no BLOB; configure sua
propriedade Options.ofFileMustExist para True. Dentro do Panel insira dois BitBtns (paleta
Additional) com Captions “Importar Arquivo” e “Visualizar Blob”. Precisamos também de um
Checkbox, para escolhermos se a aplicação irá esperar ou não pelo retorno do visualizador
(aplicação externa). Pronto, a estrutura básica do formulário já está montada (veja a Figura 2).

Figura 2. Formulário principal da aplicação

Recuperando o valor do generator

Utilizaremos o generator GEN_TAB_BLOB_ID (veja novamente a Listagem 1) para fornecer os


valores seqüenciais para o campo ID (simulando um campo autoincremento). Quando um registro
for adicionado, o generator será incrementado, e o valor gerado será recuperado e atribuído ao
campo ID do ClientDataSet1. Isso é necessário porque esse campo é a chave primária sendo
utilizado pelo provider para sincronizar os dados em memória com o servidor; portanto o provider
precisa conhecer seu valor antes de postar o registro no banco.
No evento OnNewRecord do ClientDataSet1 inclua:
4

procedure TForm1.ClientDataSet1NewRecord(DataSet: TDataSet);


var
ResultSet : TCustomSQLDataSet;
SQLstmt : string;
begin
SQLStmt := 'select gen_id(GEN_TAB_BLOB_ID,1) '+
'as VALOR from RDB$DATABASE;';
ResultSet := nil;
try
SQLConnection1.Execute(SQLstmt, nil, @ResultSet);
if Assigned(ResultSet) then
ClientDataSet1ID.asInteger :=
ResultSet.FieldByName('VALOR').AsInteger;
finally
ResultSet.Free;
end;
end;

Note que o código enviado ao servidor é um simples select, que utiliza no lugar de um campo uma
chamada à função gen_id, fazendo com que o generator seja incrementado. A consulta retorna o
valor como um campo (que chamamos de valor).
A tabela de sistema RDB$DATABASE usada no select é muito utilizada em instruções desse tipo,
pelo fato de sempre conter um único registro. Para quem já usou Oracle, a RDB$DATABASE
equivale à tabela DUAL daquele banco de dados.

Inserindo o arquivo

Dê um duplo clique no botão Importar Arquivo e adicione:


procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if ClientDataSet1.State in [dsEdit,dsInsert] then
ClientDataSet1.Edit;
if OpenDialog1.Execute then
begin
ClientDataSet1DADOS.LoadFromFile(OpenDialog1.FileName);
ClientDataSet1FILENAME.AsString :=
ExtractFileName(OpenDialog1.FileName);
end;
end;

Esse código verifica se o ClientDataSet1 está em modo de edição ou inserção, e então mostra uma
caixa de diálogo para a escolha do arquivo. O conteúdo desse arquivo é copiado para o campo
DADOS utilizando LoadFromFile. Seu nome também é gravado (sem o path) em FILENAME.
Rode a aplicação, insira um registro usando o DBNavigator, e digite uma descrição para ele.
Clique no botão Importar Arquivo, escolha um arquivo do seu disco rígido (preferencialmente algo
não-textual como um .AVI, .MP3 ou .HTML). Se tudo der certo, basta então confirmar a gravação
de dados no DBNavigator.

Visualizando o conteúdo

Muito bem, agora que já conseguimos gravar os arquivos no banco, incluiremos o código que
apresenta ao usuário o conteúdo do campo BLOB, usando um aplicativo externo. De início, pode
parecer uma tarefa trabalhosa, mas felizmente o Windows vai nos ajudar bastante no processo.
O que vamos fazer quando alguém quiser visualizar o BLOB é gerar um novo arquivo temporário
com o seu conteúdo; depois usaremos a função ShellExecute da API do Windows para “executar” o
arquivo. Mas como assim "executar" um arquivo MP3, BMP etc.? A função ShellExecute pode
identificar e acionar a aplicação associada, com base na extensão do arquivo, usando informações
no registro do Windows.
5

Nota: Caso você tente abrir um arquivo cuja extensão não tenha uma aplicação associada, será mostrada a
tradicional janela que solicita o aplicativo a ser utilizado para abri-lo.

O código para apresentação do arquivo deve ser incluído no evento OnClick do botão Visualizar
Blob (BitBtn2) – veja a Listagem 2. Lembre-se de adicionar a ShellApi ao uses da unit. Você já
pode executar a aplicação, e inserir arquivos no campo BLOB ou visualizar seu conteúdo (veja a
Figura 3).

Listagem 2. Visualização do conteúdo do campo BLOB


procedure TForm1.BitBtn2Click(Sender: TObject);
var
PathBuf : array [0..255] of char;
TempPath : string;
begin
// Recupera o diretório TEMP do windows
GetTempPath(SizeOf(PathBuf), PathBuf);

// Monta o path completo para o diretório temporário


TempPath := StrPas(PathBuf) + '\Blobs';

// Verifica se o diretório temporário já existe; caso contrário, cria


if not DirectoryExists(TempPath) then
if not ForceDirectories(TempPath) then
begin
MessageDlg('Não foi possível criar o diretório temporário!',
mtError, [mbOk], 0);
SysUtils.Abort;
end;
TempPath := TempPath + '\' + tbDadosFILENAME.asString;

// Grava o BLOB no arquivo temporário


tbDadosDADOS.SaveToFile(TempPath);

// Chama a aplicação associada à extensão do arquivo


if (not cbAguardar.checked) then
ShellExecute(self.Handle, 'open', PChar(TempPath),
nil, nil, SW_SHOWNORMAL)
else begin // Mostra e aguarda retorno da aplicação externa.
ShellExecuteAndWait(TempPath, SW_SHOWNORMAL);
DeleteFile(TempPath);
end;
end;
6

Figura 3. Exibição do conteúdo de campos BLOB do IB/FB em aplicações externas

Lidando com os arquivos temporários

No exemplo, temos duas maneiras de tratar os arquivos temporários gerados quando se visualiza o
conteúdo de um BLOB. É o estado do Checkbox que define como os arquivos serão tratados. Se o
CheckBox estiver marcado, nossa aplicação irá esperar que a aplicação externa que está mostrando
o conteúdo do BLOB seja encerrada para continuar o processamento, apagando automaticamente o
arquivo temporário que foi gerado; caso não esteja marcado, o processamento da aplicação não será
interrompido, mas, como conseqüência, os arquivos temporários gerados não serão apagados
automaticamente, ficando essa tarefa à cargo do usuário.
A Listagem 3 mostra a rotina que chama a aplicação externa e aguarda pelo seu encerramento. Ela
faz uso de uma versão estendida da função ShellExecute, ShellExecuteEx, que oferece mais opções
de configuração do processo a ser iniciado. A listagem está comentada para que você entenda o que
está sendo feito no código.

Listagem 3. Função para disparar uma aplicação e aguardar o seu retorno


function ShellExecuteAndWait(FileName: string; Visibility: integer): DWORD;
// Espera até que o processo criado seja encerrado
procedure WaitForExec(processHandle: THandle);
var
msg: TMsg;
ret: DWORD;
begin
// Fica em loop esperando o processo terminar
repeat
ret := MsgWaitForMultipleObjects(1, { Indica que deve aguardar }
processHandle, { handle do processo }
False, { "acorda" com qualquer evento }
INFINITE, { espera o quanto for necessário }
QS_PAINT or { "acorda" em mensagens de PAINT }
QS_SENDMESSAGE { "acorda" com mensagens enviadas por outras threads }
);

if ret = WAIT_FAILED then exit; { se falhou, sai... }

if ret = (WAIT_OBJECT_0 + 1) then


begin
{ Recebeu uma mensagem, mas processa apenas mensagens de PAINT }
while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(msg);
end;
until ret = WAIT_OBJECT_0;
end;

var
ShellExecuteInfo: TShellExecuteInfo;

begin
FillChar(ShellExecuteInfo, SizeOf(ShellExecuteInfo), #0);
with ShellExecuteInfo do
begin
cbSize := SizeOf(TShellExecuteInfo);
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := application.Handle;
lpVerb := 'open';
lpFile := PCHAR(FileName);
lpParameters := nil;
lpDirectory := nil;
nShow := SW_SHOWNORMAL;
end;

ShellExecuteEx(@ShellExecuteInfo);
if ShellExecuteInfo.hProcess = 0 then
Result := DWORD(-1) { Falhou na criação do processo}
else
begin
// Aguarda pelo encerramento do processo
WaitforExec(ShellExecuteInfo.hProcess);
7
// Recupera o código de retorno da aplicação
GetExitCodeProcess(ShellExecuteInfo.hProcess, Result);
// Fecha o handle do processo para liberar recursos
CloseHandle(ShellExecuteInfo.hProcess);
end;
end;

Tratando imagens

Sabemos que o Delphi pode exibir imagens do tipo BMP e JPG sem a necessidade de aplicações
externas. Sendo assim poderemos visualizá-las no nosso próprio aplicativo, bastando criar um
segundo formulário, que será chamado quando o usuário solicitar a visualização da imagem.
Crie um formulário e coloque nele um Image (Additional), configurando sua propriedade Align
para alClient, e Center e Autosize para True. Volte ao formulário principal e adicione a unit do novo
formulário ao uses; adicione também a unit JPEG.
A rotina do botão Visualizar Blob, alterada para mostrar internamente as imagens JPG e BMP, está
na Listagem 4. Basicamente verificamos se a informação no BLOB é um JPG ou BMP, e caso seja,
criamos um Stream para receber seu conteúdo. É necessário criar um objeto do tipo da imagem
desejada (uma instância de TJpegImage ou TBitMap) e carregar o Stream para dentro dele, e depois
carregamos a imagem no componente Image através do método Assign.

Listagem 4. Código alterado para manipular imagens JPG e BMP internamente


procedure TForm1.BitBtn2Click(Sender: TObject);
var PathBuf: array[0..255] of char;
TempPath: string;
Jpg: TJPegImage;
Bmp: TBitMap;
Extensao: string;
Stream: TStream;
begin
// Se não há informação sobre o tipo do arquivo, sai...
if tbDadosFILENAME.asString = '' then exit;

// Recupera a extensão do arquivo


Extensao := UpperCase(ExtractFileExt(tbDadosFILENAME.asString));

// Se for imagem JPG ou BMP, trata dentro do próprio aplicativo


if Pos(Extensao, '.JPEG .JPG .BMP') <> 0 then
begin
Stream := tbDados.CreateBlobStream(tbDadosDADOS, bmRead); ;
try
frmImage.visible := true;
if Extensao = '.BMP' then
begin
Bmp := TBitMap.create;
try
Bmp.LoadFromStream(Stream);
frmImage.Image1.Picture.Assign(Bmp);
finally
bmp.free;
end;
end
else
begin
Jpg := TJpegImage.create;
try
Jpg.LoadFromStream(Stream);
frmImage.Image1.Picture.Assign(Jpg);
finally
Jpg.Free;
end;
end;
finally
Stream.Free;
end;
end
else // Não é JPG nem BMP, chama aplicativo externo.
begin
// Recupera o diretorio TEMP do windows
GetTempPath(SizeOf(PathBuf), PathBuf);
8

// Monta o path completo para o nosso diretório temporário


TempPath := StrPas(PathBuf) + '\Blobs';

// Verifica se o diretório temporário já existe, caso contrário cria.


if not DirectoryExists(TempPath) then
if not ForceDirectories(TempPath) then
begin
MessageDlg('Não foi possível criar o diretório temporário!', mtError, [mbOk], 0);
SysUtils.Abort;
end;
TempPath := TempPath + '\' + tbDadosFILENAME.asString;

// Grava o blob no arquivo temporario


tbDadosDADOS.SaveToFile(TempPath);

// Chama a aplicação associada à extensão do arquivo


if (not cbAguardar.checked) then
ShellExecute(self.Handle, 'open', PChar(TempPath), nil, nil, SW_SHOWNORMAL)
else
begin // Mostra e aguarda retorno da aplicação externa.
ShellExecuteAndWait(TempPath, SW_SHOWNORMAL);
DeleteFile(TempPath);
end;
end;
end;

Talvez você esteja se perguntando por que não utilizamos um TDBImage em vez do TImage para
mostrar as imagens. A resposta é que o TDBImage não é capaz de tratar automaticamente imagens
JPEG. Veja um JPG sendo exibido na Figura 4.

Figura 4. Imagem exibida diretamente na aplicação

BLOBs ou arquivos externos?


O armazenamento de arquivos dentro de BLOBs pode facilitar a administração dos dados, mas essa prática
não deve ser usada indiscriminadamente. Dependendo do tamanho dos arquivos, pode ser melhor
armazenar no banco de dados apenas um path apontando para o arquivo externo. Os arquivos poderiam
ficar gravados em um diretório compartilhado da rede, por exemplo. Obviamente manter os arquivos fora do
banco de dados vai diminuir o seu tamanho, mas também aumentará o trabalho na hora de fazer backups,
além de exigir medidas de segurança adicionais para evitar que pessoas não-autorizadas alterem os
arquivos externos.

Conclusões

Vimos que a utilização de campos BLOBs em aplicações de bancos de dados é bastante simples,
além de muito útil. No entanto, devemos saber que o armazenamento de arquivos dentro de BLOBs
deve ser bem planejado. Em algumas situações é melhor manter apenas um link para os arquivos e
deixá-los armazenado externamente ao banco. Uma das vantagens de armazenar arquivos dentro do
banco é a facilidade de administração. Por outro lado, um banco muito grande poderá exigir um
9

tempo maior de backup e restore, e isso pode ser um fator determinante em aplicações críticas que
não podem ficar offline por muito tempo (veja mais no quadro "BLOBs ou arquivos externos).

Esse artigo foi extraído do livro “Firebird Essencial”, de minha autoria, e revisado em 05-setembro-2021. Autor: Carlos Henrique
Cantu

Você também pode gostar