Happy about the API

Java 8: No More Need for ORMs

Lukas Eder
happy-coffee

Java 8, and the new Streams API finally allow us to solve data-centric problems in a very concise manner. Lukas Eder breaks it all down.

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
.

No More Need for ORMs

Debates about the usefulness of ORM
(Object-Relational Mapping)
 have been going on for the
last decade. While many people would agree that Hibernate and JPA
solve a lot of problems very well (mostly the persistence of
complex object graphs), others may claim that the mapping complexity is
mostly overkill for data-centric applications
.

JPA solves mapping problems by establishing standardised,
declarative mapping rules through hard-wired annotations on the
receiving target types. We claim that many data-centric problems
should not be limited by the narrow scope of these annotations, but
be solved in a much more functional way. Java 8, and the new
Streams API finally allow us to do this in a very concise
manner!

Let’s start with a simple example, where we’re using
H2′sINFORMATION_SCHEMA to collect all tables and
their columns. We’ll want to produce an ad-hoc data structure of
the type Map<String,
List<String>>
 to contain this information. For
simplicity of SQL interaction, we’ll use jOOQ (as always, a shocker on this
blog). Here’s how we prepare this:

public static void main(String[] args)
throws Exception {
    Class.forName("org.h2.Driver");
    try (Connection c = getConnection(
            "jdbc:h2:~/sql-goodies-with-mapping", 
            "sa", "")) {
 
        // This SQL statement produces all table
        // names and column names in the H2 schema
        String sql =
            "select table_name, column_name " +
            "from information_schema.columns " +
            "order by " +
                "table_catalog, " +
                "table_schema, " +
                "table_name, " +
                "ordinal_position";
 
        // This is jOOQ's way of executing the above
        // statement. Result implements List, which
        // makes subsequent steps much easier
        Result<Record> result =
        DSL.using(c)
           .fetch(sql)
    }
}

Now that we’ve set up this query, let’s see how we can produce
theMap<String, List<String>> from the
jOOQ Result:

DSL.using(c)
   .fetch(sql)
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),
       mapping(
           r -> r.getValue("COLUMN_NAME"),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> 
           System.out.println(table + ": " + columns)
   );

The above example produces the following output:

FUNCTION_COLUMNS: [ALIAS_CATALOG, ALIAS_SCHEMA, ...]
CONSTANTS: [CONSTANT_CATALOG, CONSTANT_SCHEMA, ...]
SEQUENCES: [SEQUENCE_CATALOG, SEQUENCE_SCHEMA, ...]

How does it work? Let’s go through it step-by-step

DSL.using(c)
   .fetch(sql)
 
// Here, we transform a List into a Stream
   .stream()
 
// We're collecting Stream elements into a new
// collection type
   .collect(
 
// The Collector is a grouping operation, producing
// a Map
            groupingBy(
 
// The grouping operation's group key is defined by
// the jOOQ Record's TABLE_NAME value
       r -> r.getValue("TABLE_NAME"),
 
// The grouping operation's group value is generated
// by this mapping expression...
       mapping(
 
// ... which is essentially mapping each grouped
// jOOQ Record to the Record's COLUMN_NAME value
           r -> r.getValue("COLUMN_NAME"),
 
// ... and then collecting all those values into a
// java.util.List. Whew
           toList()
       )
   ))
 
// Once we have this List<String, List<String>> we
// can simply consume it with the following Consumer
// lambda expression
   .forEach(
       (table, columns) -> 
           System.out.println(table + ": " + columns)
   );

Got it? These things are certainly a bit tricky when playing
around with it for the first time. The combination of new types,
extensive generics, lambda expressions can be a bit confusing at
first. The best thing is to simply practice with these things until
you get a hang of it. After all, the whole Streams API is really a
revolution compared to previous Java Collections APIs.

The good news is: This API is final and here to stay. Every
minute you spend practicing it is an investment into your own
future.

Note that the above programme used the following static
import:

import static java.util.stream.Collectors.*;

Note also, that the output was no longer ordered as in the
database. This is because
the groupingBy collector returns
java.util.HashMap. In our case, we might prefer
collecting things into a java.util.LinkedHashMap,
which preserves insertion / collection order:

DSL.using(c)
   .fetch(sql)
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),
 
       // Add this Supplier to the groupingBy
       // method call
       LinkedHashMap::new,
       mapping(
           r -> r.getValue("COLUMN_NAME"),
           toList()
       )
   ))
   .forEach(...);

We could go on with other means of transforming results. Let’s
imagine, we would like to generate simplistic DDL from the above
schema. It’s very simple. First, we’ll need to select column’s data
type. We’ll simply add it to our SQL query:

String sql =
    "select " +
        "table_name, " +
        "column_name, " +
        "type_name " + // Add the column type
    "from information_schema.columns " +
    "order by " +
        "table_catalog, " +
        "table_schema, " +
        "table_name, " +
        "ordinal_position";

I have also introduced a new local class for the example, to
wrap name and type attributes:

class Column {
    final String name;
    final String type;
 
    Column(String name, String type) {
        this.name = name;
        this.type = type;
    }
}

Now, let’s see how we’ll change our Streams API method
calls:

result
    .stream()
    .collect(groupingBy(
        r -> r.getValue("TABLE_NAME"),
        LinkedHashMap::new,
        mapping(
 
            // We now collect this new wrapper type
            // instead of just the COLUMN_NAME
            r -> new Column(
                r.getValue("COLUMN_NAME", String.class),
                r.getValue("TYPE_NAME", String.class)
            ),
            toList()
        )
    ))
    .forEach(
        (table, columns) -> {
 
            // Just emit a CREATE TABLE statement
            System.out.println(
                "CREATE TABLE " + table + " (");
 
            // Map each "Column" type into a String
            // containing the column specification,
            // and join them using comma and
            // newline. Done!
            System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",n"))
            );
 
            System.out.println(");");
        }
    );

The output couldn’t be more awesome!

CREATE TABLE CATALOGS(
  CATALOG_NAME VARCHAR
);
CREATE TABLE COLLATIONS(
  NAME VARCHAR,
  KEY VARCHAR
);
CREATE TABLE COLUMNS(
  TABLE_CATALOG VARCHAR,
  TABLE_SCHEMA VARCHAR,
  TABLE_NAME VARCHAR,
  COLUMN_NAME VARCHAR,
  ORDINAL_POSITION INTEGER,
  COLUMN_DEFAULT VARCHAR,
  IS_NULLABLE VARCHAR,
  DATA_TYPE INTEGER,
  CHARACTER_MAXIMUM_LENGTH INTEGER,
  CHARACTER_OCTET_LENGTH INTEGER,
  NUMERIC_PRECISION INTEGER,
  NUMERIC_PRECISION_RADIX INTEGER,
  NUMERIC_SCALE INTEGER,
  CHARACTER_SET_NAME VARCHAR,
  COLLATION_NAME VARCHAR,
  TYPE_NAME VARCHAR,
  NULLABLE INTEGER,
  IS_COMPUTED BOOLEAN,
  SELECTIVITY INTEGER,
  CHECK_CONSTRAINT VARCHAR,
  SEQUENCE_NAME VARCHAR,
  REMARKS VARCHAR,
  SOURCE_DATA_TYPE SMALLINT
);

Excited? The ORM era may have ended just now

This is a strong statement. The ORM era may have ended. Why?
Because using functional expressions to transform data sets is one
of the most powerful concepts in software engineering. Functional
programming is very expressive and very versatile. It is at the
core of data and data streams processing. We Java developers
already know existing functional languages. Everyone has used SQL
before, for instance. Think about it. With SQL, you declare table
sources, project / transform them onto new tuple streams, and feed
them either as derived tables to other, higher-level SQL
statements, or to your Java program.

If you’re using XML, you can declare XML transformation using
XSLT and feed results to other XML processing entities, e.g.
another XSL stylesheet, using XProc pipelining.

Java 8′s Streams are nothing else. Using SQL and the Streams API
is one of the most powerful concepts for data processing. If you
add jOOQ to the stack,
you can profit from typesafe access to your database records and
query APIs. Imagine writing the previous statement using jOOQ’s
fluent API, instead of using SQL strings.

jooq-the-best-way-to-write-sql-in-java

The whole method chain could be one single fluent data
transformation chain as such:

DSL.using(c)
   .select(
       COLUMNS.TABLE_NAME,
       COLUMNS.COLUMN_NAME,
       COLUMNS.TYPE_NAME
   )
   .from(COLUMNS)
   .orderBy(
       COLUMNS.TABLE_CATALOG,
       COLUMNS.TABLE_SCHEMA,
       COLUMNS.TABLE_NAME,
       COLUMNS.ORDINAL_POSITION
   )
   .fetch()  // jOOQ ends here
   .stream() // Streams start here
   .collect(groupingBy(
       r -> r.getValue(COLUMNS.TABLE_NAME),
       LinkedHashMap::new,
       mapping(
           r -> new Column(
               r.getValue(COLUMNS.COLUMN_NAME),
               r.getValue(COLUMNS.TYPE_NAME)
           ),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> {
            // Just emit a CREATE TABLE statement
            System.out.println(
                "CREATE TABLE " + table + " (");
 
            // Map each "Column" type into a String
            // containing the column specification,
            // and join them using comma and
            // newline. Done!
            System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",n"))
            );
 
           System.out.println(");");
       }
   );

Java 8 is the future, and with jOOQ,
Java 8, and the Streams API, you can write powerful data
transformation APIs. I hope we got you as excited as we
are! 

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