Putting 'fun' into the future

Java 8: More functional relational transformation

Lukas Eder
dance

Lukas Eder reiterates why ORM is out, and why it’s time to ditch the dogma and embrace a new functional programming approach.

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
.

In this last, short post of the Java 8 Friday series, we’d like
to re-iterate the fact that we believe that the future belongs
to functional
relational data transformation (as opposed to ORM)
. We’ve spent
about 20 years now using the object-oriented
software development paradigm
. Many of us have been very
dogmatic about it. In the last 10 years, however, a “new” paradigm
has started to get increasing traction in programming
communities: Functional
programming
.

Functional programming is not that new,
however. Lisp has been a very early functional programming
language. XSLT and SQL are also somewhat functional (and
declarative!). As we’re big fans of SQL’s functional (and
declarative!) nature, we’re quite excited about the fact that we
now have sophisticated tools in Java to transform tabular data that
has been extracted from SQL databases. Streams!

SQL ResultSets are very similar to Streams

As we’ve pointed out before, JDBC
ResultSets and Java 8 Streams are quite similar
. This is even
more true when you’re using jOOQ, which replaces the JDBC ResultSet
by an org.jooq.Result, which
extendsjava.util.List, and thus automatically inherits
all Streams functionality. Consider the following query that allows
fetching a one-to-many relationship between BOOK and AUTHOR
records:

Map<Record2<String, String>, 
    List<Record2<Integer, String>>> booksByAuthor =
 
// This work is performed in the database
// --------------------------------------
ctx.select(
        BOOK.ID,
        BOOK.TITLE,
        AUTHOR.FIRST_NAME,
        AUTHOR.LAST_NAME
    )
   .from(BOOK)
   .join(AUTHOR)
   .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
   .orderBy(BOOK.ID)
   .fetch()
 
// This work is performed in Java memory
// -------------------------------------
   .stream()
 
   // Group BOOKs by AUTHOR
   .collect(groupingBy(
 
        // This is the grouping key      
        r -> r.into(AUTHOR.FIRST_NAME, 
                    AUTHOR.LAST_NAME),
 
        // This is the target data structure
        LinkedHashMap::new,
 
        // This is the value to be produced for each
        // group: A list of BOOK
        mapping(
            r -> r.into(BOOK.ID, BOOK.TITLE),
            toList()
        )
    ));

The fluency of the Java 8 Streams API is very idiomatic to
someone who has been used to writing SQL with jOOQ. Obviously, you
can also use something other than jOOQ, e.g. Spring’s JdbcTemplate,
or Apache Commons DbUtils, or just wrap the JDBC ResultSet in an
Iterator…

What’s very nice about this approach, compared to ORM is the
fact that there is no magic happening at all. Every piece of
mapping logic is explicit and, thanks to Java generics, fully
typesafe. The type of thebooksByAuthor output is
complex, and a bit hard to read / write, in this example, but it is
also fully descriptive and useful.

The same functional transformation with POJOs

If you aren’t too happy with using
jOOQ’s Record2 tuple types, no problem. You
can specify your own data transfer objects like so:

class Book {
    public int id;
    public String title;
 
    @Override
    public String toString() { ... }
 
    @Override
    public int hashCode() { ... }
 
    @Override
    public boolean equals(Object obj) { ... }
}
 
static class Author {
    public String firstName;
    public String lastName;
 
    @Override
    public String toString() { ... }
 
    @Override
    public int hashCode() { ... }
 
    @Override
    public boolean equals(Object obj) { ... }
}

With the above DTO, you can now leverage jOOQ’s
built-in POJO mapping
to transform the jOOQ records into your
own domain classes:

Map<Author, List<Book>> booksByAuthor =
ctx.select(
        BOOK.ID,
        BOOK.TITLE,
        AUTHOR.FIRST_NAME,
        AUTHOR.LAST_NAME
    )
   .from(BOOK)
   .join(AUTHOR)
   .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
   .orderBy(BOOK.ID)
   .fetch()
   .stream()
   .collect(groupingBy(
 
        // This is the grouping key      
        r -> r.into(Author.class),
        LinkedHashMap::new,
 
        // This is the grouping value list
        mapping(
            r -> r.into(Book.class),
            toList()
        )
    ));

Explicitness vs. implicitness

At Data Geekery, we believe that a new time has started for Java
developers. A time where Annotatiomania™ (finally!) ends and people
stop assuming all that implicit behaviour through annotation magic.
ORMs depend on a huge amount of specification to explain how each
annotation works with each other annotation. It is hard to
reverse-engineer (or debug!) this kind of not-so-well-understood
annotation-language that JPA has brought to us.

On the flip side, SQL is pretty well understood. Tables are an
easy-to-handle data structure, and if you need to transform those
tables into something more object-oriented, or more hierarchically
structured, you can simply apply functions to those tables and
group values yourself! By grouping those values explicitly, you
stay in full control of your mapping, just as with jOOQ, you stay
in full control of your SQL.

This is why we believe that in the next 5 years, ORMs will lose
relevance and people start
embracing explicitstateless and magicless data
transformation techniques again, using Java 8 Streams.

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