JOOQ: Dynamic join conditions - java

I would like to create conditions from this select in JOOQ. Because in my case I want to declare a dynamic query and check if TABLE_C.FIELDC contains "foo" only when I need...
Example:
create.select().from(TABLE_A).join(TABLE_B).onKey(Keys.FK_TABLEA_TABLEB)
.join(TABLE_C).onKey(Keys.FK_TABLEB_TABLEC)
.where(TABLE_C.FIELDC.containsIgnoreCase("foo");
to:
SelectFinalStep select = create.select().from(TABLEA);
if (isFooSearched) {
query.addCondition( <JOIN> and <CONTAINS> like first example)
}
How can I do this?

There are several ways to solve this:
Using implicit joins
In relatively simple cases, when the optional join follows a to-one relationship, you may be able to use an implicit join (if you're using the code generator):
create.select()
.from(TABLE_A)
.join(TABLE_B).onKey(Keys.FK_TABLEA_TABLEB)
.where(isFooSearched
? TABLE_B.tableC().FIELDC.containsIgnoreCase("foo")
: noCondition())
.fetch();
Using SEMI JOIN instead of INNER JOIN, which makes dynamic SQL much easier
create.select()
.from(TABLE_A)
.where(
isFooSearched
? TABLE_A.TABLE_B_ID.in(
select(TABLE_B.ID)
.from(TABLE_B)
.join(TABLE_C).onKey(FK_TABLEB_TABLEC)
.where(TABLE_C.FIELDC.containsIgnoreCase("foo"))
)
: trueCondition())
.fetch();
Note that a semi join is also more formally correct in this case than an inner join, as you will not get any duplicate rows on TABLE_A for any matches in to-many relationships (removing them with DISTINCT might be wrong and certainly is inefficient).
Side-note: Not all databases recognise semi-joins in EXISTS or IN syntax, and may thus not optimally run this statement, compared to a JOIN based solution.
Using INNER JOIN as you asked for
// I'm assuming DISTINCT is required here, as you
// - are not interested in TABLE_B and TABLE_C results (semi join semantics)
// - do not want any duplicate TABLE_A values
create.selectDistinct(TABLE_A.fields())
.from(
isFooSearched
? TABLE_A
.join(TABLE_B).onKey(FK_TABLEA_TABLEB)
.join(TABLE_C).onKey(FK_TABLEB_TABLEC)
)
: TABLE_A)
.where(
isFooSearched
? TABLE_C.FIELDC.containsIgnoreCase("foo")
: trueCondition())
.fetch();
I've mad a few assumptions here, including the fact that DISTINCT usage could be correct on your joined variant of the query, but it is hurting you (probably) on your "default" query variant, so perhaps, shoe horning this into a single dynamic query might be overkill.
Thus...
Using two different queries
For my taste, the two queries are simple enough to allow for some duplication and simply run two different queries depending on the flag:
if (isFooSearched)
create.select().from(TABLE_A) /* joins or semi joins here */ .fetch();
else
create.select().from(TABLE_A).fetch();
Side note
All solutions are assuming you have these static imports in your code:
import static org.jooq.impl.DSL.*;

Related

Hibernate criteria left our join

I have the following entities:
Machines {
id,
name
}
Favorites {
userId,
objectId,
objectType
}
Now I want to return list of machines ordered by favorites, name.
Favorites does not have any relation with Machines entities, its a generic entity which can hold various favoritable objects.
I got the sorting to work by the following raw sql. Is there any way to get this working using hibernate criterias. Basically ability to add alias for Favorites though Machines doesn't have any reference to it.
select m.* from Machines m left outer join Favorites f on m.id=f.objectId and f.userId =#userId order by f.userId desc, m.name asc
There are two ways to do this:
Use an SQL query or an SQL restriction for a criteria query.
Map the relation between Favorites and Machines as an Any type association.
Since you already have the query, executing it as a native SQL query through hibernate is the simplest solution:
sess
.createSQLQuery("SELECT ... ")
.addEntity(Machine.class)
.list();
The Any type association however, will probably benefit you in other situations where you want to query favoritables.
As said by #Maarten Winkels, you can use native SQL query that you have. You can do that but, if you want change your database then syntax may differs. So, it is recommended not to use native SQL query.
You cannot perform outer join using Hibernate criteria without any association between tables.
If you change your mind & want to add an association between these tables, then You can do something like below using criteria
List machines = session.createCriteria( Machines.class )
.createAlias("Machines", "m")
.createAlias("Favorites", "f", Criteria.LEFT_JOIN,
Restrictions.and(Restrictions.eq("m.id", "f.objectId"),
Restrictions.eq("f.userId", "#userId")))
.addOrder(Order.desc("f.userId"))
.addOrder(Order.asc("m.name"))
.list();
If for some reason you don't want to add any relationship between these tables, then you can use INNER JOIN or CROSS JOIN to get all machines with whatever criteria you want.
from Machines m, Favorites f where m.id = f.objectId and f.userId = #userId order by f.userId desc, m.name asc;
With inner joins there is one problem, if there is outer join relation in database then it doesn't work.
You can also write subqueries or separate queries & perform manual conditional checks between the results returned by those queries.
And one thing I want to ask you, What is the meaning of #userId in your SQL query ? I keep it as it is in my answer but, I didn't understand for what purpose # is there ?

SELECT e From Employee e -- why a redundant "e"?

Sample query:
SELECT e FROM Employee e WHERE SUBSTRING(e.name, 3) = 'Mac'
In this syntax, it seems intuitive to say SELECT e, that e is now declared or defined(?). However, isn't the second e: FROM Employee e redundant?
This is a throwback or similarity to SQL SELECT syntax?
The second e is a identification variable. It actually defines e by telling the JPQL parser that you are using e somewhere else in your query, and that it refers to the Employee entity. The first occurrence of e is where you usa that e.
So, it's not redundant. If you leave out the first one, the JPQL parser doesn't know what to select. If you leave it out the second time, you're selecting something that the JPQL parser doesn't know.
JPQL syntax is a little different with normal SQL syntax.According to your sample,first e is represent * of normal SQL. So it is not redundant. But you use JPA 2.x, using criteria query is more better than JPQL
I came to this question with the same concerns as you are. I noticed that when you select * the results comes in columns, and when you select e there's only one column with some kind of serialized entities as results. So I found a pretty good explanation about this at http://www.thejavageek.com/2014/03/17/jpa-select-clause/
SELECT e FROM Employee e
This is quite similar to SQL, the difference is:
This query does not return a set of records from columns, instead it
returns an entity.
This entity is aliased to e , it is called as
identification variable.
This identification variable is tied to
Employee type and it means result will be an entity of Employee
type.
IMO there is no need to use the second e in this case (yes it is redundant) if you have single query like this. It makes sense when you join two tables and if those two tables have common column names then you would select each column using that table alias 'e'
use "e." (which columns you have)
SELECT e.name, e.e FROM Employee e WHERE SUBSTRING(e.name, 3) = 'Mac'
DISCLAIMER: This only works with Hibernate as JPA implementation since this information corresponds to HQL.
In this simple case you don't have to use the e (which is nothing more than an alias). Since you are selecting the complete entity, you even don't have to write the select e part. So you can write:
FROM Employee WHERE SUBSTRING(name, 3) = 'Mac'
Explanation:
In the from part, you specify for which Entities you are looking for. The e behind the Employee is just an alias for Employee which you can use to address the whole object (the select part) or attributes from it. In simple queries you don't need it, but as soon you have a join in your query, it's always a good idea to use an alias.
The select part of the query is for selecting which attributes of an entity you want to get back. If you omit the ´selectpart or just specify the alias (e` in this case), JPA gives back the whole Entity. In SQL this does usually not work (at least for Oracle).
To answer your question in the comment: You can use the alias e in the select part of the query. But in Order to do so, you must teach JPA what this e refers to. And this is what the from Employee e part is doing.

Hibernate N+1 SELECTs with DTO objects

I have a problem with hibernate where it is doing N+1 SELECTS when using a DTO object.
For example, this JPQL query:
SELECT com.acme.MyDto(t) FROM Thing t
Where the constructor of MyDto is something like:
public MyDto(Thing t) {
...
}
Which results in a query of something like:
SELECT t.id FROM thing t WHERE condition
followed by all the single queries for each row, ie:
SELECT t.id, t.column1, t.column2 FROM thing t WHERE t.id = 1
SELECT t.id, t.column1, t.column2 FROM thing t WHERE t.id = 2
SELECT t.id, t.column1, t.column2 FROM thing t WHERE t.id = 3
...
However if the constructor doesn't accept the Entity, but instead each individual column, then hibernate behaves as you would expect, ie:
public MyDto(Integer id, String column1, String column2) {
...
}
Then the generated SQL looks like this:
SELECT t.id, t.column1, t.column2 FROM thing t WHERE condition
Aside from creating DTO constructors that takes every column, is there a way to coax hibernate to just select all the columns at once from the beginning?
The table that we're working with has 100+ columns spread across embeddables, so it's pretty annoying to maintain a huge constructor. The table is hugely normalised and has no joins.
Read your question wrong the first time... I don't recall using DTOs if they just take the whole entity and not just some specific columns, so I'm not sure why Hibernate behaves like that when you use a whole entity as a parameter in DTO constructor. Anyway, you could work around it by just gettin the actual Things via a query and then construct the DTOs in a loop, something along the lines of:
public List<ThingDTO> getThingDTOs( ... )
{
Query query = em().createQuery("FROM Thing t WHERE ...");
query.setParameter( ... );
List<Thing> things = query.getResultList();
List<ThingDTO> thingDTOs = new ArrayList(things.size());
for(Thing t : things)
{
thingDTOs.add(new ThingDTO(t));
}
return thingDTOs
}
It ain't pretty, but this way Hibernate should fetch all the needed rows in one go
As you have probably noticed already, the constructor expression approach has quite some downsides. If you need nested associations it going to get worse. The main problem with working with entity objects here is that you might still run into N + 1 queries problems. I wrote a blog post on that topic a while ago that justifies why I created Blaze-Persistence Entity Views, a library that allows to map DTOs as interfaces.

A set of questions on Hibernate quering

Please help me with these Hibernate querying issues.
Consider the following structure:
#Entity
class Manager {
#OneToMany
List<Project> projects;
}
0) there are 2 possible ways of dynamic fetching in HQL:
select m from Manager m join m.projects
from Manager m join fetch m.projects
In my setup second one always returns a result of cartesian product with wrong number of objects in a list, while the first one always returns correct number of entities in a list. But the sql queries look the same. Does this mean that "select" clause removes redundant objects from the list in-memory? In this case its strange to see an advice in a book to use select distinct ... to get rid of redundant entities, while "select" does the job. If this is a wrong assumption than why these 2 queries return different results?
If I utilize dynamic fetching by one of the 2 methods above I see a classic n+1 select problem output in my hibernate SQL log. Indeed, FetchMode annotations (subselect or join) do not have power while fetching dynamically. Do I really can't solve the n+1 problem in this particular case?
Looks like Hibernate Criteria API does not support generics. Am I right? Looks like I have to use JPA Criteria API instead?
Is it possible to write HQL query with an entity name parameter inside? For example "from :myEntityParam p where p.id=1" and call setParameter("myEntityParam", MyClass.class) after this. Actually what I want is generic HQL query to replace multiple non-generic dao's by one generic one.
0) I always use a select clause, because it allows telling what you want to select, and is mandatory in JPQL anyway. If you want to select the managers with their projects, use
select distinct m from Manager m left join fetch m.projects
If you don't use the distinct keyword, the list will contain n instances of each manager (n being the number of projects of the manager): Hibernate returns as many elements as there are rows in the result set.
1) If you want to avoid the n + 1 problem, fetch the other association in the same query:
select distinct m from Manager m
left join fetch m.projects
left join fetch m.boss
You may also configure batch fetching to load 10 bosses (for example) at a time when the first boss is accessed. Search for "batch fetching" in the reference doc.
2) The whole Hibernate API is not generified. It's been made on JDK 1.4, before generics. That doesn't mean it isn't useful.
3) No. HQL query parameters are, in the end, prepared statement parameters. You must use String concatenation to do this.

Avoid N+1 select with native sqlQuery?

Here's what I have :
Entity A -> oneToMany -> Entity B -> manyToOne -> Entity C
And because I have to do an inner join without foreign keys between A and another entity X, I have to use createSqlQuery and not createQuery. (obviously I can't change the database)
So, all I was able to do is a nice 2N+1 select. (with fetch=EAGER or by hand, it's the same).
Does someone have any idea?
EDIT: with a #BatchSize I reduced the number of selects from A to B. I have now a N+2 select.
EDIT 2: I can't use the inner join (with the comma) because the database is an old DB2, and it crashes.
To avoid N+1, you can use the following code in your map field
#Fetch(FetchMode.JOIN)
Hope this will help.
Sorry for the vague answer, I really never experienced this. I would try to approach this problem using ResultTransformers:
http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/transform/ResultTransformer.html
Unfortunately, there's little documentation about it, so, your best option is to look at the test suite and see how it's used.
You can use something like this, but I'm not sure how would it work with complex query:
s.createSQLQuery(
"SELECT {a.*}, {b.*}, {c.*} " +
"FROM X x JOIN A a ON ... JOIN B b ON ... JOIN C c ON ...")
.addEntity(A.class, "a")
.addJoin(B.class, "a.b")
.addJoin(C.class, "a.b.c")
See also:
18.1.3. Handling associations and collections

Categories