Páginas

quarta-feira, 30 de julho de 2014

Mapeamento de tipos SQL em Java

Ao trabalhar com banco de dados em Java fazemos uso dos drivers JDBC. Estes drivers são fornecidos pelos desenvolvedores dos bancos de dados e seguem uma implementação definida pelo Java e realizam, entre diversas responsabilidades, o mapeamento entre a forma que uma informação é armazenada no banco de dados e os tipos primitivos ou classes em Java, que são o que efetivamente iremos trabalhar durante nossa codificação. O principal objetivo do JDBC é a abstração do sistema de banco de dados, mas esta abstração não é totalmente infalível e algumas práticas devem ser observadas, entre elas o correto mapeamento entre os tipos, nesta postagem vamos realizar uma pequena análise nesta prática.

A abstração da tipagem

Um desenvolvedor, normalmente, não irá se preocupar em como a sua String "nome da empresa" está definida no banco de dados, até por que o banco de dados pode mudar dependendo do cliente, e esta forma de armazenamento normalmente recairá sobre o DBA. Entre estes dois deve existir a documentação do sistema que irá definir a forma como cada um deve trabalhar, e, para evitar dores de cabeça, entre esta documentação é recomendável que existam alguns acordos técnicos como padrão de nomenclaturas e tipos de armazenamento.

Os tipos de dados SQL

Cada banco de dados possui o seu conjunto próprio de tipos de dados, maior ou menor que os concorrentes, mas a especificação JDBC define um uso baseado em um conjunto genérico de tipos de dados SQL. Nesta postagem iremos analisar este conjunto genérico, mas uma análise de mapeamentos mais detalhada de qualquer banco, se necessária, pode ser encontrado na internet.

CHAR, VARCHAR e LONGVARCHAR

Estes tipos representam textos, com tamanhos variáveis ou não, e devem ser mapeados para a classe String. São manipulados através dos métodos ResultSet.getString e PreparedStatement.setString.

NUMERIC e DECIMAL

Estes tipos representam números reais com precisão fixa, onde a precisão é importante para os cálculos matemáticos, arredondamentos ou representação textual, como, por exemplo, uso monetário ou comparações precisas. Devem ser mapeados para a classe java.math.BigDecimal e são manipulados através dos métodos ResultSet.getBigDecimal e PreparedStatement.setBigDecimal.
Caso você não conheça os problemas comuns encontrados ao trabalhar com os tipos float e double, entenda que estes tipos geram pequenas inconsistências nas precisões numéricas que podem gerar falhas de lógica, inicie sua pesquisa a partir daqui: http://floating-point-gui.de/.

BIT e BOOLEAN

Representam valores boolean e devem ser mapeados para a o tipo primitivo boolean ou a classe Boolean. São manipulados através dos métodos ResultSet.getBoolean e PreparedStatement.setBoolean.
Muitos bancos de dados, como o Oracle, não implementam um tipo como estes. No Oracle uma das recomendações é o uso do tipo CHAR com uma constraint de domínio de valores, veja abaixo:
ALTER TABLE tabela ADD coluna_boleana CHAR CHECK (coluna_boleana IN (0,1) );
Mas como dito antes, o driver JDBC abstrai esta característica e torna transparente para o desenvolvedor.

TINYINT

Representam números inteiros de 0 a 255 e devem ser mapeados para o tipo byte. São manipulados através dos métodos ResultSet.getByte e PreparedStatement.setByte.
Este tipo sofre com falta de suporte e é recomendável preferir o uso do tipo SMALLINT no lugar.

SMALLINT

Representam números inteiros de -32768 a 32767 e devem ser mapeados para o tipo short. São manipulados através dos métodos ResultSet.getShort e PreparedStatement.setShort.

INTEGER

Representam números inteiros de -2147483648 e 2147483647 e devem ser mapeados para o tipo int ou a classe Integer. São manipulados através dos métodos ResultSet.getInt e PreparedStatement.setInt.

BIGINT

Representam números inteiros de 64 bits e devem ser mapeados para o tipo long ou a classe Long. São manipulados através dos métodos ResultSet.getLong e PreparedStatement.setLong.

REAL

Representam números reais de precisão simples (7 dígitos de mantissa) e devem ser mapeados para o tipo float ou a classe Float. São manipulados através dos métodos ResultSet.getFloat e PreparedStatement.setFloat.

FLOAT e DOUBLE

Representam números reais de precisão dupla (15 dígitos de mantissa) e devem ser mapeados para o tipo double ou a classe Double. São manipulados através dos métodos ResultSet.getDouble e PreparedStatement.setDouble.
Para não gerar confusões no entendimento da precisão devido ao mapeamento SQL-Java o tipo SQL FLOAT deve ser evitado.

BINARY, VARBINARY or LONGVARBINARY

Representam conjuntos de valores binários e devem ser mapeados para um vetor do tipo byte[]. São manipulados através dos métodos ResultSet.getBytes e PreparedStatement.setBytes.

DATE

Representam datas que consistem no conjunto de valores para dia, mês e ano e devem ser mapeados para a classe java.sql.Date. São manipulados através dos métodos ResultSet.getDate e PreparedStatement.setDate.
É recomendável que os valores de hora, minutos, segundos e milissegundos da classe java.util.Date sejam definidos como zero ao realizar conversões.

TIME

Representam horas que consistem no conjunto de valores para hora, minutos e segundos e devem ser mapeados para a classe java.sql.Time. São manipulados através dos métodos ResultSet.getTime e PreparedStatement.setTime.
É recomendável que os valores de dia, mês e ano da classe java.util.Date sejam definidos como 1º de janeiro de 1970.

TIMESTAMP

Representam o equivalente aos tipos DATE e TIME juntos mais o valor de nanossegundos e devem ser mapeados para a classe java.sql.Timestamp. São manipulados através dos métodos ResultSet.getTimestamp e PreparedStatement.setTimestamp.
Normalmente o tipo TIMESTAMP armazenará, também, o timezone.

Outros tipos SQL

Além destes tipos existem outras especificações JDBC como, por exemplo, BLOB ou CLOB. Em geral estes outros mapeamentos seguem a regra conforme descrito nos tipos desta postagem. Se necessário, uma pequisa mais aprofundada é recomendada.

Mapeamentos cruzados

A especificação JDBC também permite o mapeamento cruzado entre os diversos tipos, ou seja, é possível utilizar o método ResultSet.getString em praticamente todos os tipos SQL, o driver JDBC tentará realizar a conversão da forma mais adequada possível. Mas, isto não é uma regra, algumas conversões são impossíveis ou podem gerar erros, como por exemplo tentar extrair um número de um tipo VARCHAR.

Especificidades de bancos de dados

Caso você seja o responsável pela implementação SQL é importante conhecer as especificidades do banco de dados que irá trabalhar, muitas vezes um tipo é desencorajado ou inexistente e outro é recomendado no lugar, como foi anotado no tipo SQL BIT acima. Esta recomendação será obedecida pelo driver JDBC e não causará nenhum problema para o desenvolvedor.

Referências

http://docs.oracle.com/javase/tutorial/jdbc/basics/index.html
http://docs.oracle.com/cd/B19306_01/java.102/b14188/datamap.htm
http://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html
http://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html
http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html

Nenhum comentário:

Postar um comentário

Olá! Antes de postar seu comentário, por favor, observe que comentários técnicos, elogios e sugestões são antecipadamente agradecidos, esclarecimentos sobre os conceitos envolvidos na postagem serão respondidos da melhor forma possível, mas pedidos de ajuda técnica ou suporte individual deverão ser feitos através do formulário de contato. Grato!