Un guide pour Jdbi

Un guide pour Jdbi

1. introduction

Dans cet article, nous allons voir comment interroger une base de données relationnelle avecjdbi.

Jdbi est une bibliothèque Java open source (licence Apache) qui utiliselambda expressions etreflection pour fournir une interface plus conviviale et de plus haut niveau queJDBC pour accéder à la base de données.

Jdbi, however, isn’t an ORM; même s'il dispose d'un module de mappage d'objets SQL optionnel, il n'a pas de session avec des objets attachés, une couche d'indépendance de la base de données et tout autre cloche et sifflet d'un ORM typique.

2. Configuration Jdbi

Jdbi est organisé en un noyau et plusieurs modules optionnels.

Pour commencer, il suffit d'inclure le module principal dans nos dépendances:


    
        org.jdbi
        jdbi3-core
        3.1.0
    

Au cours de cet article, nous montrerons des exemples utilisant la base de données HSQL:


    org.hsqldb
    hsqldb
    2.4.0
    test

Nous pouvons trouver la dernière version dejdbi3-core,HSQLDB et les autres modules Jdbi sur Maven Central.

3. Connexion à la base de données

Tout d'abord, nous devons nous connecter à la base de données. Pour ce faire, nous devons spécifier les paramètres de connexion.

Le point de départ est la classeJdbi:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");

Ici, nous spécifions l'URL de connexion, un nom d'utilisateur et, bien sûr, un mot de passe.

3.1. Paramètres supplémentaires

Si nous devons fournir d'autres paramètres, nous utilisons une méthode surchargée acceptant un objetProperties:

Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);

Dans ces exemples, nous avons enregistré l'instanceJdbi dans une variable locale. En effet, nous l’utiliserons pour envoyer des déclarations et des requêtes à la base de données.

En fait, le simple fait d’appelercreate n’établit aucune connexion avec la base de données. Il enregistre simplement les paramètres de connexion pour plus tard.

3.2. Utilisation d'unDataSource

Si nous nous connectons à la base de données en utilisant unDataSource, comme c'est généralement le cas, nous pouvons utiliser la surchargecreate appropriée:

Jdbi jdbi = Jdbi.create(datasource);

3.3. Travailler avec des poignées

Les connexions réelles à la base de données sont représentées par des instances de la classeHandle.

Le moyen le plus simple de travailler avec des poignées et de les fermer automatiquement consiste à utiliser des expressions lambda:

jdbi.useHandle(handle -> {
    doStuffWith(handle);
});

Nous appelonsuseHandle lorsque nous n’avons pas à renvoyer de valeur.

Sinon, nous utilisonswithHandle:

jdbi.withHandle(handle -> {
    return computeValue(handle);
});

Il est également possible, mais non recommandé, d'ouvrir manuellement une poignée de connexion; dans ce cas, nous devons le fermer lorsque nous avons terminé:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
    doStuffWith(handle);
}

Heureusement, comme nous pouvons le voir,Handle implémenteCloseable, donc il peut être utilisé avectry-with-resources.

4. Déclarations simples

Maintenant que nous savons comment obtenir une connexion, voyons comment l'utiliser.

Dans cette section, nous allons créer un tableau simple que nous utiliserons tout au long de l'article.

Pour envoyer des instructions telles quecreate table à la base de données, nous utilisons la méthodeexecute:

handle.execute(
  "create table project "
  + "(id integer identity, name varchar(50), url varchar(100))");

execute renvoie le nombre de lignes affectées par l'instruction:

int updateCount = handle.execute(
  "insert into project values "
  + "(1, 'tutorials', 'github.com/eugenp/tutorials')");

assertEquals(1, updateCount);

En fait, exécuter n’est qu’une méthode pratique.

Nous examinerons des cas d'utilisation plus complexes dans les sections suivantes, mais avant cela, nous devons apprendre à extraire les résultats de la base de données.

5. Interroger la base de données

L'expression la plus simple qui produit des résultats à partir de la base de données est une requête SQL.

Pour émettre une requête avec un Jdbi Handle, il faut au moins:

  1. créer la requête

  2. choisir comment représenter chaque ligne

  3. parcourir les résultats

Nous allons maintenant examiner chacun des points ci-dessus.

5.1. Créer une requête

Sans surprise,Jdbi represents queries as instances of the Query class.

Nous pouvons en obtenir un d'un manche:

Query query = handle.createQuery("select * from project");

5.2. Cartographie des résultats

Jdbi fait abstraction des JDBCResultSet, qui ont une API assez lourde.

Par conséquent, il offre plusieurs possibilités pour accéder aux colonnes résultant d'une requête ou d'une autre instruction renvoyant un résultat. Nous allons maintenant voir les plus simples.

Nous pouvons représenter chaque ligne comme une carte:

query.mapToMap();

Les clés de la carte seront les noms de colonne sélectionnés.

Ou, lorsqu'une requête renvoie une seule colonne, nous pouvons la mapper sur le type Java souhaité:

handle.createQuery("select name from project").mapTo(String.class);

Jdbi has built-in mappers for many common classes. Ceux qui sont spécifiques à une bibliothèque ou à un système de base de données sont fournis dans des modules séparés.

Bien entendu, nous pouvons également définir et enregistrer nos mappeurs. Nous en parlerons dans une section ultérieure.

Enfin, nous pouvons mapper des lignes sur un bean ou une autre classe personnalisée. Encore une fois, nous verrons les options les plus avancées dans une section dédiée.

5.3. Itérer les résultats

Une fois que nous avons décidé comment mapper les résultats en appelant la méthode appropriée,we receive a ResultIterable object.

Nous pouvons ensuite l'utiliser pour parcourir les résultats, une ligne à la fois.

Nous examinerons ici les options les plus courantes.

Nous pouvons simplement accumuler les résultats dans une liste:

List> results = query.mapToMap().list();

Ou vers un autre typeCollection:

List results = query.mapTo(String.class).collect(Collectors.toSet());

Ou nous pouvons parcourir les résultats sous forme de flux:

query.mapTo(String.class).useStream((Stream stream) -> {
    doStuffWith(stream)
});

Ici, nous avons explicitement tapé la variablestream pour plus de clarté, mais ce n’est pas nécessaire de le faire.

5.4. Obtenir un seul résultat

Comme cas particulier, lorsque nous attendons ou sommes intéressés par une seule ligne, nous disposons de deux méthodes dédiées.

Si nous voulonsat most one result, nous pouvons utiliserfindFirst:

Optional> first = query.mapToMap().findFirst();

Comme nous pouvons le voir, il renvoie une valeurOptional, qui n'est présente que si la requête renvoie au moins un résultat.

Si la requête renvoie plusieurs lignes, seule la première est renvoyée.

Si à la place, nous voulonsone and only one result, nous utilisonsfindOnly:

Date onlyResult = query.mapTo(Date.class).findOnly();

Enfin, s'il n'y a aucun résultat ou plus d'un,findOnly lance unIllegalStateException.

6. Paramètres de liaison

Souvent,queries have a fixed portion and a parameterized portion. Cela présente plusieurs avantages, notamment:

  • sécurité: en évitant la concaténation de chaînes, nous empêchons l'injection SQL

  • facilité: nous n'avons pas à nous souvenir de la syntaxe exacte des types de données complexes tels que les horodatages

  • performance: la partie statique de la requête peut être analysée une fois et mise en cache

Jdbi prend en charge les paramètres de position et les paramètres nommés.

Nous insérons des paramètres de position sous forme de points d'interrogation dans une requête ou une instruction:

Query positionalParamsQuery =
  handle.createQuery("select * from project where name = ?");

Les paramètres nommés commencent à la place par deux points:

Query namedParamsQuery =
  handle.createQuery("select * from project where url like :pattern");

Dans les deux cas, pour définir la valeur d'un paramètre, nous utilisons l'une des variantes de la méthodebind:

positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");

Notez que, contrairement à JDBC, les index commencent à 0.

6.1. Lier plusieurs paramètres nommés à la fois

Nous pouvons également lier plusieurs paramètres nommés ensemble à l'aide d'un objet.

Disons que nous avons cette requête simple:

Query query = handle.createQuery(
  "select id from project where name = :name and url = :url");
Map params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");

Ensuite, par exemple, nous pouvons utiliser une carte:

query.bindMap(params);

Ou nous pouvons utiliser un objet de différentes manières. Ici, par exemple, nous lions un objet qui suit la convention JavaBean:

query.bindBean(paramsBean);

Mais nous pouvons aussi lier les champs ou les méthodes d’un objet; pour toutes les options prises en charge, voirthe Jdbi documentation.

7. Émettre des relevés plus complexes

Maintenant que nous avons vu les requêtes, les valeurs et les paramètres, nous pouvons revenir aux déclarations et appliquer les mêmes connaissances.

Rappelez-vous que la méthodeexecute que nous avons vue précédemment n'est qu'un raccourci pratique.

En fait, comme pour les requêtes,DDL and DML statements are represented as instances of the class Update.

On peut en obtenir un en appelant la méthodecreateUpdate sur un handle:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");

Ensuite, sur unUpdate, nous avons toutes les méthodes de liaison que nous avons dans unQuery, donc la section 6. s'applique également aux mises à jour.url

Les instructions sont exécutées lorsque nous appelons, surprenons,execute:

int rows = update.execute();

Comme nous l’avons déjà vu, il renvoie le nombre de lignes affectées.

7.1. Extraction de valeurs de colonne à incrémentation automatique

Comme cas particulier, lorsque nous avons une instruction insert avec des colonnes générées automatiquement (généralement des incrémentations automatiques ou des séquences), nous souhaitons peut-être obtenir les valeurs générées.

Ensuite, nous n'appelons pasexecute, maisexecuteAndReturnGeneratedKeys:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) "
  + "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();

ResultBearing is the same interface implemented by the Query class que nous avons vu précédemment, donc nous savons déjà comment l'utiliser:

generatedKeys.mapToMap()
  .findOnly().get("id");

8. Transactions

Nous avons besoin d'une transaction chaque fois que nous devons exécuter plusieurs instructions en une seule opération atomique.

Comme pour les descripteurs de connexion, nous introduisons une transaction en appelant une méthode avec une fermeture:

handle.useTransaction((Handle h) -> {
    haveFunWith(h);
});

Et, comme pour les descripteurs, la transaction est automatiquement clôturée lorsque la clôture est renvoyée.

However, we must commit or rollback the transaction avant de retourner:

handle.useTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
});

Si, toutefois, une exception est levée à la clôture, Jdbi annule automatiquement la transaction.

Comme pour les handles, nous avons une méthode dédiée,inTransaction, si nous voulons retourner quelque chose de la fermeture:

handle.inTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
    return true;
});

8.1. Gestion manuelle des transactions

Bien que cela ne soit pas recommandé dans le cas général, nous pouvons égalementbegin etclose une transaction manuellement:

handle.begin();
// ...
handle.commit();
handle.close();

9. Conclusions et lectures complémentaires

Dans ce tutoriel, nous avons présenté le cœur de Jdbi:queries, statements, and transactions.

Nous avons omis certaines fonctionnalités avancées, telles que le mappage de lignes et de colonnes personnalisé et le traitement par lots.

Nous n'avons également abordé aucun des modules optionnels, notamment l'extension SQL Object.

Tout est présenté en détail dans lesJdbi documentation.

L'implémentation de tous ces exemples et extraits de code se trouve dansthe GitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.