X

Les nombreuses façons différentes de récupérer des données dans jOOQ – Java, SQL et jOOQ.


L’API jOOQ est une question de commodité, et en tant que telle, une opération importante (la plus importante ?) comme fetch() doit venir avec commodité, aussi. La méthode par défaut pour récupérer les données est la suivante :

Result<Record1<String>> result =
ctx.select(BOOK.TITLE)
   .from(BOOK)
   .fetch();

for (Record1<String> record : result) {
    // ...
}

Il récupère l’ensemble des résultats dans la mémoire et ferme les ressources JDBC sous-jacentes avec impatience. Mais quelles autres options avons-nous ?

Extraction itérable

Dans l’exemple ci-dessus, le fetch() l’appel n’était pas strictement nécessaire. jOOQ ResultQuery<R> le type s’étend commodément Iterable<R>ce qui signifie qu’un appel à ResultQuery.iterator() exécutera également la requête. Cela peut se faire principalement de deux manières :

Itération externe :

for (Record1<String> record : ctx
    .select(BOOK.TITLE)
    .from(BOOK)
) {
    // ...
}

C’est particulièrement agréable car cela ressemble à PL/SQL ou PL/pgSQL FOR boucle pour les curseurs implicites :

FOR rec IN (SELECT book.title FROM book) LOOP
  -- ...
END LOOP;

Cela doit toujours récupérer l’ensemble des résultats en mémoire, car il n’y a pas de for-with-resources syntaxe en Java qui combine la foreach syntaxe avec un try-with-resources syntaxe.

Itération interne :

Le JDK 8 ajouté Iterable::forEachdont jOOQ ResultQuery hérite, vous pouvez donc le faire aussi bien :

ctx.select(BOOK.TITLE)
   .from(BOOK)
   .forEach(record -> {
       // ...
   });

Les deux sont parfaitement équivalents.

Récupération d’un seul enregistrement

Si vous êtes sûr de ne récupérer qu’une seule valeur, inutile de matérialiser une liste. Utilisez simplement l’une des méthodes suivantes. Étant donné cette requête :

ResultQuery<Record1<String>> query = ctx
    .select(BOOK.TITLE)
    .from(BOOK)
    .where(BOOK.ID.eq(1));

Tu peux maintenant:

Récupérez un enregistrement nullable :

Cela récupère un enregistrement nullable, c’est-à-dire si l’enregistrement n’a pas été trouvé, null est produit. S’il y a plus d’un enregistrement, un TooManyRowsException Est lancé.

Record1<String> r = query.fetchOne();

Récupérer un enregistrement facultatif :

Le null le bikeshed est réel, alors pourquoi vous empêcher de faire du bikeshed également lorsque vous travaillez avec jOOQ ? Exactement équivalent à ce qui précède, mais en utilisant un style différent, est-ce:

Optional<Record1<String>> r = query.fetchOptional();

Récupérer un seul enregistrement :

Si vous savez que votre requête produit exactement un enregistrement, il y a le terme “single” dans l’API de jOOQ qui signifie exactement un :

Record1<String> r = query.fetchSingle();
println(r.toString()); // NPE safe!

Le r.toString() l’appel est NullPointerException sûr, parce que si l’enregistrement n’existait pas un NoDataFoundException aurait été jeté.

Récupération ingénieuse

La valeur par défaut consiste à tout récupérer avec impatience dans la mémoire, car cela est probablement plus utile pour la plupart des applications que la gestion par défaut de JDBC des ressources en permanence (y compris les collections imbriquées, les lobs, etc.). Comme on a pu le voir ci-dessus Iterator Par exemple, c’est souvent la seule approche possible qui ne produit pas de fuites de ressources accidentelles, étant donné que les utilisateurs ne peuvent même pas accéder à la ressource (par défaut) via jOOQ.

Mais ce n’est pas toujours le bon choix, de sorte que vous pouvez également garder ouvertes les ressources JDBC sous-jacentes lors de la récupération des données, si votre ensemble de données est volumineux. Il existe 2 manières principales :

Impératif:

En appelant ResultQuery.fetchLazy()vous créez un Cursor<R>qui encapsule le JDBC sous-jacent ResultSetet donc, doit être contenu dans un try-with-resources déclaration:

try (Cursor<Record1<String>> cursor = ctx
    .select(BOOK.TITLE)
    .from(BOOK)
    .fetchLazy()
) {
    for (Record1<String> record : cursor) {
        // ...
    }
}

Le Cursor<R> s’étend encore Iterable<R>mais vous pouvez également y extraire manuellement des enregistrements, par exemple

Record record;

while ((record = cursor.fetchNext()) != null) {
    // ...
}

Fonctionnel:

Si la Stream L’API est plus comme si vous vouliez travailler avec des données, appelez simplement ResultQuery.fetchStream() à la place, alors (mais n’oubliez pas d’envelopper cela dans try-with-resourcesaussi!):

try (Stream<Record1<String>> stream = ctx
    .select(BOOK.TITLE)
    .from(BOOK)
    .fetchStream()
) {
    stream.forEach(record -> {
        // ...
    });
}

Ou utiliser Stream::map, Stream::reduce, ou peu importe. Malheureusement, le Stream L’API ne se ferme pas automatiquement. Bien qu’il aurait été possible d’implémenter l’API de cette manière, ses “trappes d’évacuation”, comme Stream.iterator() empêcherait toujours le comportement de fermeture automatique (du moins à moins que de nombreuses autres fonctionnalités n’aient été introduites, comme par exemple un AutoCloseableIteratorou peu importe).

Donc, vous devrez casser votre pipeline fluent avec le try-with-resources déclaration.

Fonctionnel, mais pas ingénieux

Bien sûr, vous pouvez toujours appeler fetch() d’abord, puis diffusez plus tard, afin de diffuser directement les données de votre mémoire. Si l’ingéniosité n’est pas importante (c’est-à-dire que l’impact sur les performances est négligeable car l’ensemble de résultats n’est pas volumineux), vous pouvez écrire ceci :

ctx.select(BOOK.TITLE)
   .from(BOOK)
   .fetch()
   .stream()
   .forEach(record -> {
       // ...
   });

Ou utiliser Stream::map, Stream::reduceou peu importe

Récupération du collecteur

À partir de jOOQ 3.11, les deux ResultQuery::collect et Cursor::collect avait été ajouté. Le JDK Collector L’API est extrêmement puissante. Il ne reçoit pas l’attention qu’il mérite (en dehors du Stream API). À mon avis, il devrait y avoir un Iterable::collect méthode, car il serait logique de réutiliser Collector types sur n’importe quelle collection, par exemple

Set<String> s = Set.of(1, 2, 3);
List<String> l = s.collect(Collectors.toList());

Pourquoi pas? Collector est une sorte de duel à la Stream API elle-même. Les opérations ne sont pas composées dans une syntaxe en pipeline, mais dans une syntaxe imbriquée. A part ça, pour moi du moins, c’est assez similaire.

En cas de jOOQ, ils sont très puissants. jOOQ propose quelques collecteurs prêts à l’emploi utiles dans Records. Permettez-moi de mettre en valeur Records.intoMap()qui a cette surcharge, par exemple :

<K,V,R extends Record2<K,V>> Collector<R,?,Map<K,V>> intoMap()

Ce qui est intéressant ici, c’est qu’il capture les types d’un Record2 type comme clé et type de valeur de la carte résultante. Une astuce générique simple pour s’assurer que cela ne fonctionne que si vous projetez exactement 2 colonnes, par exemple :

Map<Integer, String> books =
ctx.select(BOOK.ID, BOOK.TITLE)
   .from(BOOK)
   .collect(Records.intoMap());

Ceci est complètement sûr. Vous ne pouvez pas projeter 3 colonnes, ou les mauvais types de colonnes grâce à tous ces génériques. C’est plus pratique que l’équivalent disponible sur le ResultQuery API directement, où vous devez répéter les expressions de colonne projetées :

Map<Integer, String> books =
ctx.select(BOOK.ID, BOOK.TITLE)
   .from(BOOK)
   .fetchMap(BOOK.ID, BOOK.TITLE);

Avec le ResultQuery::collect et Cursor::collect APIs, vous pouvez utiliser n’importe quel collecteur arbitraire, y compris le vôtre, qui est vraiment très puissant ! En outre, cela supprime le besoin de l’intermédiaire Result structure de données, de sorte qu’il n’a pas à tout récupérer en mémoire (à moins que votre Collector le fait quand même, bien sûr).

Les collecteurs sont particulièrement utiles lors de la collecte MULTISET collections imbriquées. Un exemple a été donné ici, où une collection imbriquée a également été mappée dans un tel Map<K, V>.

Récupération réactive

À partir de jOOQ 3.15, R2DBC a été pris en charge. Cela signifie que ResultQuery<R> est maintenant aussi un flux réactif Publisher<R> (à la fois les reactive-streams API et JDK 9 Flow API sont pris en charge pour une meilleure interopérabilité).

Alors, choisissez simplement votre API de flux réactifs préférée, par exemple réacteur, et diffusez les ensembles de résultats jOOQ de manière réactive comme ceci :

Flux<Record1<String>> flux = Flux.from(ctx
    .select(BOOK.TITLE)
    .from(BOOK)
);

Beaucoup de récupération

Enfin et surtout, il existe de rares cas où votre requête produit plus d’un ensemble de résultats. Cela était assez en vogue dans SQL Server et les SGBDR associés, où les procédures stockées pouvaient produire des curseurs. MySQL et Oracle ont également la fonctionnalité. Par exemple:

Results results = ctx.fetch("sp_help");

for (Result<?> result : results) {
    for (Record record : result) {
        // ...
    }
}

Le standard foreach boucle ne fera qu’itérer les résultats, mais vous pouvez également accéder au nombre de lignes entrelacées à l’aide Results.resultsOrRows() si cela vous intéresse aussi.

Conclusion

La commodité et l’expérience utilisateur des développeurs sont au cœur de la conception de l’API de jOOQ. Comme toute bonne API de collection, jOOQ propose une variété de primitives composables qui permettent d’intégrer plus efficacement SQL dans votre application.

SQL n’est qu’une description d’une structure de données. jOOQ aide à décrire cette structure de données de manière sécurisée sur la JVM. Il est naturel que le traitement ultérieur soit possible de manière tout aussi sûre, comme nous en avons l’habitude à partir des propres API de collection du JDK, ou de tiers comme jOOλ, vavr, streamex, etc.