Best way to use dynamic SQL with jOOQ - java

Currently I'm using RQL (https://github.com/persvr/rql) as rest query language. Previously, Query DSL was used with the meta model for the SQL queries.
It is very easy to navigate over the metamodel to create a Predicate.
It is enough to use the root entity and was able to work your way through all the relationships and find out the Predicate
Example:
class Foo{
private Set<Bar> bars
}
class Bar{
private String name;
}
rql: filter=eq(bars.name, "test)
Query DSL is: QFoo.bars --> SetPath and then navigate to bars.name is a SimpleExpression then you can call SimpleExpression.eq returns the Predicate.
Is there also this possibility with jOOQ to create the condition dynamically?
I can't find way with the statically generated fields because they don't contain any relationships, do they?
What is the best way to create the condition using the root resource? Hints?
Or can I simply create a condition from the predicate?

As of jOOQ 3.17, to-many path expressions aren't yet available, see https://github.com/jOOQ/jOOQ/issues/13639
But you don't need them anyway, they're just syntax sugar for an IN or EXISTS predicate. E.g.
FOO.ID.in(select(BAR.FOO_ID).from(BAR).where(BAR.NAME.eq("test")))
It's a bit more verbose, but straightforward, I think? Note that every jOOQ query is a dynamic SQL query. You're not required to write the predicate like this. You can compose it from its individual parts completely dynamically.

Related

How to extract used table from SelectConditionStep<Record>

I'm extending on my last question I asked about jOOQ. In the Hibernate models the #Filter annotation gets used, and I want to apply this same 'default filter' to the jOOQ queries. As I'm passing a jOOQ query to the nativeQuery(org.jooq.Query query, Class<E> type) I was wondering if it's possible to extract the table (TableImpl<?,?>) used from the FROM clause in the jOOQ query (org.jooq.Query).
This is what I've tried:
private static <E> SelectConditionStep<Record> applyDefaultFilters(Class<E> type, SelectConditionStep<Record> query)
{
if (BaseOrganizationModel.class.isAssignableFrom(type)) {
query
.getQuery()
.addConditions(
query
.getQuery()
.asTable()
.field("organization_id", Long.class)
.eq(currentOrganization().id));
if (SoftDeletableModel.class.isAssignableFrom(type)) {
query
.getQuery()
.addConditions(query.getQuery().asTable().field("deleted", Boolean.class).eq(false));
}
}
return query;
}
The result is this SQL, which is not what I want. I want it to filter the corresponding table.
select distinct `EventGroup`.*
from `EventGroup`
where (
...
and `alias_100341773`.`organization_id` = ?
and `alias_17045196`.`deleted` = ?
)
I want this
select distinct `EventGroup`.*
from `EventGroup`
where (
...
and `EventGroup`.`organization_id` = ?
and `EventGroup`.`deleted` = ?
)
Is this possible at all? And if not, what possible other routes are there? (aside from the obvious passing the table to the function)
Using jOOQ 3.16 query object model API
jOOQ 3.16 introduced a new, experimental (as of 3.16) query object model API, which can be traversed.
On any Select, just call Select.$from() to access an unmodifiable view of the contained table list.
An alternative, dynamic SQL approach for the ad-hoc case
Every time you're trying to mutate an existing query, ask yourself, is there a more elegant way to do this using a more functional, immutable approach do dynamic SQL? Rather than appending your additional predicates to the query, why not produce predicates from a function?
private static Condition defaultFilters(Class<?> type, Table<?> table) {
Condition result = noCondition();
if (BaseOrganizationModel.class.isAssignableFrom(type)) {
result = result.and(table.field("organization_id", Long.class)
.eq(currentOrganization().id));
if (SoftDeletableModel.class.isAssignableFrom(type))
result = result.and(not(table.field("deleted", Boolean.class)))
}
return result;
}
And now, when you construct your query, you can add the filters:
ctx.select(T.A, T.B)
.from(T)
.where(T.X.eq(1))
.and(defaultFilters(myType, T))
.fetch();
A generic way to transform your SQL
If you really want to mutate your query (e.g. in a utility for all queries), then a transformation approach might be better suited. There are different ways to approach this.
Using views
Some RDBMS can access session variables in views. In Oracle, you'd be setting some SYS_CONTEXT variable to your organization_id inside of a view, and then query only the (possibly updatable) views instead of the tables directly. MySQL unfortunately can't do the equivalent thing, see Is there any equivalent to ORACLE SYS_CONTEXT('USERENV', 'OS_USER') in MYSQL?
I've described this approach here in this blog post. The advantage of this approach is that you will never forget to set the predicate (you can validate your view source code with CI/CD tests), and if you ever forget to set the session context variable, the view will just not return any data, so it's quite a secure approach.
Together with the WITH CHECK OPTION clause, you can even prevent insertions into wrong organization_id, which improves security.
Using a VisitListener in jOOQ
This is the most powerful approach to do this in jOOQ, and exactly what you want, but also quite a tricky one to get right for all edge cases. See this post about implementing row level security in jOOQ. Starting from jOOQ 3.16, there will be better ways to transform your SQL via https://github.com/jOOQ/jOOQ/issues/12425.
Note, it won't work for plain SQL templates that do not use any jOOQ query parts, nor for JDBC based queries or other queries that you may have in your system, so be careful with this approach as you might leak data from other organisations.
Of course, you could implement this step also on the JDBC layer, using jOOQ's ParsingConnection or ParsingDataSource, that way you can intercept also third party SQL and append your predicates.
This can work for all DML statements, including UPDATE, DELETE. It's a bit harder for INSERT, as you'd have to transform INSERT .. VALUES into INSERT .. SELECT, or throw an exception if someone wants to insert into the wrong organization_id.
Using a ExecuteListener in jOOQ
A bit more hackish than the above VisitListener approach, but generally easier to get right, just regex-replace the WHERE clause of all your statements by WHERE organization_id = ... AND in an ExecuteListener.
To play it safe, you could reject all queries without a WHERE clause, or do some additional trickery to add the WHERE clause at the right place in case there isn't already one.
Using jOOQ's equivalent of Hibernate's #Filter
jOOQ's equivalent of Hibernate's #Filter is the Table.where(Condition) clause. It's not an exact equivalent, you'd have to prevent direct access to T in your code base and make sure users access T only via a method that replaces T by T.where(defaultFilters(myType, T)) instead.
This approach currently loses type safety of the T table, see: https://github.com/jOOQ/jOOQ/issues/8012

jOOQ Subquery in Order By

I'm using a subquery in order by like this on MySQL 8 database:
select * from series
order by (select max(competition.competition_date) from competition
where competition.series_id = series.id) desc
But I didn't find a way to do that with jOOQ.
I tried the following query but this does not compile:
dsl
.selectFrom(SERIES)
.orderBy(dsl.select(DSL.max(COMPETITION.COMPETITION_DATE))
.from(COMPETITION).where(COMPETITION.SERIES_ID.eq(SERIES.ID)).desc())
.fetch()
Are subqueries not supported in order by?
Select<R> extends Field<R>
There's a pending feature request #4828 to let Select<R> extend Field<R>. This seems tempting because jOOQ already supports nested records to some extent for those dialects that support it.
But I have some doubts whether this is really a good idea in this case, because no database I'm aware of (i.e. where I tried this) supports scalar subqueries that project more than one column. It's possible to use such subqueries in row value expression predicates, e.g.
(a, b) IN (SELECT x, y FROM t)
But that's a different story, because it's limited to predicates, and not arbitrary column expressions. And it is already supported in jOOQ, via the various DSL.row() overloads, e.g.
row(A, B).in(select(T.X, T.Y).from(T))
Select<Record1<T>> extends Field<T>
This is definitely desireable, because a SELECT statement that projects only one column of type T really is a Field<T> in SQL, i.e. a scalar subquery. But letting Select<Record1<T>> extend Field<T> is not possible in Java. There is no way to express this using Java's generics. If we wanted to do this, we'd have to "overload" the Select type itself and create
Select1<T1> extends Select<Record1<T1>>
Select2<T1, T2> extends Select<Record2<T1, T2>>
etc.
In that case, Select1<T1> could be a special case, extending Field<T1>, and the other ones would not participate in such a type hierarchy. But in order to achieve this, we'd have to duplicate the entire Select DSL API per projection degree, i.e. copy it 22 times, which is probably not worth it. There are already 67 Select.*Step types in the jOOQ API, as of jOOQ 3.13. This makes it difficult to justify the enhancement even only for scalar subqueries, i.e. for Select1.
Using DSL.field(Select<Record1<T>>) and related API
You've already found the right answer. While Select<Record1<T>> cannot extend Field<T>, we can accept Select<? extends Record1<T>> in plenty of API, as an overload to the usual T|Field<T> overloads. This has been done occasionally, and might be done more thoroughly throughout the API: https://github.com/jOOQ/jOOQ/issues/7240.
It wouldn't help you, because you want to call .desc() on a column expression (the Select), rather than wrap pass it to a method, so we're back at Java's limitation mentioned before.
Kotlin and other languages
If you're using Kotlin or other languages that have some way of providing "extension functions", however, you could use this approach:
inline fun <T> Select<Record1<T>>.desc(): SortField<T> {
return DSL.field(this).desc();
}
jOOQ might provide these out of the box in the future: https://github.com/jOOQ/jOOQ/issues/6256
Turning the subquery into a Field works:
dsl.selectFrom(SERIES)
.orderBy(DSL.field(dsl.select(DSL.max(COMPETITION.COMPETITION_DATE)).from(COMPETITION)
.where(COMPETITION.SERIES_ID.eq(SERIES.ID))).desc())
.fetch()

Hibernate: Avoid using column names as strings

I would like to avoid having column names as strings in the code. Is there any other way to accomplish this?:
String query = "SELECT c.foo1.columnA, c.foo1.foo2.columnB FROM Table c";
session.createQuery(query).list();
I'm able to iterate over a column as string like c.foo1.foo2.columnB by splitting and getting the ClassMetadata, the property Type and other Hibernate functions until I reach the last element. However, I can't think a way to get a column string from Java beans, iterating through properties too.
Not sure what is the intention. Couple of thoughts
If you are worried about possibility of property names being wrong, current day IDEs does a good job by validating the property names in JPA queries
Object reflection can give you the property names. But not necessarily all properties are mapped to columns. You can look at this and use it along with bean property names via reflection.
Hope that helps.
There is no way to achieve what you are looking for. But, if your concern is correctness of these queries and worry that the problem will not be known until the execution hits this, you could use NamedQuery
#Entity
#NamedQuery(
name="findAllEmployeesByFirstName",
queryString="SELECT OBJECT(emp) FROM Employee emp WHERE emp.firstName = 'John'"
)
public class Employee implements Serializable {
...
}
Usage
List employees = em.createNamedQuery("findAllEmployeesByFirstName").getResultList();
The benefit is that queries defined in NamedQuery annotations are compiled to actual SQL at start up time. So incorrect field references(typo etc) will cause a start up error and the application will not start.
Another option will be as mentioned in the other answer to trust in a good IDE to refactor all occurrences properly when you rename fields (Idea does a great job at this, so would any other IDE)
EDIT: I do not think there is any performance degradation with named queries. Rather it may appear to be faster as compiled queries are cached(very subjective)
Finally, its better to use the actual query as-is as mentioned in comments. It is far more readable and debug in its context. If you are concerned about correctness, unit-test the heck out of it and be confident.

Class for JPA criteria entity with _ appended to className doesn't exist

I'm trying to convert a simple Play/JPA query to use the criteria API. Below isn't even the query I'm trying to convert; this one's even simpler -- just trying to get something to succeed to begin with.
All the examples I've been finding online expect you to be able to use a class that has _ appended to the class name, much like what I've seen hibernate queries do to table name aliases in the generated SQL. However, I can't get my code to compile this way since there is no class: ExtendedHaulTrain_ (there is however ExtendedHaulTrain)
Is there some kind of annotation I need to add to the ExtendedHaulTrain class? Perhaps I have not been reading deeply enough but the examples I've found so far don't address the issue of the class with the underbar appended.
Here's my code that fails to compile on the last line, specifically on ExtendedHaulTrain_
Query query = JPA.em().createQuery("select DISTINCT(x.trnType) from ExtendedHaulTrain x");
List<String> trainTypes = query.getResultList();
//as criteria query
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery<ExtendedHaulTrain> q = cb.createQuery(ExtendedHaulTrain.class);
Root<ExtendedHaulTrain> xhtRoot = q.from(ExtendedHaulTrain.class);
q.select(xhtRoot.get(ExtendedHaulTrain_.trnType)).distinct(true);
Instead of the MetaModel classes(they end with '_') you can always use the attribute name in form of a string as refrence.
q.select(xhtRoot.get("trynType")).distinct(true);
As noted in my comment there is a notion of a meta-model class I'd rather avoid. So below is how I converted my existing query to use the criteria API. Again, this is just to get a success under my belt; I'm probably not going to replace this query. Rather I have another more complex query, for which I intend to use the Criteria API; this was just to get some familiarity with the Criteria API -- there will probably be more questions to follow!
/*
Query query = JPA.em().createQuery("select DISTINCT(x.trnType) from ExtendedHaulTrain x");
List<String> trainTypes = query.getResultList();
*/
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(ExtendedHaulTrain.class);
Root root = cq.from(ExtendedHaulTrain.class);
cq.select(root.get("trnType")).distinct(true);
List<String> trainTypes = JPA.em().createQuery(cq).getResultList();
I understand that you do not like these meta-models but this is actually a very useful thing, which keeps your code on the safe side of type-safety (believe me, once you begin to write more queries, you will see the advantage). And the advantage is: you can generate them automatically with the so called meta-model generators (which are annotation processing tools). Hibernate has for example something one generator. In Eclipse it is very easy to generate them. Also in Maven it is easy. I recommend to use them.
UPDATE
Type Safety means actually beside not having to write xhtRoot.get("trynType") also that you work with correct join types. Do not forget, that compared to NamedQueries, CriteriaQueries are not checked on deployment. This means, if you remove or use the wrong type in the generic part of a join result (WrongOwner below)
Join<WrongOwner, Address> address = cq.join(Pet_.owners).join(Owner_.addresses);
you will know that on compile time.

Inject attribute into JPQL SELECT clause

Let's depict the following use case: I have a JPQL Query which on the fly creates data objects using the new keyword. In the SELECT clause I would like to inject an attribute which is not known to the database but to the layer which queries it.
This could look like
EntityManager em; // Got it from somewhere
boolean editable = false; // Value might change, e.g. depending on current date
Query q = em.createQuery("SELECT new foo.bar.MyDTO(o, :editable) FROM MyObject o")
.setParameter("editable", editable);
List<MyDTO> results = (List<MyDTO>) q.getResultList();
Any ideas how this kind of attribute or parameter injection into the SELECT clause might work in JPQL? Both JPA and JPA 2.0 solutions are applicable.
Edit: Performance does not play a key role, but clarity and cleanness of code.
Have you measured a performance problem when simply iterating over the list of results and call a setter on each of the elements. I would guess that compared to
the time it takes to execute the query over the database (inter-process call, network communication)
the time it takes to transform each row into a MyObject instance using reflection
the time it takes to transform each MyObject instance into a MyDTO using reflection
your loop will be very fast.
If you're so concerned about performance, you should construct your MyDTO instances manually from the returned MyObject instances instead of relying on Hibernate and reflection to do it.
Keep is simple, safe, readable and maintainable first. Then, if you have a performance problem, measure to detect where it comes from. Then and only then, optimize.
It will not work without possible vendor extensions, because according specification:
4.6.4 Input Parameters
...
Input parameters can only be used in the
WHERE clause or HAVING clause of a query.

Categories

Resources