JAVA

The Many Different Ways to Fetch Data in jOOQ – Java, SQL and jOOQ.

The jOOQ API is all about convenience, and as such, an important operation (the most important one?) like fetch() must come with convenience, too. The default way to fetch data is this:

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

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

It fetches the entire result set into memory and closes the underlying JDBC resources eagerly. But what other options do we have?

Iterable fetching

In the above example, the fetch() call wasn’t strictly necessary. jOOQ’s ResultQuery<R> type conveniently extends Iterable<R>, which means that a call to ResultQuery.iterator() will also execute the query. This can be done mainly in two ways:

External iteration:

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

This is particularly nice because it feels just like PL/SQL or PL/pgSQL’s FOR loop for implicit cursors:

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

This still has to fetch the entire result set into memory, though, because there isn’t a for-with-resources syntax in Java that combines the foreach syntax with a try-with-resources syntax.

Internal iteration:

The JDK 8 added Iterable::forEach, which jOOQ’s ResultQuery inherits, so you can do this just as well:

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

The two are perfectly equivalent.

Single record fetching

If you’re sure you’re going to fetch only a single value, no need to materialise a list. Just use one of the following methods. Given this query:

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

You can now:

Fetch a nullable record:

This fetches a nullable record, i.e. if the record hasn’t been found, null is produced. If there are more than one records, a TooManyRowsException is thrown.

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

Fetch an optional record:

The null bikeshed is real, so why keep you from bikeshedding also when working with jOOQ? Exactly equivalent to the above, but using a different style, is this:

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

Fetch a single record:

If you know your query produces exactly one record, there’s the term “single” in jOOQ’s API which means exactly one:

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

The r.toString() call is NullPointerException safe, because if the record didn’t exist a NoDataFoundException would have been thrown.

Resourceful fetching

The default is to eagerly fetch everything into memory, as that is likely more useful to most applications than JDBC’s default of managing resources all the time (including nested collections, lobs, etc.). As could be seen in the above Iterator fetching example, it is often the only possible approach that doesn’t produce accidental resource leaks, given that users can’t even access the resource (by default) via jOOQ.

But it isn’t always the right choice, so you can alternatively keep open underlying JDBC resources while fetching data, if your data set is large. There are 2 main ways:

Imperative:

By calling ResultQuery.fetchLazy(), you’re creating a Cursor<R>, which wraps the underlying JDBC ResultSet, and thus, should be contained in a try-with-resources statement:

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

The Cursor<R> still extends Iterable<R>, but you can fetch records also manually from it, e.g.

Record record;

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

Functional:

If the Stream API is more like you want to work with data, just call ResultQuery.fetchStream() instead, then (but don’t forget to wrap that in try-with-resources, too!):

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

Or, use Stream::map, Stream::reduce, or whatever. Regrettably, the Stream API isn’t auto-closing. While it would have been possible to implement the API this way, its “escape hatches,” like Stream.iterator() would still prevent auto-closing behaviour (at least unless many more features had been introduced, such as e.g. an AutoCloseableIterator, or whatever).

So, you’ll have to break your fluent pipeline with the try-with-resources statement.

Functional, but not resourceful

Of course, you can always call fetch() first, then stream later, in order to stream the data from your memory directly. If resourcefulness isn’t important (i.e. the performance impact is negligible because the result set isn’t big), you can write this:

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

Or use Stream::map, Stream::reduce, or whatever

Collector fetching

Starting with jOOQ 3.11, both ResultQuery::collect and Cursor::collect had been added. The JDK Collector API is extremely poweful. It doesn’t get the attention it deserves (outside of the Stream API). In my opinion, there should be an Iterable::collect method, as it would make sense to re-use Collector types on any collection, e.g.

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

Why not? Collector is kind of a dual to the Stream API itself. The operations aren’t composed in a pipelined syntax, but in a nested syntax. Other than that, to me at least, it feels quite similar.

In case of jOOQ, they’re very powerful. jOOQ offers a few useful out-of-the-box collectors in Records. Let me showcase Records.intoMap(), which has this overload, for example:

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

The interesting bit here is that it captures the types of a Record2 type as the key and value type of the resulting map. A simple generic trick to make sure it works only if you project exactly 2 columns, for example:

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

This is completely type safe. You can’t project 3 columns, or the wrong column types thanks to all those generics. This is more convenient than the equivalent that is available on the ResultQuery API directly, where you have to repeat the projected column expressions:

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

With the ResultQuery::collect and Cursor::collect APIs, you can use any arbitrary collector, including your own, which is really very powerful! Also, it removes the need for the intermediary Result data structure, so it doesn’t have to fetch everything into memory (unless your Collector does it anyway, of course).

Collectors are particularly useful when collecting MULTISET nested collections. An example has been given here, where a nested collection was also mapped into such a Map<K, V>.

Reactive fetching

Starting from jOOQ 3.15, R2DBC has been supported. This means that ResultQuery<R> is now also a reactive streams Publisher<R> (both the reactive-streams API and the JDK 9 Flow API are supported for better interoperability).

So, just pick your favourite reactive streams API of choice, e.g. reactor, and stream jOOQ result sets reactively like this:

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

Many fetching

Last but not least, there are rare cases when your query produces more than one result set. This used to be quite en vogue in SQL Server and related RDBMS, where stored procedures could produce cursors. MySQL and Oracle also have the feature. For example:

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

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

The standard foreach loop will only iterate results, but you can also access the interleaved row counts using Results.resultsOrRows() if that is of interest to you as well.

Conclusion

Convenience and developer user experience is at the core of jOOQ’s API design. Like any good collection API, jOOQ offers a variety of composable primitives that allow for more effectively integrating SQL into your application.

SQL is just a description of a data structure. jOOQ helps describe that data structure in a type safe way on the JVM. It is natural for further processing to be possible in an equally type safe way, as we’re used to from the JDK’s own collection APIs, or third parties like jOOλ, vavr, streamex, etc.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button