Mapeamento de tipos SQL em Java
Autor: Claudio Weiler | Publicado: 2014-07-30T22:08:00-03:00

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 tipagemUm 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 SQLCada 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 LONGVARCHAREstes 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 DECIMALEstes 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 BOOLEANRepresentam 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.

TINYINTRepresentam 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.

SMALLINTRepresentam 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.

INTEGERRepresentam 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.

BIGINTRepresentam 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.

REALRepresentam 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 DOUBLERepresentam 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 LONGVARBINARYRepresentam 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.

DATERepresentam 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.

TIMERepresentam 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.

TIMESTAMPRepresentam 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 SQLAlé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 cruzadosA 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 dadosCaso 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ênciashttp://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