Neste artigo veremos como trabalhar com byte em Java e com PostgreSQL. O tipo byte é muito utilizado para serialização de objetos para transferência entre pontos distintos, ou seja, sistemas distribuidos, mas não somente isto, além disso o byte é amplamente utilizado para salvar informações em considerar codifição de caracteres. Imagine, por exemplo, que você precise gravar um texto escrito em Japonês mas o encode do seu banco não suporte tal codificação, neste caso o byte é uma ótima alternativa para que você não perca o conteúdo, pois se você tentar salvar como varchar os caracteres ficarão totalmente desconfigurados e você perderá a informação.
Em Java usamos o tipo “byte” que é uma palavra reservada para definição do tipo de uma variável, mas como nosso caso sempre será vários “bytes” e não apenas 1, trabalharemos com um array de bytes, ou seja, “byte[]”.
No PostgreSQL o tipo byte é definido através da palavra reservada “bytea”, ou seja, ao gerarmos um array de bytes no Java nós salvamos no campo “bytea” no PostgreSQL. Vejamos um caso onde a aplicação do byte pode ser necessária:
“O seu sistema A recebe informações de um sistema B e deve salvar essas informações no banco de dados para depois retornar ao sistema B novamente, quando este solicitar. O sistema A não sabe qual o banco de dados e muito menos a codificação usada pelo sistema B, sendo assim como poderá o sistema A escolher um encode apropriado ? (UTF-8, ISO e etc). A melhor maneira de solucionar este problema é criar um campo no Java do tipo “byte[]” e um no PostgreSQL do tipo “bytea”, assim nós não nos preocuparemos com o tipo de informação contida.
Neste artigo vamos construir um projeto simples para demonstrar o uso do byte no Java e no PostgreSQL.
O projeto
O projeto proposto terá como objetivo salvar um objeto Cliente no banco de dados e depois retorná-lo a qualquer momento, apenas pelo seu ID.
Vejamos nosso script para criação de tabela no banco, como mostra a Listagem 1.
CREATE TABLE cliente
(
id integer NOT NULL,
objeto bytea,
CONSTRAINT cliente_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE cliente
OWNER TO postgres;
Perceba acima que nossa tabela cliente possui dois campos: id e objeto. Onde o id é um inteiro, o que já estamos acostumados e usar e o objeto é do tipo bytea. Nosso campo objeto armazenará exatamente o que descreve, um objeto. Isso significa que poderemos armazenar um objeto cliente inteiro com todas suas dependências e depois apenas retorná-lo da forma como era, um Cliente.
Alguns podem pensar: É muito mais fácil trabalhar dessa forma, evitamos de criar centenas de campos e criamos apenas 1 campo objeto, isso é ótimo porém tem duas desvantagens:
- Performance: Pelo fato de precisar serializar e deserializar o objeto toda vez isso torna o processo mais lento;
- integridade: Você perde o controle sobre a integridade dos dados. Como você vai criar um “check” em algum campo se o campo nem existe;
- Consultas de relatórios: Torna-se inviável criar relatórios se você apenas tem informações em “byte”;
Enfim, não pense em ir colocando bytea em todas as suas tabelas, faça isso apenas quando realmente for necessário.
O nosso projeto irá conter apenas três classes, são elas:
- Conexao.java: Classe responsável por realizar a conexão com o banco de dados;
- Cliente.java: Nosso bean que será salvo no banco;
- ClienteMainSave.java: Classe principal, com método main, responsável por salvar e retornar o objeto cliente do banco de dados.
- ClienteMainGet.java: Classe principal, com método main, responsável por retornar o objeto cliente do banco de dados.
Vejamos a nossa classe Conexao.java presente na Listagem 2.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Conexao {
private static Connection connection;
public static Connection getConnection() {
if (connection == null) {
try {
Class.forName("org.postgresql.Driver");
String host = "localhost";
String port = "5432";
String database = "teste";
String user = "postgres";
String pass = "postgres";
String url = "jdbc:postgresql://" + host + ":" + port + "/"
+ database;
connection = DriverManager.getConnection(url, user, pass);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return connection;
}
public static void close() {
if (connection == null){
throw new RuntimeException("Nenhum conexao aberta");
}else{
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Nossa classe Conexao não é o objetivo principal do nosso artigo mas faz-se necessária para que nosso projeto funcione corretamente. No método getConnection() definimos as configurações necessárias para conectar-se ao nosso banco de dados, lembrando que você deve importar o “.jar” do driver PostgreSQL JDBC ao seu projeto. O método close() simplesmente fecha a conexão quando necessário, mas fazendo uma checagem com antecedência para evitar chamar o método close() em um objeto nulo.
Você não precisa se preocupar sobre detalhes da conexão, pois não é o foco do nosso artigo. Apenas mude as configurações das variáveis:
- host: IP da máquina onde está o banco de dados ou localhost se for a sua própria máquina;
- port: Porta usada para o PostgreSQL, geralmente 5432 se não foi alterada;
- database: Nome do banco de dados;
- user: Usuário do banco de dados;
- pass: Senha do banco de dados;
Definindo corretamente as variáveis acima e importando o driver do PostgreSQL no seu projeto a conexão será com sucesso.
Perceba também que nossos método são todos estáticos, pois não há necessidade de instanciar a classe conexão, precisaremos apenas capturar a conexão e usá-la.
import java.io.Serializable;
import java.util.Date;
public class Cliente implements Serializable {
private int id;
private String nome;
//m = masculino, f = feminino
private char genero;
private Date dataNascimento;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public char getGenero() {
return genero;
}
public void setGenero(char genero) {
this.genero = genero;
}
public Date getDataNascimento() {
return dataNascimento;
}
public void setDataNascimento(Date dataNascimento) {
this.dataNascimento = dataNascimento;
}
}
Nosso Bean Cliente, presente na Listagem 3, implementa a interface Serializable, e isto é necessário para conseguirmos salvar o objeto Cliente no banco como um byte[]. Objetos sem a interface Serializable não podem sofrer serialização e muito menos deserialização, sendo assim ficamos impedidos de salvar o Cliente como byte no banco de dados.
Na serialização do objeto apenas o nome dos atributos e valores são conservados, os métodos getters e setters não são, você verá isso mais a frente.
Vejamos os dois métodos que farão a “mágica” necessária para salvar o cliente como byte[], como mostra a Listagem 4.
private static byte[] converterClienteParaByte(Cliente cliente) {
try {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream ous;
ous = new ObjectOutputStream(bao);
ous.writeObject(cliente);
return bao.toByteArray();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
O ObjectOutputStream recebe como argumento no seu construtor um ByteArrayOutputStream que servirá para retornar nosso array de byte, o ObjectOutputStream escreve o nosso objeto serializando o mesmo, por isso ele tem que implementar Serializable. O retorno do nosso método é um byte[]. Agora vejamos o processo inverso, transformar no array de byte (byte[]) em um objeto do tipo Cliente.
private static Cliente converterByteParaCliente(byte[] clienteAsByte) {
try {
ByteArrayInputStream bao = new ByteArrayInputStream(clienteAsByte);
ObjectInputStream ous;
ous = new ObjectInputStream(bao);
return (Cliente) ous.readObject();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Note que na Listagem 4 o nosso ByteArrayInputStream não recebe nada no construtor, porém, na Listagem 5 recebe um array de byte. Esse objeto é passado ao ObjectInputStream que com o readObject() faz a conversão do byte[] para o Object, que em nosso caso esta sendo feito um Cast para Cliente.
Você já deve ter percebido que o primeiro método será usado para salvar o objeto no banco e o segundo método será usado para retornar o objeto do banco. Estes tem muito mais utilidades que apenas salvar e recuperar dados do banco, principalmente trantando-se de transferência de dados em rede. Para quem está trabalhando com Socket é uma ótima pedida para envio de arquivos.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class ClienteMainSave {
/**
* @param args
*/
public static void main(String[] args) {
Cliente cliente = new Cliente();
cliente.setId(1);
cliente.setDataNascimento(new Date());
cliente.setGenero(''m'');
cliente.setNome("DevMedia");
salvarCliente(cliente);
}
private static void salvarCliente(Cliente cliente) {
try {
Connection con = Conexao.getConnection();
PreparedStatement ps = con.prepareStatement("INSERT
INTO cliente(id, objeto) values (?, ?)");
ps.setInt(1, cliente.getId());
ps.setBytes(2, converterClienteParaByte(cliente));
ps.execute();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static byte[] converterClienteParaByte(Cliente cliente) {
try {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream ous;
ous = new ObjectOutputStream(bao);
ous.writeObject(cliente);
return bao.toByteArray();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
Vamos entender passo a passo a nossa Listagem 6:
- No nosso método main() criamos o objeto Cliente e definimos todos os seus campos, após essas definições nós passamos o objeto Cliente ao método salvarCliente();
- O método salvarCliente() monta um PreparedStatement para inserir o registro no banco, mas precisamos converter o Cliente em um array de bytes e para isso chamamos o método converterClienteParaByte() que já foi explicado anteriormente;
Até este ponto já temos o registro salvo no banco de dados contendo o ID seguido de uma sequência de bytes. Agora vejamos como capturar essas informações, como mostra a Listagem 7.
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ClienteMainGet {
/**
* @param args
*/
public static void main(String[] args) {
Cliente cliente = getClienteFromDB(1);
System.out.println(cliente.getId());
System.out.println(cliente.getGenero());
System.out.println(cliente.getNome());
System.out.println(cliente.getDataNascimento());
}
private static Cliente getClienteFromDB(int id) {
try {
Connection con = Conexao.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT objeto
FROM cliente WHERE id = ?");
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
return converterByteParaCliente(rs.getBytes(1));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private static Cliente converterByteParaCliente(byte[] clienteAsByte) {
try {
ByteArrayInputStream bao = new ByteArrayInputStream(clienteAsByte);
ObjectInputStream ous;
ous = new ObjectInputStream(bao);
return (Cliente) ous.readObject();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
A lógica nessa classe é o inverso da mostrada na Listagem 6, pois aqui nós chamamos o método getClienteFromDB() passando o id do cliente. Internamente este método percorre o ResultSet retornando o campo “objeto” do banco de dados, porém como sabemos este campo é um array de bytes e precisamos converte-lo para um objeto Cliente, sendo assim chamamos o método converterByteParaCliente() que também já foi explicado anteriormente.
Veja que ao realizar um select no nosso banco de dados retornando o conteúdo do campo “objeto” temos o seguinte retorno. Observe a Listagem 8.
DML:
SELECT objeto FROM cliente
Saída:
"\254\355\000\005sr\000\007Cliente\364\221#b\262p7\374\002
000\004C\000\006generoI\000\002idL\000
016dataNascimentot\000\020Ljava/util/Date;L\000\004nomet\000
022Ljava/lang/String;xp\000m\000\000\000
001sr\000\016java.util.Datehj\201\001KYt\031\003\000\000xpw\010
000\000\001J\274ca\261xt\000\010DevMedia"
Todo conteúdo acima é deserializado para construir o objeto que nós serializamos. Lembra que falamos logo no início que os métodos não são serializados, apenas seus atributos e valores. Isso pode ser percebido na listagem 8.
Um pouco sobre bytea no PostgresQL
No PostgreSQL, o bytea armazena um “binary String” que na verdade é um array de bytes. Cada byte contém 8 bits e um sequência de bytes podem conter inúmeros bits.
A questão é que o PostgreSQL possui dois tipos de tratamento para o bytea: hex e escape. Quando usado o formato hex o tipo bytea usa 2 hexadecimais por cada byte armazenado, além de ser aceito tanto no input como no output das informações, ou seja, tanto da escrita das informações como na leitura destas. O formato hex é mais novo e só está presente nas versões 9.0 e posteriores do PostgreSQL. Por outro lado o formato “escape” é mais tradicional e antigo, utilizando a representação de caracteres em “ASCII”.
Neste artigo estudamos como utilizar o tipo byte para trabalhar com dados serializados, passando o objeto cliente de um lado ao outro sem se preocupar com os campos que o mesmo possui. Como citamos anteriormente, esse processo é muito útil quando trabalhamos com transferência de dados na Rede, pois além de garantir a integridade da informação, conseguimos minimizar a complexidade na recupereção dessa informação.
O PostgreSQL já nos da um tipo especial de campo para trabalhar com byte, o bytea que armazena um array de bytes podendo estes serem formatados usando “hex” ou “escape”. Explicamos um pouco sobre cada um desses tipos na última seção, mas vale ressaltar que o “hex” é o tipo mais novo e só está presente na versão 9.0 ou posterior do PostgreSQL, além de ser a mais indicada para aplicação que usem tal versão do banco.
Confira outros conteúdos:
Black November
Desconto exclusivo para as primeiras 200 matrículas!
Pagamento anual
12x no cartão
De: R$ 69,00
Por: R$ 59,00
Total: R$ 708,00
Garanta o desconto
- Formação FullStack Completa
- Carreira Front-end I e II, Algoritmo e Javascript, Back-end e Mobile
- +10.000 exercícios gamificados
- +50 projetos reais
- Comunidade com + 200 mil alunos
- Estude pelo Aplicativo (Android e iOS)
- Suporte online
- 12 meses de acesso
Pagamento recorrente
Cobrado mensalmente no cartão
De: R$ 79,00
Por: R$ 64,00 /mês
Total: R$ 768,00
Garanta o desconto
- Formação FullStack Completa
- Carreira Front-end I e II, Algoritmo e Javascript, Back-end e Mobile
- +10.000 exercícios gamificados
- +50 projetos reais
- Comunidade com + 200 mil alunos
- Estude pelo Aplicativo (Android e iOS)
- Suporte online
- Fidelidade de 12 meses
- Não compromete o limite do seu cartão
<Perguntas frequentes>
Nossos casos de sucesso
Eu sabia pouquíssimas coisas de programação antes de começar a estudar com vocês, fui me especializando em várias áreas e ferramentas que tinham na plataforma, e com essa bagagem consegui um estágio logo no início do meu primeiro período na faculdade.
Estudo aqui na Dev desde o meio do ano passado!
Nesse período a Dev me ajudou a crescer muito aqui no trampo.
Fui o primeiro desenvolvedor contratado pela minha
empresa. Hoje eu lidero um time de desenvolvimento!
Minha meta é continuar estudando e praticando para ser um
Full-Stack Dev!
Economizei 3 meses para assinar a plataforma e sendo sincero valeu muito a pena, pois a plataforma é bem intuitiva e muuuuito didática a metodologia de ensino. Sinto que estou EVOLUINDO a cada dia. Muito obrigado!
Nossa! Plataforma maravilhosa. To amando o curso de desenvolvimento front-end, tinha coisas que eu ainda não tinha visto. A didática é do jeito que qualquer pessoa consegue aprender. Sério, to apaixonado, adorando demais.
Adquiri o curso de vocês e logo percebi que são os melhores do Brasil. É um passo a passo incrível. Só não aprende quem não quer. Foi o melhor investimento da minha vida!
Foi um dos melhores investimentos que já fiz na vida e tenho aprendido bastante com a plataforma. Vocês estão fazendo parte da minha jornada nesse mundo da programação, irei assinar meu contrato como programador graças a plataforma.
Wanderson Oliveira
Comprei a assinatura tem uma semana, aprendi mais do que 4 meses estudando outros cursos. Exercícios práticos que não tem como não aprender, estão de parabéns!
Obrigado DevMedia, nunca presenciei uma plataforma de ensino tão presente na vida acadêmica de seus alunos, parabéns!
Eduardo Dorneles
Aprendi React na plataforma da DevMedia há cerca de 1 ano e meio... Hoje estou há 1 ano empregado trabalhando 100% com React!
Adauto Junior
Já fiz alguns cursos na área e nenhum é tão bom quanto o de vocês. Estou aprendendo muito, muito obrigado por existirem. Estão de parabéns... Espero um dia conseguir um emprego na área.
Utilizamos cookies para fornecer uma melhor experiência para nossos usuários, consulte nossa política de privacidade.