Um Guia para Jdbi
1. Introdução
Neste artigo, veremos como consultar um banco de dados relacional com jdbi.
O Jdbi é uma biblioteca Java de código aberto (licença Apache) que usa o link:/java-8-lambda-expression-tips [expressões lambda] e o link:/java-reflection [reflection] para fornecer uma interface mais amigável e de nível superior ao link:/java-jdbc [JDBC] para acessar o banco de dados.
*O Jdbi, no entanto, não é um ORM;* embora tenha um módulo opcional de mapeamento de objetos SQL, ele não possui uma sessão com objetos anexados, uma camada de independência de banco de dados e quaisquer outros sinos e assobios de um ORM típico.
*2. Configuração Jdbi *
O Jdbi está organizado em um núcleo e em vários módulos opcionais.
Para começar, basta incluir o módulo principal em nossas dependências:
<dependencies>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
No decorrer deste artigo, mostraremos exemplos usando o banco de dados HSQL:
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
Podemos encontrar a versão mais recente de _https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jdbi%22%20AND%20a%3A%22jdbi3-core%22 [jdbi3- core] _, HSQLDB e os outros módulos Jdbi] no Maven Central.
===* 3. Conectando ao banco de dados *
Primeiro, precisamos nos conectar ao banco de dados. Para fazer isso, precisamos especificar os parâmetros de conexão.
O ponto de partida é a classe Jdbi:
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
Aqui, especificamos o URL da conexão, um nome de usuário e, é claro, uma senha.
====* 3.1 Parâmetros adicionais *
Se precisarmos fornecer outros parâmetros, usamos um método sobrecarregado que aceita um objeto Properties:
Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);
Nesses exemplos, salvamos a instância Jdbi em uma variável local. Isso porque nós o usaremos para enviar instruções e consultas ao banco de dados.
De fato, apenas chamar create não estabelece nenhuma conexão com o banco de dados. Apenas salva os parâmetros de conexão para mais tarde.
====* 3.2 Usando um DataSource *
Se nos conectarmos ao banco de dados usando um DataSource, como geralmente é o caso, podemos usar a sobrecarga create apropriada:
Jdbi jdbi = Jdbi.create(datasource);
====* 3.3 Trabalhando com alças *
As conexões reais com o banco de dados são representadas por instâncias da classe Handle.
A maneira mais fácil de trabalhar com identificadores e fechá-los automaticamente é usando expressões lambda:
jdbi.useHandle(handle -> {
doStuffWith(handle);
});
Chamamos useHandle quando não precisamos retornar um valor.
Caso contrário, usamos withHandle:
jdbi.withHandle(handle -> {
return computeValue(handle);
});
Também é possível, embora não recomendado, abrir manualmente um identificador de conexão; nesse caso, temos que fechá-lo quando terminarmos:
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
doStuffWith(handle);
}
Felizmente, como podemos ver, Handle implementa Closeable, para que possa ser usado com o link:/java-try-with-resources [try-with-resources].
===* 4. Declarações simples *
Agora que sabemos como obter uma conexão, vamos ver como usá-la.
Nesta seção, criaremos uma tabela simples que usaremos ao longo do artigo.
Para enviar instruções como criar tabela para o banco de dados, usamos o método execute:
handle.execute(
"create table project "
+ "(id integer identity, name varchar(50), url varchar(100))");
execute retorna o número de linhas que foram afetadas pela instrução:
int updateCount = handle.execute(
"insert into project values "
+ "(1, 'tutorials', 'github.com/eugenp/tutorials')");
assertEquals(1, updateCount);
Na verdade, executar é apenas um método de conveniência.
Examinaremos casos de uso mais complexos nas seções posteriores, mas antes de fazer isso, precisamos aprender como extrair resultados do banco de dados.
===* 5. Consultando o banco de dados *
A expressão mais direta que produz resultados do banco de dados é uma consulta SQL.
Para emitir uma consulta com um identificador Jdbi, precisamos pelo menos:
-
crie a consulta
-
escolha como representar cada linha
-
iterar sobre os resultados
Vamos agora olhar para cada um dos pontos acima.
====* 5.1. Criando uma consulta *
Sem surpresa,* Jdbi representa consultas como instâncias da classe Query. *
Podemos obter um de um identificador:
Query query = handle.createQuery("select* from project");
5.2 Mapeando os resultados
O Jdbi abstrai do JDBC ResultSet, que possui uma API bastante complicada.
Portanto, oferece várias possibilidades para acessar as colunas resultantes de uma consulta ou de alguma outra instrução que retorne um resultado. Agora veremos os mais simples.
Podemos representar cada linha como um mapa:
query.mapToMap();
As chaves do mapa serão os nomes das colunas selecionadas.
Ou, quando uma consulta retorna uma única coluna, podemos mapeá-la para o tipo Java desejado:
handle.createQuery("select name from project").mapTo(String.class);
*O Jdbi possui mapeadores internos para muitas classes comuns.* Os que são específicos para alguma biblioteca ou sistema de banco de dados são fornecidos em módulos separados.
Obviamente, também podemos definir e registrar nossos mapeadores. Falaremos sobre isso em uma seção posterior.
Finalmente, podemos mapear linhas para um bean ou alguma outra classe personalizada. Mais uma vez, veremos as opções mais avançadas em uma seção dedicada.
5.3. Iterando sobre os resultados
Depois que decidimos como mapear os resultados chamando o método apropriado, recebemos um objeto ResultIterable.
Em seguida, podemos usá-lo para iterar sobre os resultados, uma linha de cada vez.
Aqui, veremos as opções mais comuns.
Podemos apenas acumular os resultados em uma lista:
List<Map<String, Object>> results = query.mapToMap().list();
Ou para outro tipo de Collection:
List<String> results = query.mapTo(String.class).collect(Collectors.toSet());
Ou podemos iterar sobre os resultados como um fluxo:
query.mapTo(String.class).useStream((Stream<String> stream) -> {
doStuffWith(stream)
});
Aqui, digitamos explicitamente a variável stream para maior clareza, mas não é necessário fazê-lo.
5.4 Obtendo um único resultado
Como um caso especial, quando esperamos ou estamos interessados em apenas uma linha, temos alguns métodos dedicados disponíveis.
Se queremos no máximo um resultado , podemos usar findFirst:
Optional<Map<String, Object>> first = query.mapToMap().findFirst();
Como podemos ver, ele retorna um valor Optional, que só está presente se a consulta retornar pelo menos um resultado.
Se a consulta retornar mais de uma linha, somente a primeira será retornada.
Se, em vez disso, queremos um e apenas um resultado , usamos findOnly:
Date onlyResult = query.mapTo(Date.class).findOnly();
Por fim, se houver zero resultados ou mais de um, findOnly lança uma IllegalStateException.
*6. Parâmetros de encadernação *
Frequentemente,* as consultas têm uma parte fixa e uma parte parametrizada. * Isso tem várias vantagens, incluindo:
-
segurança: evitando a concatenação de cadeias, impedimos a injeção de SQL
-
facilidade: não precisamos lembrar a sintaxe exata de tipos de dados complexos, como registros de data e hora *desempenho: a parte estática da consulta pode ser analisada uma vez e armazenada em cache
O Jdbi suporta parâmetros posicionais e nomeados.
Nós inserimos parâmetros posicionais como pontos de interrogação em uma consulta ou instrução:
Query positionalParamsQuery =
handle.createQuery("select* from project where name = ?");
Parâmetros nomeados, em vez disso, começam com dois pontos:
Query namedParamsQuery =
handle.createQuery("select *from project where url like :pattern");
Em qualquer um dos casos, para definir o valor de um parâmetro, usamos uma das variantes do método bind:
positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");
Observe que, diferentemente do JDBC, os índices começam em 0.
====* 6.1 Vinculando vários parâmetros nomeados de uma vez *
Também podemos ligar vários parâmetros nomeados usando um objeto.
Digamos que temos esta consulta simples:
Query query = handle.createQuery(
"select id from project where name = :name and url = :url");
Map<String, String> params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");
Então, por exemplo, podemos usar um mapa:
query.bindMap(params);
Ou podemos usar um objeto de várias maneiras. Aqui, por exemplo, ligamos um objeto que segue a convenção JavaBean:
query.bindBean(paramsBean);
Mas também poderíamos vincular os campos ou métodos de um objeto; para todas as opções suportadas, consulte documentação Jdbi.
===* 7. Emissão de declarações mais complexas *
Agora que vimos consultas, valores e parâmetros, podemos voltar às instruções e aplicar o mesmo conhecimento.
Lembre-se de que o método execute que vimos anteriormente é apenas um atalho útil.
De fato, da mesma forma que as consultas, as instruções* DDL e DML são representadas como instâncias da classe Update . *
Podemos obter um chamando o método createUpdate em um identificador:
Update update = handle.createUpdate(
"INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");
Em um Update, temos todos os métodos de ligação que temos em uma Query, portanto, na seção 6. também se aplica a atualizações.url
As instruções são executadas quando chamamos, surpresa, execute:
int rows = update.execute();
Como já vimos, ele retorna o número de linhas afetadas.
====* 7.1 Extraindo valores da coluna de incremento automático *
Como um caso especial, quando temos uma instrução de inserção com colunas geradas automaticamente (geralmente incremento automático ou sequências), podemos obter os valores gerados.
Então, não chamamos execute, mas executeAndReturnGeneratedKeys:
Update update = handle.createUpdate(
"INSERT INTO PROJECT (NAME, URL) "
+ "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();
*_ResultBearing_ é a mesma interface implementada pela classe _Query_* que vimos anteriormente, portanto já sabemos como usá-lo:
generatedKeys.mapToMap()
.findOnly().get("id");
*8. Transações *
*Precisamos de uma transação sempre que precisamos executar várias instruções como uma única operação atômica.*
Assim como nas alças de conexão, introduzimos uma transação chamando um método com um fechamento:
handle.useTransaction((Handle h) -> {
haveFunWith(h);
});
E, como nas alças, a transação é fechada automaticamente quando o fechamento retorna.
*No entanto, devemos confirmar ou reverter a transação* antes de retornar:
handle.useTransaction((Handle h) -> {
h.execute("...");
h.commit();
});
Se, no entanto, uma exceção for lançada, o Jdbi reverterá automaticamente a transação.
Assim como nas alças, temos um método dedicado, inTransaction, se quisermos retornar algo do fechamento:
handle.inTransaction((Handle h) -> {
h.execute("...");
h.commit();
return true;
});
8.1. Gerenciamento manual de transações *
Embora no caso geral não seja recomendado, também podemos begin e close uma transação manualmente:
handle.begin();
//...
handle.commit();
handle.close();
===* 9. Conclusões e leituras adicionais *
Neste tutorial, apresentamos o núcleo do Jdbi:* consultas, instruções e transações. *
Deixamos de lado alguns recursos avançados, como mapeamento personalizado de linhas e colunas e processamento em lote.
Também não discutimos nenhum dos módulos opcionais, principalmente a extensão SQL Object.
Tudo é apresentado em detalhes na Jdbi documentation.
A implementação de todos esses exemplos e trechos de código pode ser encontrada em o projeto GitHub - este é um projeto do Maven, por isso deve ser fácil de importar e executar como está.