Check yo except'

Java 8 goodie: SQL ResultSet Streams

Lukas Eder
8-shape.1

It’s SQL time again, as Lukas Eder turns his attention to one very important aspect of Java 8 lambdas and interoperability with “legacy” APIs.

This post was originally published over at jooq.org as part of a special series
focusing on all things Java 8, including how take advantage of
lambda expressions, extension methods, and other great
stuff. You’ll find the
source code on GitHub
.

Checked Exceptions

Yes. Unfortunately, those beasts from the past still haunt us,
more than ever when we’re using Java 8′s lambda expressions.
Already before Java 8′s release, there are a couple of Stack
Overflow questions related to the subject.


Let’s remember how the IOExceptions caused issues when traversing
the file system
. Unless you write your own utility, you’ll have
to resort to this beauty:

Arrays.stream(dir.listFiles()).forEach(file -> {
    try {
        System.out.println(file.getCanonicalPath());
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
 
    // Ouch, my fingers hurt! All this typing!
});

We think it is safe to say:

Java 8 and checked exceptions don’t match.

A workaround is to write your
own CheckedConsumer that wraps the checked
exception. Such a consumer will be highly reusable, but… Did you
think of all the other FunctionalInterfaces?
There are quite a few of them in
the java.util.function package:

jOOλ – Fixing lambda in Java 8 

While writing this Java 8 blog series,
we’ve constantly run into the need to wrap checked exceptions
inside lambda expressions. And what do we geeks do when we
frequently run into a problem? We fix it! And we have
created jOOλ
(also jOOL, jOO-Lambda)
ASL 2.0 licensed,
where we have duplicated pretty much
every FunctionalInterface that is available
from the JDK to support checked exceptions. Here’s how you would
use jOOλ in the above example:

Arrays.stream(dir.listFiles()).forEach(
    Unchecked.consumer(file -> {
        // Throw all sorts of checked exceptions
        // here, we don't care...
        System.out.println(file.getCanonicalPath());
    })
);

The above example shows how you can simply ignore and pass
checked exceptions as RuntimeExceptions. If you actually want to
handle them, you can pass an exception handler lambda:

Arrays.stream(dir.listFiles())
      .forEach(Unchecked.consumer(
 
    file -> {
        System.out.println(file.getCanonicalPath());
    },
    e -> {
        log.info("Log stuff here", e);
        throw new MyRuntimeException(e);
    }
);

The second example now seems equally verbose, but don’t worry.
You will probably reuse that exception handler and fall back to
this:

Arrays.stream(dir.listFiles())
      .forEach(Unchecked.consumer(
    file -> {
        System.out.println(file.getCanonicalPath());
    },
    myExceptionHandler
);

jOOλ – Providing JDBC ResultSet Streams

Unfortunately, most efforts in the Java 8 Streams API were made
in the area of correctly implementing parallelisable streams. While
this is very useful for those of us actually doing parallel
computing, for most others better integration with legacy APIs
would have been better. One API that seriously deserves some
lifting is JDBC,
and we’ve blogged about this before
. With jOOλ, you can now generate
Streams directly fromResultSets or
even from PreparedStatements.
Here’s how you prepare:

Class.forName("org.h2.Driver");
try (Connection c = getConnection()) {
    String sql = "select schema_name, is_default " +
                 "from information_schema.schemata " +
                 "order by schema_name";
 
    try (PreparedStatement stmt = c.prepareStatement(sql)) {
        // code here
    }
}

Now, all you have to do when using jOOλ is stream
your PreparedStatements as
such:

SQL.stream(stmt, Unchecked.function(rs ->
    new SQLGoodies.Schema(
        rs.getString("SCHEMA_NAME"),
        rs.getBoolean("IS_DEFAULT")
    )
))
.forEach(System.out::println);

Where SQLGoodies.Schema is just an
ordinary POJO. Some of thestream() method’s
signatures are these ones:

public static <T> Stream<T> stream(
    PreparedStatement stmt,
    Function<ResultSet, T> rowFunction
);
 
public static <T> Stream<T> stream(
    PreparedStatement stmt,
    Function<ResultSet, T> rowFunction,
    Consumer<? super SQLException> exceptionHandler
);

Others are available as well.

That is awesome, isn’t it?

JDBC ResultSets should be Java 8 Streams.

Too bad, the above code didn’t make it into the JDK 8, as this
would have been a chance to finally greatly improve on the JDBC
API. Another,
similar attempt at improving things has been done here by Julian
Exenberger.

Java 8 alternatives of writing SQL

We’ve also published a couple of alternatives to jOOλ, using
Java 8 with SQL here:http://www.jooq.org/java-8-and-sql

Conclusion

While Java 8′s lambda expressions are awesome, the new Streams
API is pretty incomplete. When implementing the above, we had to
implement our own ResultSetIterator, and write
all this mess to wrap the iterator in aStream:

StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        new ResultSetIterator<>(
            supplier,
            rowFunction,
            exceptionTranslator
        ), 0
    ), false
);

And it shouldn’t be necessary to write
an Iterator in the first place, if
only we
were able to generate finite streams
:

// Unfortunately, this method doesn't exist
Stream.generate(
    // Supplier, generating new POJOs
    () -> {
        rs.next();
        return new SQLGoodies.Schema(
            rs.getString("SCHEMA_NAME"),
            rs.getBoolean("IS_DEFAULT")
        );
    },
 
    // Predicate, terminating the Stream
    () -> { !rs.isLast(); }
);

While jOOλ is an acceptable
intermediate solution, and the Guava
guys
 are probably already working out how to fix their
library, it is really too bad, that Java 8 is lacking such utility
functionality.

 


Author
Lukas Eder
Lukas is a Java and SQL aficionado. He’s the founder and head of R&D at Data Geekery GmbH, the company behind jOOQ, the best way to write SQL in Java.
Comments
comments powered by Disqus