Salvar arquivos em Banco de Dados com Java

Uma das questões que surgem quanto ao armazenamento de arquivos (imagens, documentos, etc) é como e onde salvar tais arquivos.
Neste post explicarei como salvar e recuperar arquivos em banco de dados (PostgreSQL) com Java.

Quando temos um sistema que trabalha com imagens, documentos ou algum tipo de arquivo, temos várias formas de tratar este tipo de funcionalidade. As duas formas mais comuns são:

– Salvar o arquivo em disco (file system) e sua localização no banco de dados (como varchar);

– Salvar o arquivo inteiro dentro do banco de dados.

Existem muita discussão em cima destas duas soluções, então, vou postar aqui os prós e os contras de cada uma delas:
File System – Vantagens

  • Talvez uma das vantagens seja a facilidade de salvar em disco. Basta mandar escrever em um diretório em específico e pronto.
  • Outra vantagem seria a facilidade de acesso aos arquivos para backup: basta ir no diretório onde eles estão e mandar compactar tudo :).

File System – Desvantagens

  • Uma das desvantagens é o desacoplamento do banco de dados com o arquivo em si. Se você excluir a entidade referente ao arquivo, por exemplo, terá de ir no diertório para excluir o arquivo manualmente. Da mesma forma que a leitura do arquivo não pode ser feita apenas utilizando um JOIN na tabela de arquivos: será necessário, além do SELECT no banco, acessar o arquivo em disco.
  • Segurança: o arquivo pode ser facilmente encontrado (ou até mesmo excluído) no disco. Além disso, a aplicação terá de ter permissão de leitura/escrita no diretório em questão – o que pode ser um problema se tratando de aplicações web.

Banco de Dados – Vantagens

  • O que é desvantagem em File System se torna vantagem em se tratando de banco de dados. O arquivo pode ser facilmente relacionado a outros registros de outras tabelas. Podem ser recuperados através de JOIN e excluídos com relacionamentos do tipo CASCADE.
  • Outra vantagem é que todos os dados estão armazenado em um único local. O backup do banco de dados já contém os arquivos, e a migração do banco de dados para outro local é mais fácil, pois não implica em diretórios e permissões.

Banco de Dados – Desvantagens

  • Uma das desvantagens seria a performance do banco de dados. Na verdade esta é uma desvantagem “questionável”, pois depende muito do hardware, SGBD e carga de usuários. Grandes aplicações (picasaweb, flickr, youtube) utilizam banco de dados para armazenar arquivos – o que torna esta desvantagem passível de questionamento.
  • O backup da base de dados pode ser muito grande (se tiver 1gb de imagens, o backup do banco irá ficar com toda esta carga). Normalmente backups de bancos de dados são armazenados em uma máquina diferente da que etá rodando o SGBD, o que pode ser problemático para arquivos muito grandes.

A estratégia depende muito do problema em questão. De qualquer forma, irei explicar aqui como utilizar a estratégia de salvar arquivos diretamente no banco de dados.

Preparando o Banco de Dados

No banco de dados Posrgres, temos a seguinte tabela (e sequência):

CREATE TABLE arquivo (
    id integer NOT NULL,
    nome character varying,
    arquivo bytea,
    CONSTRAINT pk_arquivo PRIMARY KEY (id)
);
CREATE SEQUENCE seq_arquivo INCREMENT 1 START 1;

O campo do tipo BYTEA representa um array de bytes, e nesta coluna iremos armazenar os nossos arquivos.

Enviando o arquivo para o banco de dados

Para armazenar um arquivo (File) em java, podemos fazer da seguinte forma:

public boolean insertFile( File f ){
    Connection c = this.getConnection();//busca uma conexao com o banco
    try {
        PreparedStatement ps = c.prepareStatement("INSERT INTO arquivo( id, nome, arquivo ) VALUES ( nextval('seq_arquivo'), ?, ? )");

        //converte o objeto file em array de bytes
        InputStream is = new FileInputStream( f );
        byte[] bytes = new byte[(int)f.length() ];
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        ps.setString( 1, f.getName() );
        ps.setBytes( 2, bytes );
        ps.execute();
        ps.close();
        c.close();
        return true;

    } catch (SQLException ex) {
        ex.printStackTrace();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
    return false;
}

Entendendo o método: primeiro, convertemos o arquivo para um array de bytes (byte []). Depois, utilizamos o setBytes do PreparedStatement para armazenar o arquivo na coluna especificada.

E pronto: ao utilizar este método, guardamos o nome e o arquivo em si em uma tabela no banco de dados.

Recuperando o arquivo do banco de dados

Para recuperar o arquivo do banco de dados e salvar ele em disco (File) em java, podemos fazer da seguinte forma:

public File getFile( int id ){
    Connection c = this.getConnection();//busca uma conexao com o banco
    File f = null;
    try {
        PreparedStatement ps = c.prepareStatement("SELECT id, nome, arquivo FROM arquivo WHERE id = ?");
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        if ( rs.next() ){
            byte [] bytes = rs.getBytes("arquivo");
            String nome = rs.getString("nome");

            //converte o array de bytes em file
            f = new File( "/local_a_ser_salvo/" + nome );
            FileOutputStream fos = new FileOutputStream( f);
            fos.write( bytes );
            fos.close();
        }
        rs.close();
        ps.close();
        c.close();
        return f;
} catch (SQLException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
ex.printStackTrace();
}
return null;
}

O procedimento é o inverso: retorna o valor da coluna em array de bytes e cria um arquivo em disco a partir destes bytes.

Claro que este é um exemplo simples: em cenários mais “profissionais”, o arquivo recuperado nem precisa ser salvo em disco: pode ser mantido em memória (byte array) e enviado para a tela “renderizar” o arquivo.

Abraço!

Referência: http://imar.spaanjaars.com/414/storing-uploaded-files-in-a-database-or-in-the-file-system-with-aspnet-20

Deixe um Comentário

17 Comentários.

  1. ae professor mto bom o post

  2. Is great to see good post!
    Congratulations.

  3. Esse Post é muito bom estava precisando mesmo disso para um trabalho de escola…Muito obg

  4. Porque so funciona do IE e não funciona no firefox e no chrome?

    • Dede,

      Em nenhum momento do post estamos falando da camada de visão – o post aqui é independente de plataforma (web/desktop). Se funciona para você apenas em um navegador, recomento que você verifique qual tecnologia de visão está sendo utilizada em seu projeto (jsp, jsf, javascript, etc), pois o problema provavelmente esta lá.

      Um abraço

  5. Cara, antes de tudo, ficou MUITO bom seu post, muito bem explicado. Porém, eu precisava salvar o arquivo no banco e ao recuperá-lo visualizar na tela ao invés de salvar em um diretório, como você comentou em cima.
    Poderia me ajudar?
    Obrigado.
    E parabéns pelo post!

    • Posso ajudar sim. Qual é a duvida?

      Um abraço!

      • Boa noite Hallan.
        Muito bom o post….porporém fiquei com uma dúvida.
        Desculpe…sei q ja faz mais de 2 anos o post, mas só o ví agora.

        Qual a utilidade do while no meio do código, no momento de salvar os bytes?
        Vi q vc seta o array de bytes direto no statement e em nenhum lugar utilizou o offset.
        Minha duvida é apenas essa, qual a utilidade desse loop?

        Até mais e valeu. Fora essa dúvida, o post esta perfeito.

        Abs.

  6. como selecionar um arquivo .jpg no desktop e salva-lo no banco de dados e quando eu pesquisar: um exemplo um determinado funcionário alem de aparecer os dados aparecer também a foto, não acho nada na net seria uma CRUD para imagem, você tem alguma?

  7. Funcionou perfeitamente.
    Excelente trabalho!

    Adicionado ao meu pacote de utilidades.

  8. Boa noite Hallan.
    Muito bom o post….porporém fiquei com uma dúvida.
    Desculpe…sei q ja faz mais de 2 anos o post, mas só o ví agora.

    Qual a utilidade do while no meio do código, no momento de salvar os bytes?
    Vi q vc seta o array de bytes direto no statement e em nenhum lugar utilizou o offset.
    Minha duvida é apenas essa, qual a utilidade desse loop?

    Até mais e valeu. Fora essa dúvida, o post esta perfeito.

    Abs.

  9. Como que gravo imagem no banco MySQL em campo blob? Eu preciso escolher a imagem no disco para gravar no banco.

  10. Mas como faz pra ligar um formulario html que envia arquivo para o banco de dados?

  11. Que envia arquivo pelo formulario html?

Deixe um Comentário


NOTA - Você pode usar estesHTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>