Reputation: 1941
Just about to start playing with jOOQ for a proof of concept. jOOQ looks really simple, expressive and makes SQL maintenance a lot more easier.
We're a Java 8 shop. The usecase here is to write the data layer for a reporting app that dynamically queries tables, columns, filters and functions based on the user selection on the screen.
Although I really like the idea of writing type-safe queries (using the jOOQ codegen), I suppose for my usecase, that won't be a best fit. Because tables, columns etc etc are completely unknown, I suppose I just need the jOOQ SQL builder. That means I have to give up type safety. Is my assessment correct? Or are there any patterns I could use for building "dynamic" SQLs without compromising the type safety? Any pointers would be much appreciated.
Upvotes: 2
Views: 243
Reputation: 220987
You don't have to use jOOQ's code generator to take advantage of most features in jOOQ. The manual's introduction section states that jOOQ can be easily used as a SQL builder without the extra type static safety provided by the code generator:
https://www.jooq.org/doc/latest/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder
The code generator essentially provides two type safety elements:
These things certainly helps develop your application
... but beware that the code generator simply reverse engineers a static snapshot of your schema. It is type safe because the entire jOOQ API allows for this kind of type safety. For instance, a Field<T>
type has a generic type <T>
, which can be used without the code generator as well, e.g. by using the plain SQL APIs:
Field<String> firstName = field(name("USER", "FIRST_NAME"), SQLDataType.VARCHAR(50));
The above API usage (DSL.field(Name, DataType)
) does roughly the same as what the code generator would do anyway. It creates a column reference with column type information attached to it. You can use it like the columns generated by the code generator:
DSL.using(configuration)
.select(firstName)
.from(name("USER"))
.where(firstName.like("A%")) // Compiles
.and(firstName.eq(1)) // Doesn't compile: firstName must be compared to String
.join(name("ADDRESS")) // Doesn't compile: the SQL syntax is wrong
.fetch();
As you can see, the only thing that changed compared to using the code generator is the table / column references.
But this means, that jOOQ is even more powerful for you without the code generator. You can still create dynamic SQL statements very easily. For instance:
// Construct your SQL query elements dynamically, and type safely
Condition condition = hasFirstNameFilter()
? firstName.like("A%")
: DSL.trueCondition();
DSL.using(configuration)
.select(firstName)
.from(name("USER"))
.where(condition) // Use dynamically constructed element here
.fetch();
You could also do this in a "functional way":
DSL.using(configuration)
.select(firstName)
.from(name("USER"))
.where(condition()) // Call a function to create the condition here
.fetch();
Or even better
public static Select<Record1<String>> firstNames(
Function<? super Field<String>, ? extends Condition> condition
) {
return
DSL.using(configuration)
.select(firstName)
.from(name("USER"))
.where(condition.apply(firstName)); // Lazy evaluate the predicate here
}
// Use it like this:
firstNames(col -> col.like("A%")).fetch();
Or even better, make the above a higher order function:
public static Function<
? super Function<? super Field<String>, ? extends Condition>,
? extends Select<Record1<String>>
> firstNames() {
// Lazy construct query here
return f -> DSL.using(configuration)
.select(firstName)
.from(name("USER"))
.where(f.apply(firstName)); // Lazy evaluate the predicate here
}
// Use it like this:
firstNames().apply(col -> col.like("A%")).fetch();
More details here: https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql
As you can see, while the code generator does add a lot of value for static schemas, there's nothing really static in the jOOQ API. jOOQ is an API for dynamic SQL query construction, that just happens to work well for static queries as well.
Upvotes: 2