Cinco coisas que você não sabia sobre ... Java Database Connectivity

Atualize seu relacionamento com a API JDBC

JDBC ou Java™ Database Connectivity, é um dos pacotes mais usados frequentemente em todo o JDK e mesmo assim poucos desenvolvedores de Java aproveitam sua capacidade total — ou mais atualizada— . Ted Neward oferece uma introdução aos recursos mais novos de JDBC, como ResultSets com rolagem e atualização dinâmicas, e Rowsets que podem funcionar com ou sem uma conexão com o banco de dados, e atualizações com o lote que podem executar várias instruções SQL em um percurso rápido pela rede.

Ted Neward, Principal, Neward & Associates

Ted Neward photoTed Neward é o diretor da Neward & Associates, onde ele dá consultoria, orientação, ensina e faz apresentações sobre Java, .NET, Serviços XML e outras plataformas. Ele reside perto de Seattle, Washington.



28/Out/2010

Sobre esta série

Então, você acha que possui conhecimento sobre programação Java? A verdade é que a maioria dos desenvolvedores tem apenas algum conhecimento sobre a plataforma Java, aprendendo o suficiente para concluir a tarefa. Nesta série,Ted Neward examina a fundo a funcionalidade da plataforma Java para revelar fatos pouco conhecidos que podem ajudar você a superar até mesmo os desafios de programação mais difíceis.

Atualmente, muitos desenvolvedores de Java conhecem a API Java Database Connectivity (JDBC) por meio de uma plataforma de acesso a dados, como Hibernate ou Spring. Mas o JDBC é mais do que um reprodutor de plano de fundo na conectividade do banco de dados. Quanto mais você souber sobre isso, mais eficientes serão suas interações com RDBMS.

Nesta área da série 5 coisas, vou demonstrar vários dos novos recursos introduzidos entre JDBC 2.0 e JDBC 4.0. Projetados visando os desafios de desenvolvimento de software moderno, esses recursos suportam a escalabilidade do aplicativo e a produtividade do desenvolvedor — dois dos desafios comuns enfrentados pelos desenvolvedores de Java atualmente.

1. Funções escalares

Diversas implementações de RDBMS oferecem suporte irregular para recursos de SQL e/ou de valor agregado projetados para a facilidade do desenvolvedor. É bem conhecido, por exemplo, que a SQL fornece uma operação escalar, COUNT(), para retornar o número de linhas que atende um critério de filtragem de SQL específico (ou seja, o predicado WHERE ). Mas, além disso, a tentativa de modificar os valores retornados por SQL pode ser complicada — e a tentativa de obter data e hora atuais do banco de dados pode enlouquecer (e possivelmente estressar também) até mesmo o desenvolvedor de JDBC mais paciente.

Para isso, a especificação JDBC fornece um grau de isolamento/adaptação contra diferentes implementações de RDBMS, por meio de funções escalares. A especificação de JDBC inclui uma lista de operações suportadas que os drivers JDBC reconhecem e adaptam conforme necessário para a respectiva implementação de banco de dados específica. Portanto, para um banco de dados que suportava o retorno à data e/ou hora atuais, pode ser tão simples como a Listagem 1:

Listagem 1. Que horas são?
Connection conn = ...; // get it from someplace
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“{fn CURRENT_DATE()}”);

A lista completa de funções escalares reconhecidas pela API JDBC é fornecida no apêndice da especificação JDBC (consulte Recursos), mas a lista completa pode não ser suportada por um determinado driver ou banco de dados. É possível usar o objeto DatabaseMetaData retornado de Connection para obter as funções suportadas por uma determinada implementação de JDBC, conforme mostrado na Listagem 2:

Listagem 2. O que você pode fazer por mim?
Connection conn = ...; // get it from someplace
DatabaseMetaData dbmd = conn.getMetaData();

A lista de funções escalares é um String retornado de vários métodos DatabaseMetaData . Por exemplo, todos os escalares numéricos são listados via chamada getNumericFunctions() . Execute um String.split() no resultado e —aparecerá uma— lista equals()-testable instantânea.


2. ResultSets Roláveis

É um procedimento muito comum em JDBC para criar um objeto Connection (ou obter um existente) e usá-lo para criar um Statement.. O Statement, que está sendo alimentado por um SQL SELECT, retorna um ResultSet. O ResultSet é então alimentado por meio de um loop while (não diferente de um Iterator) até que ResultSet informe que está vazio, com o corpo do loop extraindo uma coluna por vez da esquerda para a direita.

Toda essa operação é tão comum que ela se tornou quase consagrada: é executada dessa maneira simplesmente porque é como é feita. Ora, ela é completamente desnecessária.

Introduzindo o ResultSet rolável

Muitos desenvolvedores não têm ciência do fato de o JDBC ter sido consideravelmente aperfeiçoado com o passar dos anos, ainda que os aprimoramentos sejam refletidos nos novos números de versão e releases. O primeiro maior aprimoramento, o JDBC 2.0, ocorreu perto do lançamento do JDK 1.2. Quando este artigo foi escrito, o JDBC estava na versão 4.0.

Um dos aprimoramentos interessantes (embora frequentemente ignorado) para o JDBC 2.0 é a capacidade de "rolar" pelo ResultSet, ou seja, podemos ir para frente ou para trás, ou para ambas as direções ao mesmo tempo, conforme necessário. Para isso é necessário um avanço, mas — a chamada de JDBC deve indicar que ela deseja um ResultSet no momento em que Statement é criado.

Verificando o tipo ResultSet

Se você suspeita de que um driver pode realmente não suportar ResultSets roláveis, apesar do que ele informa no DatabaseMetaData, é possível verificar o tipo ResultSet chamando-se getType(). É claro que, se você possuir essa suspeita, poderá não confiar no valor de retorno de getType() também. É suficiente dizer que, se getType() não condiz com o ResultSet retornado, você poderá realmente ser pego em uma armadilha.

Se o driver JDBC subjacente suportar a rolagem, o ResultSet rolável será retornado desse Statement, mas é melhor entender se o driver suporta a capacidade de rolagem antes de solicitá-la. Você pode perguntar sobre a rolagem por meio do objeto DatabaseMetaData , que pode ser obtido de qualquer Connection, conforme descrito anteriormente.

Assim que você tiver um objeto DatabaseMetaData , uma chamada para getJDBCMajorVersion() determinará se o driver suporta pelo menos a especificação JDBC 2.0. É claro que se um driver não condiz com seu nível de suporte para uma determinada especificação, então para reproduzi-lo particularmente com segurança, chame o método supportsResultSetType() com o tipo ResultSet desejado. (É uma constante na classe do ResultSet . Falaremos sobre os valores de cada logo a seguir.)

Listagem 3. Você consegue rolar?
int JDBCVersion = dbmd.getJDBCMajorVersion();
boolean srs = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
if (JDBCVersion > 2 || srs == true)
{
    // scroll, baby, scroll!
}

Solicitando um ResultSet rolável

Supondo-se que seu driver informe que sim (se ele não fizer isso, será necessário um novo driver ou banco de dados), você poderá solicitar um ResultSet rolável passando dois parâmetros para a chamada de Connection.createStatement() , mostrada na Listagem 4:

Listagem 4. Eu quero rolar!
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE, 
                       ResultSet.CONCUR_READ_ONLY);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");

Você deve tomar cuidado especial ao chamar createStatement() porque seu primeiro e segundo parâmetros são ints. (Desaprove o fato de não termos enumerado os tipos até o Java 5!) Qualquer valor int (incluindo o valor constante errado) funcionará com o createStatement().

O primeiro parâmetro, que indica a "capacidade de rolagem" desejada no ResultSet, pode ser um dos três valores aceitos:

  • ResultSet.TYPE_FORWARD_ONLY: Esse é o padrão, o estilo de cursor firehose que conhecemos e adoramos.
  • ResultSet.TYPE_SCROLL_INSENSITIVE: Esse ResultSet permite a iteração para trás e para frente, mas se os dados no banco de dados forem alterados, o ResultSet não a refletirá. Esse ResultSet rolável é provavelmente o tipo desejado mais comum.
  • ResultSet.TYPE_SCROLL_SENSITIVE: O ResultSet criado não só permitirá a iteração bidirecional, como também fornecerá uma visualização "ativa" dos dados no banco de dados quando forem alterados.

O segundo parâmetro é discutido na próxima dica, espere mais um pouco.

Rolagem direcional

Assim que você tiver obtido um ResultSet do Statement, rolar para trás nele é apenas uma questão de chamada do previous(), que retrocede uma linha em vez de avançar, como next() faria. Ou é possível chamar first() para retornar ao início do ResultSet, ou chamar last() para ir para o final do ResultSet, ou ... bem, você entendeu.

Os métodos relative() e absolute() também podem ser úteis: o primeiro move o número especificado de linhas (para frente se o valor for positivo e para trás se o valor for negativo), e o segundo move para a linha especificada no ResultSet independentemente da posição do cursor. É claro que o número da linha atual está disponível via getRow().

Se você planeja realizar muita rolagem em uma direção específica, pode ajudar oResultSet especificando essa direção, chamando setFetchDirection(). (Um ResultSet funcionará independentemente da direção da rolagem, mas um conhecimento antecipado permite otimizar a recuperação dos dados.)


3. ResultSets Atualizáveis

O JDBC não só suporta ResultSets, com também suporta atualizações do ResultSets no local. Isso significa que em vez de criar uma nova instrução SQL para alterar os valores armazenados atualmente no banco de dados, é possível simplesmente modificar o valor mantido no ResultSet, e ele será enviado automaticamente para o banco de dados para a coluna dessa linha.

A solicitação de um ResultSet é semelhante ao processo envolvido na solicitação de um ResultSet rolável. Na verdade, é onde você usará o segundo parâmetro para o createStatement(). Em vez de especificar ResultSet.CONCUR_READ_ONLY para o segundo parâmetro, envie ResultSet.CONCUR_UPDATEABLE, conforme mostrado na Listagem 5:

Listagem 5. Eu gostaria de um ResultSet atualizável
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE, 
                       ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");

Supondo que seu driver suporte cursores atualizáveis (esse é outro recurso da especificação JDBC 2.0, que a maioria dos bancos de dados do "mundo real" suportará), é possível atualizar qualquer valor em um ResultSet navegando nessa linha e chamando um dos métodos update...() nele (mostrado na Listagem 6). Como os métodos get...() em ResultSet, update...() é sobrecarregado para o tipo de coluna real no ResultSet. Portanto, altere a coluna de ponto flutuante chamada "PRICE", chame updateFloat("PRICE"). Entretanto, esse procedimento apenas atualiza o valor no ResultSet. Para enviar o valor para o banco de dados retrocedendo-o, chame updateRow(). Se o usuário mudar de ideia sobre a mudança no preço, uma chamada para cancelRowUpdates() eliminará todas as atualizações pendentes.

Listagem 6. Uma maneira melhor
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE, 
                       ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS = 
    stmt.executeQuery("SELECT * FROM lineitem WHERE id=1");
scrollingRS.first();
scrollingRS.udpateFloat("PRICE", 121.45f);
// ...
if (userSaidOK)
    scrollingRS.updateRow();
else
    scrollingRS.cancelRowUpdates();

O JDBC 2.0 suporta mais do que apenas atualizações. Se o usuário quiser incluir uma linha completamente nova, em vez de criar um novo Statement e executar um INSERT, simplesmente chame moveToInsertRow(), chame update...() para cada coluna e, em seguida, chame insertRow() para concluir o trabalho. Se um valor de coluna não for especificado, será assumido como um SQL NULL (que pode acionar um SQLException se o esquema do banco de dados não permitir NULLs para essa coluna).

Naturalmente, se ResultSet suportar a atualização em uma linha, ele deverá também suportar a exclusão de uma via deleteRow().

Ah, e antes que eu esqueça, toda essa capacidade de rolagem e de atualização se aplica igualmente a PreparedStatement (passando esses parâmetros para o método prepareStatement() ), que é infinitamente preferencial a um Statement devido ao perigo constante de ataques de injeção de SQL.


4. Rowsets

Se toda essa funcionalidade esteve no JDBC na melhor parte de uma década, por que a maioria dos desenvolvedores ainda está presa ao processo de rolar para frente os ResultSets e o acesso desconectado?

A escalabilidade é a principal culpada. Manter as conexões com o banco de dados em um nível mínimo é a chave para suportar o grande número de usuários que a Internet pode levar ao Web site de uma empresa. Como a rolagem e/ou a atualização de ResultSets geralmente requer uma conexão de rede aberta, muitos desenvolvedores não (ou não poderão) usá-los.

Felizmente o JDBC 3.0 introduziu uma alternativa que permite que você faça as mesmas coisas que faria com um ResultSet, sem necessariamente precisar manter aberta a conexão com o banco de dados.

No conceito, um Rowset é essencialmente um ResultSet, mas um que permite um modelo conectado ou desconectado. Tudo que você precisa é criar um Rowset, apontar para ele em um ResultSet, e quando ele concluir o próprio preenchimento, use-o como você usaria um ResultSet, mostrado na Listagem 7:

Listagem 7. Rowset substitui ResultSet
Statement stmt = con.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE, 
                       ResultSet.CONCUR_UPDATEABLE);
ResultSet scrollingRS = stmt.executeQuery("SELECT * FROM whatever");
if (wantsConnected)
    JdbcRowSet rs = new JdbcRowSet(scrollingRS); // connected
else
    CachedRowSet crs = new CachedRowSet(scrollingRS); disconnected

O JDBC é fornecido com cinco "implementações" (ou seja, interfaces estendidas) da interface de Rowset . JdbcRowSet é uma implementação de Rowset conectada. As quatro restantes são desconectadas:

  • CachedRowSet é apenas um Rowset desconectado .
  • WebRowSet é uma subclasse de CachedRowSet que sabe como transformar seus resultados em XML e retornar.
  • JoinRowSet é um WebRowSet que também sabe como formar o equivalente a um SQL JOIN sem ter que se conectar novamente ao banco de dados.
  • FilteredRowSet é um WebRowSet que também sabe como filtrar adicionalmente os dados retornados sem ter que se conectar novamente ao banco de dados.

Rowsets são JavaBeans integrais, ou seja, eles suportam eventos do tipo listener, portanto, qualquer modificação no Rowset pode ser capturada, examinada e influenciada, se desejado. Na verdade, Rowset pode até mesmo gerenciar a ação completa em relação ao banco de dados se ele tiver o conjunto de propriedades Username, Password, URLe DatasourceName (o que significa que ele criará uma conexão usando DriverManager.getConnection()) ou seu conjunto de propriedades Datasource (que provavelmente foi obtido por meio de JNDI). Você então especifica o SQL para executar na propriedade Command , chamar execute() e começar a trabalhar com os resultados — sem a necessidade de mais nenhuma ação.

As implementações de Rowset geralmente são fornecidas pelo driver JDBC de modo que o nome real e/ou o pacote dependa de o que o driver JDBC usará. As implementações de Rowset têm sido parte de uma distribuição padrão desde o Java 5, portanto, você deve ser capaz de criar o ...RowsetImpl() e seguir em frente. (No caso improvável de seu driver não fornecer uma, a Sun oferece uma implementação de referência. Consulte Recursos para obter o link.)


5. Atualizações de Lote

Apesar da utilidade, os Rowsets às vezes simplesmente não atendem às suas necessidades, e talvez seja necessário retornar para escrever diretamente as instruções SQL. Nessas situações, particularmente quando estiver realizando grande volume de trabalho, talvez você aprecie a capacidade de executar atualizações de lote, executando mais de uma instrução SQL em relação ao banco de dados como parte de roundtrip na rede.

Para determinar se o driver JDBC suporta atualizações de lote, uma chamada rápida para o DatabaseMetaData.supportsBatchUpdates() gera um operador booleano contendo as informações. Supondo-se que as atualizações de lote sejam suportadas (indicadas por qualquer não SELECT), enfileire uma e libere-a de uma vez, como na Listagem 8:

Listagem 8. Deixe o banco de dados executar a tarefa!
conn.setAutoCommit(false);

PreparedStatement pstmt = conn.prepareStatement("INSERT INTO lineitems VALUES(?,?,?,?)");
pstmt.setInt(1, 1);
pstmt.setString(2, "52919-49278");
pstmt.setFloat(3, 49.99);
pstmt.setBoolean(4, true);
pstmt.addBatch();

// rinse, lather, repeat

int[] updateCount = pstmt.executeBatch();
conn.commit();
conn.setAutoCommit(true);

A chamada para setAutoCommit() é necessária porque, por padrão, o driver tentará confirmar cada instrução que for alimentada. Além disso, o restante do código é bem claro: executar o SQL comum com Statement ou PreparedStatement, mas em vez de chamar execute(), chame executeBatch(), que coloca a chamada em fila em vez de enviá-la imediatamente.

Quando todas essas instruções estiverem prontas para serem executadas, ative todas no banco de dados com executeBatch(), que retorna um array de valores de número inteiro, cada um dos quais mantém o mesmo resultado como se executeUpdate() tivesse sido usado.

No caso de uma instrução no lote falhar, se o driver não suportar atualizações de lote, ou se uma instrução no lote retornar um ResultSet, o driver lançará um BatchUpdateException. Em alguns casos, o driver poderá tentar continuar a executar as instruções depois de uma exceção ter sido lançada. A especificação JDBC não determina um comportamento específico, portanto, você é aconselhado a testar com seu driver antecipadamente de modo que saiba exatamente como ele se comporta. (Mas você executará testes de unidade a fim de descobrir o erro bem antes de ele se tornar um problema, certo?)


Conclusão

Como uma base do desenvolvimento Java, a API JDBC é algo que todo desenvolvedor de Java deveria conhecer muito bem. O curioso é que a maioria dos desenvolvedores não acompanhou os aprimoramentos da API com o passar dos anos e não aprendeu os truques de economia de tempo descritos neste artigo.

A decisão de usar os recursos mais recentes de JDBC é sua, é claro. Um aspecto importante a ser considerado será a escalabilidade do sistema em que você está trabalhando. Quanto maior a necessidade de escalar, mais você será forçado a usar o banco de dados e, portanto, maior será a necessidade de reduzir o tráfego de rede em relação a ele. Rowsets, chamadas escalares e atualizações de lotes serão seus amigos. Caso contrário, tente os ResultSets roláveis e atualizáveis (que não consomem tanta memória quanto Rowsets) e meça a ocorrência de escalabilidade. É provável que não seja tão ruim quanto você espera.

A seguir, na série 5 coisas: sinalizadores de linha de comando.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

  • Participe da comunidade do My developerWorks. Conecte-se a outros usuários do developerWorks enquanto explora blogs, fóruns, grupos e wikis orientadas a desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=549482
ArticleTitle=Cinco coisas que você não sabia sobre ... Java Database Connectivity
publish-date=10282010