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::forEach
dont 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 ResultSet
et 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-resources
aussi!):
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 AutoCloseableIterator
ou 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::reduce
ou 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 telMap<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.