jOOQ’s main value proposition is obvious: Type safe embedded SQL in Java.
People who actively look for such a SQL builder will inevitably stumble upon jOOQ and love it, of course. But a lot of people don’t really need a SQL builder – yet, jOOQ can still be immensely helpful in other situations, through its lesser known features.
Here’s a list of top 5 “hidden” jOOQ features.
1. Working with JDBC ResultSet
Even if you’re otherwise not using jOOQ but JDBC (or Spring JdbcTemplate, etc.) directly, one of the things that’s most annoying is working with ResultSet
. A JDBC ResultSet
models a database cursor, which is essentially a pointer to a collection on the server, which can be positioned anywhere, e.g. to the 50th record via ResultSet.absolute(50)
(remember to start counting at 1).
The JDBC ResultSet
is optimised for lazy data processing. This means that we don’t have to materialise the entire data set produced by the server in the client. This is a great feature for large (and even large-ish) data sets, but in many cases, it’s a pain. When we know we’re fetching only 10 rows and we know that we’re going to need them in memory anyway, a List<Record>
type would be much more convenient.
jOOQ’s org.jooq.Result
is such a List
, and fortunately, you can easily import any JDBC ResultSet
easily as follows by using DSLContext.fetch(ResultSet)
:
try (ResultSet rs = stmt.executeQuery()) { Result<Record> result = DSL.using(connection).fetch(rs); System.out.println(result); }
With that in mind, you can now access all the nice jOOQ utilities, such as formatting a result, e.g. as TEXT (see The second feature for more details):
+---+---------+-----------+ | ID|AUTHOR_ID|TITLE | +---+---------+-----------+ | 1| 1|1984 | | 2| 1|Animal Farm| +---+---------+-----------+
Of course, the inverse is always possible as well. Need a JDBC ResultSet
from a jOOQ Result
? Call Result.intoResultSet()
and you can inject dummy results to any application that operates on JDBC ResultSet
:
DSLContext ctx = DSL.using(connection); // Get ready for Java 10 with var! var result = ctx.newResult(FIRST_NAME, LAST_NAME); result.add(ctx.newRecord(FIRST_NAME, LAST_NAME) .values("John", "Doe")); // Pretend this is a real ResultSet try (ResultSet rs = result.intoResultSet()) { while (rs.next()) System.out.println(rs.getString(1) + " " + rs.getString(2)); }
2. Exporting a Result as XML, CSV, JSON, HTML, TEXT, ASCII Chart
As we’ve seen in the previous section, jOOQ Result
types have nice formatting features. Instead of just text, you can also format as XML, CSV, JSON, HTML, and again TEXT
The format can usually be adapted to your needs.
For instance, this text format is possible as well:
ID AUTHOR_ID TITLE ------------------------ 1 1 1984 2 1 Animal Farm
When formatting as CSV, you’ll get:
ID,AUTHOR_ID,TITLE 1,1,1984 2,1,Animal Farm
When formatting as JSON, you might get:
[{"ID":1,"AUTHOR_ID":1,"TITLE":"1984"}, {"ID":2,"AUTHOR_ID":1,"TITLE":"Animal Farm"}]
Or, depending on your specified formatting options, perhaps you’ll prefer the more compact array of array style?
[[1,1,"1984"],[2,1,"Animal Farm"]]
Or XML, again with various common formatting styles, among which:
<result> <record> <ID>1</ID> <AUTHOR_ID>1</AUTHOR_ID> <TITLE>1984</TITLE> </record> <record> <ID>2</ID> <AUTHOR_ID>1</AUTHOR_ID> <TITLE>Animal Farm</TITLE> </record> </result>
HTML seems kind of obvious. You’ll get:
ID | AUTHOR_ID | TITLE |
---|---|---|
1 | 1 | 1984 |
2 | 1 | Animal Farm |
Or, in code:
<table> <tr><th>ID</th><th>AUTHOR_ID</th><th>TITLE</th></tr> <tr><td>1</td><td>1</td><td>1984</td></tr> <tr><td>2</td><td>1</td><td>Animal Farm</td></tr> </table>
As a bonus, you could even export the Result as an ASCII chart:
These features are obvious additions to ordinary jOOQ queries, but as I’ve shown in Section 1, you can get free exports from JDBC results as well!
3. Importing these text formats again
After the previous section’s export capabilities, it’s natural to think about how to import such data again back into a more usable format. For instance, when you write integration tests, you might expect a database query to return a result like this:
ID AUTHOR_ID TITLE -- --------- ----------- 1 1 1984 2 1 Animal Farm
Simply import the above textual representation of your result set into an actual jOOQ Result
using Result.fetchFromTXT(String)
and you can continue operating on a jOOQ Result
(or as illustrated in Section 1, with a JDBC ResultSet
!).
Most of the other export formats (except charts, of course) can be imported as well.
Now, don’t you wish for a second that Java has multi-line strings (in case of which this would be very nice looking):
Result<?> result = ctx.fetchFromTXT( "ID AUTHOR_ID TITLE \n" + "-- --------- -----------\n" + " 1 1 1984 \n" + " 2 1 Animal Farm\n" ); ResultSet rs = result.intoResultSet();
These types can now be injected anywhere where a service or DAO produces a jOOQ Result
or a JDBC ResultSet
. The most obvious application for this is mocking. The second most obvious application is testing. You can easily test that a service produces an expected result of the above form.
Let’s talk about mocking:
4. Mocking JDBC
Sometimes, mocking is cool. With the above tools, it’s only natural for jOOQ to provide a full-fledged, JDBC-based mocking SPI. I’ve written about this feature before and again here.
Essentially, you can implement a single FunctionalInterface
called MockDataProvider
. The simplest way to create one is by using the Mock.of()
methods, e.g.:
MockDataProvider provider = Mock.of(ctx.fetchFromTXT( "ID AUTHOR_ID TITLE \n" + "-- --------- -----------\n" + " 1 1 1984 \n" + " 2 1 Animal Farm\n" ));
What this provider does is it simply ignores all the input (queries, bind variables, etc.) and always returns the same simple result set. You can now plug this provider into a MockConnection
and use it like any ordinary JDBC connection:
try (Connection c = new MockConnection(provider); PreparedStatement s = c.prepareStatement("SELECT foo"); ResultSet rs = s.executeQuery()) { while (rs.next()) { System.out.println("ID : " + rs.getInt(1)); System.out.println("First name: " + rs.getString(2)); System.out.println("Last name : " + rs.getString(3)); } }
The output being (completely ignoring the SELECT foo statement):
ID : 1 First name: 1 Last name : 1984 ID : 2 First name: 1 Last name : Animal Farm
This client code doesn’t even use jOOQ (although it could)! Meaning, you can use jOOQ as a JDBC mocking framework on any JDBC-based application, including a Hibernate based one.
Of course, you don’t always want to return the exact same result. This is why a MockDataProvider
offers you an argument with all the query information in it:
try (Connection c = new MockConnection(ctx -> { if (ctx.sql().toLowerCase().startsWith("select")) { // ... } })) { // Do stuff with this connection }
You can almost implement an entire JDBC driver with a single lambda expression. Read more here. Cool, eh?
Side note: Don’t get me wrong: I don’t think you should mock your entire database layer just because you can. My thoughts are available in this tweet storm:
Speaking of “synthetic JDBC connections”
5. Parsing Connections
jOOQ 3.9 introduced a SQL parser, whose main use case so far is to parse and reverse engineer DDL scripts for the code generator.
Another feature that has not been talked about often yet (because still a bit experimental) is the parsing connection, available through DSLContext.parsingConnection()
. Again, this is a JDBC Connection
implementation that wraps a physical JDBC connection but runs all SQL queries through the jOOQ parser before generating them again.
What’s the point?
Let’s assume for a moment that we’re using SQL Server, which supports the following SQL standard syntax:
SELECT * FROM (VALUES (1), (2), (3)) t(a)
The result is:
a --- 1 2 3
Now, let’s assume we are planning to migrate our application to Oracle and we have the following JDBC code that doesn’t work on Oracle, because Oracle doesn’t support the above syntax:
try (Connection c = DriverManager.getConnection("..."); Statement s = c.createStatement(); ResultSet rs = s.executeQuery( "SELECT * FROM (VALUES (1), (2), (3)) t(a)")) { while (rs.next()) System.out.println(rs.getInt(1)); }
Now, we have three options (hint #1 sucks, #2 and #3 are cool):
- Tediously migrate all such manually written JDBC based SQL to Oracle syntax and hope we don’t have to migrate back again
- Upgrade our JDBC based application to use jOOQ instead (that’s the best option, of course, but it also takes some time)
- Simply use the jOOQ parsing connection as shown below, and a lot of code will work right out of the box! (and then, of course, gradually migrate to jOOQ, see option #2)
try (DSLContext ctx = DSL.using("..."); Connection c = ctx.parsingConnection(); // Magic here Statement s = c.createStatement(); ResultSet rs = s.executeQuery( "SELECT * FROM (VALUES (1), (2), (3)) t(a)")) { while (rs.next()) System.out.println(rs.getInt(1)); }
We haven’t touched any of our JDBC based client logic. We’ve only introduced a proxy JDBC connection that runs every statement through the jOOQ parser prior to re-generating the statement on the wrapped, physical JDBC connection.
What’s really executed on Oracle is this emulation here:
select t.a from ( (select null a from dual where 1 = 0) union all (select * from ( (select 1 from dual) union all (select 2 from dual) union all (select 3 from dual) ) t) ) t
Looks funky, eh? The rationale for this emulation is described here.
Every SQL feature that jOOQ can represent with its API and that it can emulate between databases will be supported! This includes far more trivial things, like parsing this query:
SELECT substring('abcdefg', 2, 4)
… and running this one on Oracle instead:
select substr('abcdefg', 2, 4) from dual
You’re all thinking
Want to learn more about jOOQ?
There are many more such nice little things in the jOOQ API, which help make you super productive. Some examples include: