How JPA 2.1 has become the new EJB 2.0
Beauty lies in the eye of the beholder. So does “ease”:
Learn more about SQL Result Set Mappings and handle your native query results with ease: http://t.co/WH4BTlClIP #JPA #Java #JavaEE
— Thorben Janssen (@thjanssen123) April 15, 2015
Thorben writes very good and useful articles about JPA, and he’s recently started an excellent series about JPA 2.1’s new features. Among which: Result set mapping. You may know result set mapping from websites like CTMMC, or annotatiomania.com. We can summarise this mapping procedure as follows:
a) define the mapping
@SqlResultSetMapping( name = "BookAuthorMapping", entities = { @EntityResult( entityClass = Book.class, fields = { @FieldResult(name = "id", column = "id"), @FieldResult(name = "title", column = "title"), @FieldResult(name = "author", column = "author_id"), @FieldResult(name = "version", column = "version")}), @EntityResult( entityClass = Author.class, fields = { @FieldResult(name = "id", column = "authorId"), @FieldResult(name = "firstName", column = "firstName"), @FieldResult(name = "lastName", column = "lastName"), @FieldResult(name = "version", column = "authorVersion")})})
The above mapping is rather straight-forward. It specifies how database columns should be mapped to entity fields and to entities as a whole. Then you give this mapping a name ("BookAuthorMapping"
), which you can then reuse across your application, e.g. with native JPA queries.
I specifically like the fact that Thorben then writes:
If you don’t like to add such a huge block of annotations to your entity, you can also define the mapping in an XML file
… So, we’re back to replacing huge blocks of annotations by huge blocks of XML – a technique that many of us wanted to avoid using annotations… :-)
b) apply the mapping
Once the mapping has been statically defined on some Java type, you can then fetch those entities by applying the above BookAuthorMapping
List<Object[]> results = this.em.createNativeQuery( "SELECT b.id, b.title, b.author_id, b.version, " + " a.id as authorId, a.firstName, a.lastName, " + " a.version as authorVersion " + "FROM Book b " + "JOIN Author a ON b.author_id = a.id", "BookAuthorMapping" ).getResultList(); results.stream().forEach((record) -> { Book book = (Book)record[0]; Author author = (Author)record[1]; });
Notice how you still have to remember the Book
and Author
types and cast explicitly as no verifiable type information is really attached to anything.
The definition of “complex”
Now, the article claims that this is “complex” mapping, and no doubt, I would agree. This very simple query with only a simple join already triggers such an annotation mess if you want to really map your entities via JPA. You don’t want to see Thorben’s mapping annotations, once the queries get a little more complex. And remember, @SqlResultSetMapping
is about mapping (native!) SQL results, so we’re no longer in object-graph-persistence land, we’re in SQL land, where bulk fetching, denormalising, aggregating, and other “fancy” SQL stuff is king.
The problem is here:
Java 5 introduced annotations. Annotations were originally intended to be used as “artificial modifiers”, i.e. things like static
, final
, protected
(interestingly enough, Ceylon only knows annotations, no modifiers). This makes sense. Java language designers could introduce new modifiers / “keywords” without breaking existing code – because “real” keywords are reserved words, which are hard to introduce in a language. Remember enum
?
So, good use-cases for annotations (and there are only few) are:
@Override
@Deprecated
(although, a comment attribute would’ve been fancy)@FunctionalInterface
JPA (and other Java EE APIs, as well as Spring) have gone completely wacko on their use of annotations. Repeat after me:
No language before or after Java ever abused annotations as much as Java
There is a strong déjà vu in me when reading the above. Do you remember the following?
No language before or after Java ever abused checked exceptions as much as Java
We will all deeply regret Java annotations by 2020.
Annotations are a big wart in the Java type system. They have an extremely limited justified use and what we Java Enterprise developers are doing these days is absolutely not within the limits of “justified”. We’re abusing them for configuration for things that we should really be writing code for.
Here’s how you’d run the same query with jOOQ (or any other API that leverages generics and type safety for SQL):
Book b = BOOK.as("b"); Author a = AUTHOR.as("a"); DSL.using(configuration) .select(b.ID, b.TITLE, b.AUTHOR_ID, b.VERSION, a.ID, a.FIRST_NAME, a.LAST_NAME, a.VERSION) .from(b) .join(a).on(b.AUTHOR_ID.eq(a.ID)) .fetch() .forEach(record -> { BookRecord book = record.into(b); AuthorRecord author = record.into(a); });
This example combines both JPA 2.1’s annotations AND querying. All the meta information about projected “entities” is already contained in the query and thus in the Result
that is produced by the fetch()
method. But it doesn’t really matter, the point here is that this lambda expression …
record -> { BookRecord book = record.into(b); AuthorRecord author = record.into(a); }
… it can be anything you want! Like the more sophisticated examples we’ve shown in previous blog posts:
Mapping can be defined ad-hoc, on the fly, using functions. Functions are the ideal mappers, because they take an input, produce an output, and are completely stateless. And the best thing about functions in Java 8 is, they’re compiled by the Java compiler and can be used to type-check your mapping. And you can assign functions to objects, which allows you to reuse the functions, when a given mapping algorithm can be used several times.
In fact, the SQL SELECT
clause itself is such a function. A function that transforms input tuples / rows into output tuples / rows, and you can adapt that function on the fly using additional expressions.
There is absolutely no way to type-check anything in the previous JPA 2.1 native SQL statement and @SqlResultSetMapping
example. Imagine changing a column name:
List<Object[]> results = this.em.createNativeQuery( "SELECT b.id, b.title as book_title, " + " b.author_id, b.version, " + " a.id as authorId, a.firstName, a.lastName, " + " a.version as authorVersion " + "FROM Book b " + "JOIN Author a ON b.author_id = a.id", "BookAuthorMapping" ).getResultList();
Did you notice the difference? The b.title
column was renamed to book_title
. In a SQL string. Which blows up at run time! How to remember that you have to also adapt
@FieldResult(name = "title", column = "title")
… to be
@FieldResult(name = "title", column = "book_title")
Conversely, how to remember that once you rename the column
in your @FieldResult
, you’ll also have to go check wherever this "BookAuthorMapping"
is used, and also change the column names in those queries.
@SqlResultSetMapping( name = "BookAuthorMapping", ... )
Annotations are evil
You may or may not agree with some of the above. You may or may not like jOOQ as an alternative to JPA, that’s perfectly fine. But it is really hard to disagree with the fact that:
- Java 5 introduced very useful annotations
- Java EE / Spring heavily abused those annotations to replace XML
- We now have a parallel universe type system in Java
- This parallel universe type system is completely useless because the compiler cannot introspect it
- Java SE 8 introduces functional programming and lots of type inference
- Java SE 9-10 will introduce more awesome language features
- It now becomes clear that what was configuration (XML or annotations) should have been code in the first place
- JPA 2.1 has become the new EJB 2.0: Obsolete
As I said. Hard to disagree. Or in other words:
Code is much better at expressing algorithms than configuration
I’ve met Thorben personally on a number of occasions at conferences. This rant here wasn’t meant personally, Thorben :-) Your articles about JPA are very interesting. If you readers of this article are using JPA, please check out Thorben’s blog: http://www.thoughts-on-java.org.
In the meantime, I would love to nominate Thorben for the respected title “The Annotatiomaniac of the Year 2015“
Reference: | How JPA 2.1 has become the new EJB 2.0 from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |
So, this article starts from a bad example (would be quite easy to do and type-safe in pure JPA instead of native queries), to conclude that JPA is obsolete. What’s the free standard (as in specification-backed) that does replace it? Reality: you don’t need such a mess to map a relation. Background: the author is strongly involved in JOOQ and has an history of presenting opinions as facts. Lukas: why do you choose to present biased opinions about JPA instead of using your experience to evolve the spec? The “facts” around annotations are pure inventions. Any background for what they… Read more »
Thanks for chiming in also on this channel Yannick. I see you have a desire to get your point across. I have answered to your comment on DZone:
http://java.dzone.com/articles/how-jpa-21-has-become-new-ejb#comment-133759
Cheers,
Lukas
Well, something had to be done. Please don’t reverse it: you’re the one who’s spamming, I just commented ;-)
Let’s go on on dzone I guess.
Yannick