O PostgreSQL provê duas formas distintas para armazenar dados binários. Os dados binários podem ser armazenados em uma tabela utilizando o tipo de dado bytea, ou através da funcionalidade de objeto grande que armazena os dados binários em uma tabela a parte em um formato especial, e faz referência a esta tabela armazenando um valor do tipo oid na tabela do usuário.
Para ser possível determinar qual é o método apropriado, é necessário compreender as limitações de cada método. O tipo de dado bytea não é muito adequado para armazenar quantidades muito grande de dados binários. Embora uma coluna do tipo bytea possa conter até 1 GB de dados binários, é necessária uma grande quantidade de memória para processar um valor tão grande. O método objeto grande para armazenar dados binários é mais adequado para armazenar valores muito grandes, mas possui suas próprias limitações. Especificamente, a exclusão da linha que contém a referência ao objeto grande não exclui o objeto grande. A exclusão do objeto grande é uma operação que precisa ser realizada em separado. Os objetos grandes também possuem alguns problemas de segurança, uma vez que qualquer um que se conecte ao banco de dados pode ver e/ou modificar qualquer objeto grande, mesmo que não possua permissão para ver/atualizar a linha que faz referência ao objeto grande.
A primeira versão do driver JDBC com suporte a tipo de dado bytea foi a 7.2. A introdução desta funcionalidade na versão 7.2 ocasionou uma mudança de comportamento em comparação com as versões anteriores. Desde a versão 7.2, os métodos getBytes()
, setBytes()
, getBinaryStream()
e setBinaryStream()
operam no tipo de dado bytea. Na versão 7.1, e nas anteriores, estes métodos operavam no tipo de dado oid associado ao objeto grande. É possível fazer com que o driver volte a ter o comportamento antigo da versão 7.1, definindo a propriedade compatible do objeto Connection
com o valor 7.1.
Para utilizar o tipo de dado bytea deve-se simplesmente utilizar os métodos getBytes()
, setBytes()
, getBinaryStream()
e setBinaryStream()
.
Para utilizar a funcionalidade de objeto grande pode ser utilizada a classe LargeObject
fornecida pelo driver JDBC do PostgreSQL, ou utilizar os métodos getBLOB()
e setBLOB()
.
Importante: Os Objetos Grandes devem ser acessados dentro de um bloco de transação do SQL. Um bloco de transação pode ser iniciado chamando
setAutoCommit(false)
.
Nota: Em uma versão futura do driver JDBC, os métodos
getBLOB()
esetBLOB()
poderão não mais interagir com os Objetos Grandes e, em vez disso, trabalharem com o tipo de dado bytea. Portanto, recomenda-se a utilização da APILargeObject
para trabalhar com Objetos Grandes.
O Exemplo 31-8 contém alguns exemplos de como processar dados binários utilizando o driver JDBC do PostgreSQL.
Exemplo 31-8. Processamento de dados binários no JDBC
Por exemplo, supondo haver uma tabela contendo nomes de arquivos de imagem, e que se deseje armazenar estas imagens em uma coluna bytea:
CREATE TABLE tbl_imagem (nome_da_imagem text, imagem bytea);
Para inserir a imagem seria utilizado:
File file = new File("minha_imagem.jpg"); InputStream fis = new FileInputStream(file); PreparedStatement ps = conn.prepareStatement("INSERT INTO tbl_imagem VALUES (?, ?)"); ps.setString(1, file.getName()); ps.setBinaryStream(2, fis, (int)file.length()); ps.executeUpdate(); ps.close(); fis.close();
No exemplo acima, setBinaryStream()
transfere um determinado número de bytes do arquivo para a coluna do tipo bytea. Também poderia ter sido feito utilizando o método setBytes()
, se o conteúdo da imagem estivesse armazenado como byte[]
.
Trazer a imagem é mais fácil ainda (Foi utilizado PreparedStatement
neste exemplo, mas a classe Statement
também poderia ter sido utilizada).
PreparedStatement ps = conn.prepareStatement("SELECT imagem " + "FROM tbl_imagem " + "WHERE nome_da_imagem = ?"); ps.setString(1, "minha_imagem.jpg"); ResultSet rs = ps.executeQuery(); if (rs != null) { while (rs.next()) { byte[] imgBytes = rs.getBytes(1); // utilizar os dados de alguma maneira System.out.println("Tamanho da imagem: " + imgBytes.length); } rs.close(); } ps.close();
No exemplo acima os dados binários foram trazidos como byte[]
. Em vez disso, poderia ser utilizado um objeto InputStream
.
Como alternativa, se estiver sendo armazenado um arquivo muito grande pode ser utilizada a API LargeObject
para armazenar o arquivo:
CREATE TABLE tbl_imagem_lo (nome_da_imagem text, oid_da_imagem oid);
Para inserir a imagem seria utilizado:
// Todas as chamadas à API do LargeObject devem // ser feitas dentro de um bloco de transação conn.setAutoCommit(false); // Obter o Gerenciador de Objeto Grande para realizar operações com os mesmos LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI(); // Criar o novo objeto grande int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE); // Abrir o objeto grande para escrita LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE); // Abrir o arquivo File file = new File("minha_imagem.jpg"); FileInputStream fis = new FileInputStream(file); // Copiar os dados do arquivo para o objeto grande byte buf[] = new byte[2048]; int s, tl = 0; while ((s = fis.read(buf, 0, 2048)) > 0) { obj.write(buf, 0, s); tl += s; } // Fechar o objeto grande obj.close(); // Inserir a linha na tabela tbl_imagem_lo PreparedStatement ps = conn.prepareStatement("INSERT INTO tbl_imagem_lo " + "VALUES (?, ?)"); ps.setString(1, file.getName()); ps.setInt(2, oid); ps.executeUpdate(); ps.close(); fis.close(); conn.commit();
Trazer a imagem armazenada no objeto grande:
// Todas as chamadas à API LargeObject devem // ser feitas dentro de um bloco de transação conn.setAutoCommit(false); // Obter o Gerenciador de Objeto Grande para realizar operações com os mesmos LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI(); PreparedStatement ps = conn.prepareStatement("SELECT oid_da_imagem " + "FROM tbl_imagem_lo " + "WHERE nome_da_imagem = ?"); ps.setString(1, "minha_imagem.jpg"); ResultSet rs = ps.executeQuery(); if (rs != null) { while (rs.next()) { // Abrir o objeto grande para leitura int oid = rs.getInt(1); LargeObject obj = lobj.open(oid, LargeObjectManager.READ); // Ler os dados byte buf[] = new byte[obj.size()]; obj.read(buf, 0, obj.size()); // utilizar os dados de alguma maneira System.out.println("Tamanho da imagem: " + buf.length); // Fechar o objeto obj.close(); } rs.close(); } ps.close(); conn.rollback();