jun 192018
 

ESTRUTURA DA TABELA E INSERÇÃO DE REGISTROS

O intuito deste artigo é compartilhar um pouco da minha experiência com migração de arquivos BLOB em um banco de dados Oracle para sistema de arquivos utilizando Python.

Antes de efetivamente partirmos para o código de migração, vamos ver os dados de acesso e como será a estrutura da tabela.

Utilizo os seguintes dados de acesso para o esquema no Oracle:

  • user: desv
  • pass: 123456
  • service name: xe
  • IP do host onde está o SGBD do Oracle: 192.168.1.131

Vamos utilizar a estrutura da tabela a seguir:

CREATE TABLE TB_ARQUIVO (
	COD_ARQUIVO           NUMBER PRIMARY KEY,
	DTHINCLUSAO           DATE NOT NULL,
	ARQUIVO               BLOB,
	DS_ARQUIVO            VARCHAR2(50 BYTE),
	DS_PATH_ARQUIVO       VARCHAR2(255 BYTE)
);


A tabela TB_ARQUIVO contém as seguintes colunas:

  • COD_ARQUIVO: coluna para identificar um registro como único (chave primária)
  • DTHINCLUSAO: armazena a data de inclusão do arquivo
  • ARQUIVO: armazena um arquivo em formado PDF
  • DS_ARQUIVO: define um nome para o arquivo
  • DS_PATH_ARQUIVO: path, onde o arquivo ficará gravado em disco

Vamos inserir alguns registros:

INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(1,’ORACLE’,’30/05/2016′);
INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(2,’LINUX’,’31/05/2016′);
INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(3,’BIGDATA’,’30/05/2017′);
INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(4,’HADOOP’,’29/05/2017′);
INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(5,’DATAMINING’,’01/02/2018′);
INSERT INTO TB_ARQUIVO(COD_ARQUIVO,DS_ARQUIVO,DTHINCLUSAO) VALUES(6,’PYTHON’,’01/02/2018′);
COMMIT;

O script SQL está disponível no seguinte link: https://pastebin.com/0FabU0qY

Após a inserção, precisamos adicionar os arquivos PDFs.

Como são apenas seis registros, podemos adicionar os arquivos de forma rápida pelo Oracle SQL Developer.

No SQL Developer, após a criação da tabela e inserção dos registros, clique no nome da tabela “TB_ARQUIVO” e clique em “Dados”.

Na ferramenta, deverá ser exibido algo igual à imagem a seguir:

Agora, dê dois cliques abaixo da coluna ARQUIVO, onde aparece o nome “null”. Veja que aparece um ícone com a imagem de um lápis. Clique em cima do ícone.

Deverá aparecer a janela da figura a seguir:

Clique na opção “Carregar” e selecione um arquivo PDF qualquer. Depois é só repetir os passos anteriores para os demais registros. Observe a imagem a seguir:

Veja que “null” foi substituído por “BLOB”, pois os arquivos PDF já foram inseridos na tabela, entretanto, falta efetivar a transação. Basta clicar na opção onde o ponteiro do mouse está apontando, ou então, teclar em F11.

Após as ações realizadas em banco, vamos partir para os requisitos para realizar a migração.

REQUISITOS DA MIGRAÇÃO

 

Não basta simplesmente gravar todos os arquivos em disco de forma aleatória, para isso temos que seguir alguns requisitos.

1°) Os arquivos deverão ser gravados em disco, de acordo com o campo da data de inclusão (DTHINCLUSAO), ou seja, deverá ser criada uma hierarquia de diretórios ano/mes/dia. Portanto, se um registro possui uma data 30/05/2016, o arquivo deverá ser gravado em um diretório 2016/05/30.

2°) Ao gravar em disco, duas colunas precisarão ser atualizadas: DS_PATH_ARQUIVO com o path e DS_ARQUIVO com o nome do arquivo o qual foi gravado no sistema de arquivos.

3°) Em relação ao nome do arquivo, observe um cenário em que uma empresa possua centenas de milhares de arquivos em banco, devemos evitar a colisão de nomes em disco, então para contornar essa situação, iremos utilizar o módulo uuid, identificador universalmente exclusivo, para gerar os nomes dos arquivos criando strings únicas que garantam unicidade.

CÓDIGO DA MIGRAÇÃO

Para saber como configurar uma instância cliente do Oracle, e como configurar o Python para conectar ao banco de dados, há um artigo publicado anteriormente, explicando o passo a passo: Configurando uma instância do Oracle para acesso via Python [Artigo]

Vamos iniciar o código realizando os imports dos módulos.

O módulo “cx_Oracle” é para conectar ao banco de dados, enquanto que o módulo “os” é para manipulação de diretórios, criando-os em disco. Por fim, o módulo “uuid”, para criar as strings que serão os nomes dos arquivos.

Agora vamos criar três funções: uma para conectar ao banco de dados, outra para escrever o arquivo em disco e a terceira função para criar o diretório.

Função para conectar ao Oracle:

Veja na imagem anterior que a função recebe quatro parâmetros relacionados ao ambiente onde está executando o Oracle: schema, password, host e o service name. Na linha seis, eu concateno todas as informações para formar a URL de conexão e na linha sete, através da função “connect”, é criada uma conexão com o banco de dados.

A nossa próxima função será para gravar o arquivo “pdf” em disco:

Essa função recebe dois parâmetros: data se refere ao conteúdo do arquivo “pdf” e filename ao nome do arquivo que será gravado em disco.

Na linha 12, veja que é utilizado o modo “wb”, o qual significa escrita de binários. Na linha 13, a função “write” é a responsável por gravar o arquivo em disco.

A função a seguir cria o diretório em disco:

Relembrando, o 1° requisito; O arquivo deverá ser gravado em disco, em um diretório formado a partir da coluna “DTHINCLUSAO”.

Veja que a função “criarDiretorioArquivo” recebe cinco parâmetros: path (caminho do sistema de arquivos), ano, mês, dia e data (arquivo pdf).

Observe as linhas 16 e 17, a variável “caminho” realiza a concatenação de todo o path e a variável “arquivo” concatena o path acrescido do nome do arquivo com a extensão “.pdf”. Em um dos requisitos, foi mencionado que deveríamos evitar a colisão de nomes, então para isso foi utilizado str(uuid.uuid1()). Está sendo criada uma cadeia de 32 dígitos hexadecimais e a função “str” faz a conversão para string. Na linha 18 é verificado se o diretório não existe, caso não exista ele será criado, caso exista, o código partirá para a verificação na linha 20, como o arquivo não existe ele será criado com a chamada da função “write_data” na linha 21.

Com a criação dessas três funções, nosso código “main” ficará bem enxuto. Agora vamos para a função principal:

Veja que eu poderia fornecer os dados de acesso de forma estática no código, assim como o path, no entanto, considerando a possibilidade de executar o código em vários ambientes (desenvolvimento, testes, homologação…), prefiro fornecer esses dados em tempo de execução.

Vamos ao próximo trecho do código:

Aqui eu faço a chamada à primeira função criada: “conectarOracle”. Abro um cursor e executo uma consulta SQL na tabela “TB_ARQUIVO”, pois necessito de três informações: o código de cada registro, além da data de inclusão e do arquivo em PDF, obtido do campo do tipo BLOB.

Agora vamos para a última parte do código:

Na linha 41, é declarado um contador para cada migração ser exibido o somatório parcial e no final da execução ser exibida a quantidade de arquivos migrados. Depois é necessário iterar no cursor para as variáveis s_cod, s_dthinclusao e s_arquivo receberem os valores obtidos anteriormente pela consulta SQL. Para a estrutura de diretórios ser gravada em disco, necessitamos acessar o valor da coluna “s_dthinclusao” e converter para string. Isso é feito na linha 43, com o uso da função “strftime”.

Na linha 45 chamamos a função “criarDiretorioArquivo”, que além de criar os diretórios, também vai gravar o arquivo. No primeiro parâmetro passamos o path do sistema de arquivos, nos segundo, terceiro e quarto parâmetros, é utilizado o operador de slice, para obter o ano, mês e dia, pois a data está sendo obtida assim: 2016/05/30. Portanto, o path é concatenado com essa nova estrutura de diretórios. E no último parâmetro, “s_arquivo.read()” obtemos o arquivo BLOB para gravar em disco.

Na linha 49 abrimos um novo cursor para executar a instrução SQL na linha seguinte, para atualizar as colunas ds_path_arquivo e ds_arquivo para cada registro.

Agora, muita atenção quanto à linha 51. O nome do arquivo gerado fica nesse formato b75b6632-33a5-11e8-8b38-847bebfeb61e concatenado com a extensão do arquivo que é “.pdf”, a string fica assim: b75b6632-33a5-11e8-8b38-847bebfeb61e.pdf. Essa string possui 40 caracteres, então para obter todo o path para atualizar a coluna “ds_path_arquivo”, basta utilizar o slice file[:-41], assim vamos extrair tudo que estiver antes dos 40 caracteres do nome do arquivo. E para atualizar a coluna ds_arquivo com o nome do arquivo, extraímos os últimos 40 caracteres, utilizando file[-40:]. O terceiro parâmetro “s_cod” é para diferenciar cada registro que está sendo atualizado.

Estou considerando o cenário do tudo ou nada, pois o commit só é feito no final.

No link a seguir é possível baixar o código de migração: https://pastebin.com/UDQEjhuv

Por fim, basta executar pelo terminal, lembrando que é necessário executá-lo na máquina do sistema de arquivos:

 python MigracaoDados.py

Na imagem a seguir veja o resultado da execução:

Agora vamos ver como ficou a tabela no banco de dados:

Apesar dos dados da coluna “ds_arquivo” parecerem todos iguais, há uma diferença de caracteres em todos os registros. Também veja que a coluna “ds_path_arquivo” também foi atualizada com o path onde o arquivo foi gravado.

E na próxima imagem, os arquivos gravados em disco:

Deve ter surgido alguma dúvida sobre o porquê da não exclusão da coluna “arquivo”, Pois não há sentido algum em manter esses dados duplicados (em banco e no sistema de arquivos). A exclusão da coluna deve sim ser feita, porém, depois de realizadas todas as validações possíveis, como verificação da integridade do arquivo, por exemplo, ver se um determinado arquivo que foi gravado em disco se corresponde efetivamente ao que está no banco.

Caso vá reutilizar esse código de acordo com algum cenário da sua empresa, é interessante renomear a coluna “arquivo” e verificar se a aplicação passará a enxergar esses arquivos em disco. Portanto, após ter certeza de que a migração foi realizada com sucesso e a aplicação não acessa mais a coluna com os arquivos binários, a coluna poderá ser excluída da tabela.

A critério de informação, esse código foi utilizado em um cenário real na empresa a qual trabalho, onde tivemos que migrar pouco mais de 320 mil arquivos do banco para sistema de arquivos. O desempenho da migração vai depender do poder de processamento da máquina. Numa máquina do ambiente de testes, com um hardware não tão robusto, todos esses arquivos foram migrados em torno de 3h:30, enquanto que em outra máquina com as configurações similares ao ambiente de produção, todos os arquivos foram migrados em aproximadamente 2h.

CONCLUSÃO

Espero que o artigo tenha sido relevante para o aprendizado e possa servir como base para atender alguma necessidade que a sua empresa tenha, basta realizar as devidas adaptações no código, seguindo os requisitos de negócio estipulados.

Qualquer sugestão, crítica ou dúvida, sinta-se a vontade e deixe nos comentários.

Até a próxima!

Sorry, the comment form is closed at this time.