Most jOOQ users use the jOOQ DSL API, which provides compile time type safety and an easy way to write dynamic SQL.
But occasionally, this DSL get in the way, because it might be
(unfortunately, this blog’s syntax highlighter is not quite there yet…)
The output is jOOQ’s nicely formatted text result:
We can now write:
We’ve written a dynamic SQL query with jOOQ, but without using much type safety (we could still use some type safe, generated schema object references if we wanted to). jOOQ’s plain SQL templating capabilities are a much underestimated feature set. People have been using MyBatis for many years precisely for these purposes, or maybe built home grown, velocity template based frameworks to produce dynamic SQL using native SQL strings.
You can also use jOOQ for this, and profit from all of the jOOQ API, including, again the formatted output from the above:
Observe the irrelevant whitespace difference. Oracle will parse a new SQL string, produce a new SQL_ID, and yet come up with the same execution plan. You don’t want this to happen too often, because it puts a lot of contention on the plan cache and its locks and latches, and creates extra work.
This can happen, nonetheless, with string based dynamic SQL. Using jOOQ’s parser, you can normalise all sorts of SQL strings (including removing comments)
The SQL string that gets sent to the JDBC driver is this:
And now, the SQL string that gets sent to the JDBC driver is this:
The translated SQL that gets executed on H2 is this:
Producing this in H2:
I.e. just copy paste your plain SQL schema definitions and increments (as used in Flyway, for example, or hand written while developing them against your database), and start using the entire tool chain on it. For example, the above prints:
And now, the output is the increment we’ve applied earlier
- Overkill for some very simple quick and dirty SQL queries
- Too limited when running very advanced vendor specific SQL, such as Oracle’s
MODEL
orMATCH_RECOGNIZE
clauses
Plain SQL
The main use case is to use plain SQL and also plain SQL templating. For example, to run a quick and dirty jOOQ query like this on H2 or PostgreSQL, for example:
System.out.println(ctx.fetch("""
SELECT table_schema, count(*)
FROM information_schema.tables
GROUP BY table_schema
ORDER BY table_schema
"""));
+------------------+--------+ |TABLE_SCHEMA |COUNT(*)| +------------------+--------+ |INFORMATION_SCHEMA| 33| |MCVE | 2| |PUBLIC | 1| +------------------+--------+The above is a simple plain SQL query, but we can also make use of plain SQL templating with text blocks! What if we wanted to have a dynamic
GROUP BY
clause?
Always assuming this static import:
import static org.jooq.impl.DSL.*;
Stream.of(
field("table_schema"),
list(field("table_schema"), field("table_type")))
.forEach(q -> {
System.out.println(ctx.fetch("""
SELECT {0}, count(*), row_number() OVER (ORDER BY {0}) AS rn
FROM information_schema.tables
GROUP BY {0}
ORDER BY {0}
""", q));
});
+------------------+--------+----+ |TABLE_SCHEMA |COUNT(*)| RN| +------------------+--------+----+ |INFORMATION_SCHEMA| 33| 1| |MCVE | 2| 2| |PUBLIC | 1| 3| +------------------+--------+----+ +------------------+------------+--------+----+ |TABLE_SCHEMA |TABLE_TYPE |COUNT(*)| RN| +------------------+------------+--------+----+ |INFORMATION_SCHEMA|SYSTEM TABLE| 33| 1| |MCVE |TABLE | 1| 2| |MCVE |VIEW | 1| 3| |PUBLIC |TABLE | 1| 4| +------------------+------------+--------+----+All of this was available before text blocks, but with text blocks, it makes even more sense to use these features. There is not just one way to use jOOQ, but this approach is really very underestimated!
The parser
Another very important jOOQ feature that works with string based SQL is the jOOQ parser. There are a variety of use cases for using jOOQ’s parser, as we’re adding more and more features to it. One use-case is simple formatting of SQL. Why would you want to do this? One example is to standardise your SQL strings for better execution plan cache usage. As you may know, some database vendors (e.g. Oracle) treat these two SQL strings as different:
SELECT 1 FROM dual
SELECT 1 FROM dual
System.out.println(
ctx.parser()
.parseResultQuery("""
SELECT table_schema, count(*)
FROM information_schema.tables
GROUP BY table_schema
-- Order by column index!
ORDER BY 1
""")
.fetch()
);
select table_schema, count(*) from information_schema.tables group by table_schema order by 1Alternatively, you could specify some formatting in jOOQ:
DSLContext ctx = DSL.using(connection,
new Settings().withRenderFormatted(true));
select table_schema, count(*) from information_schema.tables group by table_schema order by 1You can tweak the various supported formatting rules, and use this for logging your hand written SQL strings in a human readable way, for example, rather than executing the SQL through jOOQ. The options are endless. You could also translate parser output to some other SQL dialect. Assuming you run your queries against SQL Server, but want to translate them to H2. No problem! The following SQL Server specific query can be run easily on H2 as well:
System.out.println(
ctx.parser()
.parseResultQuery("""
SELECT TOP 1 table_schema, count(*)
FROM information_schema.tables
GROUP BY table_schema
ORDER BY count(*) DESC
""")
.fetch()
);
select table_schema, count(*) from information_schema.tables group by table_schema order by count(*) desc limit 1Or, because I like showing off:
System.out.println(
ctx.parser()
.parseResultQuery("""
SELECT TOP 1 WITH TIES table_schema, count(*)
FROM information_schema.tables
GROUP BY table_schema
ORDER BY count(*) DESC
""")
.fetch()
);
select TABLE_SCHEMA, count(*) from INFORMATION_SCHEMA.TABLES group by TABLE_SCHEMA order by 2 desc fetch next 1 rows with tiesOr this, in PostgreSQL:
select "v0" as table_schema, "v1" as "count" from ( select table_schema as "v0", count(*) as "v1", rank() over (order by 2 desc) as "rn" from information_schema.tables group by table_schema ) "x" where "rn" > 0 and "rn" <= (0 + 1) order by "rn"I know, right?
Parser-derived features
The parser is used in more and more of jOOQ’s many features. Including for example in jOOQ 3.13’s schema diff and management utility. Run this with jOOQ:
System.out.println(
ctx.meta("""
create table t (
i int
)
""").apply("""
alter table t
add j int;
alter table t
add constraint t_pk primary key (i)
""")
);
create table T( I int null, J int null, constraint T_PK primary key (I) );Instead of applying increments to a schema, you can also calculate the diff between two schema versions:
System.out.println(
ctx.meta("""
create table t (
i int
)
""").migrateTo(ctx.meta("""
create table t (
i int,
j int,
constraint t_pk primary key (i)
)
"""))
);
alter table T add J int null; alter table T add constraint T_PK primary key (I);
Conclusion
jOOQ’s DSL is very powerful because:- It offers type safety
- It is compile time checked
- It helps with auto completion