I have two entities, A and B. Entity A has the following annotations.
#OneToOne(mappedBy="entityA", optional=true)
public EntityB getEntityB() {
return b;
}
public void setEntityB(EntityB b) {
this.b = b;
}
Entity B has the following annotations
#OneToOne
public A getEntityA() {
return a;
}
public void setEntityA(EntityA a) {
this.a = a;
}
When I query the following, everything works fine.
String hql = "from EntityB eb where eb.entityA is null";
When I try to query with this, it doesn't work.
String hql = "from EntityA ea where ea.entityB is null";
If I follow this other StackOverflow answer, the query works. Why do I need to do a join to find out if the relationship is null?
In your query you are using HQL implicit join syntax. According to Hibernate docs 15.4. Forms of join syntax this form of join results in SQL inner join, which by its nature filters only those items where a match exists in the other table. Using explicit left join in hql will make your query more transparent. Turn on SQL query logging via persistence.xml or via your server's logging config to examine the actual SQL commands generated by hibernate.
Related
I am using hibernate-orm with spring-data-jpa. I have three entities A, B, C declared as follows:
#Entity
public class A {
#OneToMany(....)
private List<B> listOfB;
}
#Entity
public class B {
#ManyToOne(...)
private A a;
#OneToMany(...)
private List<C> listOfC;
}
#Entity
public class C {
#ManyToOne(...)
private B b;
}
My objective is to get A and fetch listOfB as well, with some condition on entity C without fetching it. The following JPQL is working fine.
SELECT a FROM A a
LEFT JOIN FETCH a.listOfB b
LEFT JOIN b.listOfC c
WHERE c.xyz = :xyz
When I tried using JPA Specification my Specification it looks like the following:
(rootA, query, builder) -> {
Fetch fetch = rootA.fetch(A_.listOfB, JoinType.LEFT);
ListJoin listJoin = ((ListJoin)fetch).join(B_.listOfC)
return builder.equal(listJoin.get(C_.xyz), xyz);
}
I am reusing the implicit join done by the fetch operation. This join is not working in specification. It's outputting the following JPQL.
SELECT a FROM A a
LEFT JOIN FETCH a.listOfB b
WHERE c.xyz = :xyz
Saying that, there is no c alias.
I have looked into Hibernate GitHub source code. I found out that, there is a class named QueryStructure.java responsible for generating JPQL query from the criteria object.
I found the function renderFetches which render the fetches.
#SuppressWarnings({ "unchecked" })
private void renderFetches(
StringBuilder jpaqlQuery,
RenderingContext renderingContext,
Collection<? extends Fetch> fetches) {
if ( fetches == null ) {
return;
}
for ( Fetch fetch : fetches ) {
( (FromImplementor) fetch ).prepareAlias( renderingContext );
jpaqlQuery.append( renderJoinType( fetch.getJoinType() ) )
.append( "fetch " )
.append( ( (FromImplementor) fetch ).renderTableExpression( renderingContext ) );
renderFetches( jpaqlQuery, renderingContext, fetch.getFetches() );
}
}
Similarly there is a function renderJoins responsible for all the joins.
These two are recursive functions rendering the criteria object tree.
It's clear that all the joins inside the fetches are ignored. There is no call to function renderJoins inside from renderFetches which causes the generated query to be incomplete.
Is there any in depth reason why we are not joining inside from a fetch? If yes then how could I reuse the existing implicit joins done by fetch?
This issue is re-generated using hibernate test case template.
Regenerated Hibernate test case template
Specific test case file
As i mentioned in my question, there is no renderJoins call from inside of renderFetches, adding following at the end of the renderFetches solves the issue.
if (fetch instanceof From) {
From from = (From) fetch;
renderJoins(jpaqlQuery, renderingContext, from.getJoins());
}
I have given a PR HHH-14916 in the hibernate-orm.
PR is merged will be available in the next 5.6.x release.
I need to execute several different queries and I want to use the same POJO to get the results. What I get with those queries are combinations of same columns, can I reuse the same sqlResultSetMapping?
I am working with JPA-2.1 and Java 8
My queries are something like:
select tableA.a, tableB.b, tableA.c
from tableA
inner join tableB
select tableA.a, tableB.b
from tableA
inner join tableB
select tableB.b, tableA.c
from tableA
inner join tableB
My POJO is something like:
public class Result {
String a;
String b;
String c;
}
and finally, my SqlResultSetMapping is:
#SqlResultSetMapping(
name="GeneralResult",
classes = {
#ConstructorResult(
targetClass = Result.class,
columns = {
#ColumnResult(name="a", type=String.class), #ColumnResult(name="b", type=String.class), #ColumnResult(name="c", type=String.class)
}
)
}
)
When I execute the first query, with fields a,b, the call works fine. The problem is when I execute one of the other two queries.
Can I use the same SqlResultSetMapping for those queries?
I tried with several ConstructorResult but the problem is all fields are String (VARCHAR in DB).
Thanks in advance.
I am using Spring Boot, Groovy and JPA/Hibernate to migrate an old application. The only restriction is that the database model must not be changed and I found myself with a weird situation in which a OneOnOne relationship:
Please look at the following model setup:
#Entity
#Table(name='table1')
class Table1 {
#Id
Table1Id id
#Column(name='sequence_num')
Integer seqNum
#Column(name='item_source')
String itemSource
#Column(name='source_type')
String sourceType
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name='key_field_2', insertable=false, updatable=false)
#NotFound(action=NotFoundAction.IGNORE)
// #Fetch(FetchMode.JOIN)
Table2 table2
}
#Embeddable
class Table1Id implements Serializable {
#Column(name='key_field_1')
String key1
#Column(name='key_field_2')
String key2
}
#Entity
#Table(name='table2')
class Table2 {
#Id
#Column(name='key_id')
String keyId
#Column(name='field1')
String field1
#Column(name='field2')
String field2
#Column(name='field3')
String field3
}
My Spock test looks as follows:
def "Try out the JOIN select with Criteria API"() {
given:
CriteriaBuilder cb = entityManager.getCriteriaBuilder()
CriteriaQuery<Object[]> cQuery = cb.createQuery(Object[].class)
Root<Table1> t1 = cQuery.from(Table1.class)
Path<Table2> t2 = t1.get('table2')
Join<Table1, Table2> lanyonLeftJoin = t1.join('table2', JoinType.INNER)
Predicate where = cb.equal(t1.get('itemSource'), 'ABC')
cQuery.multiselect(t1, t2)
cQuery.where(where)
when:
List<Object[]> result = entityManager.createQuery(cQuery).getResultList()
then:
result.each{ aRow ->
println "${aRow[0]}, ${aRow[1]}"
}
}
This configuration successfully generates an INNER JOIN between Table1 and Table2, NOTE that even the constant on the "where" clause is correctly interpreted.
However for some strange reason Table2 gets re-queried for every row returned in the first query.
The output that I see is:
Hibernate:
select
table10_.key_field_1 as key_field_11_3_0_,
table10_.key_field_2 as key_field_22_3_0_,
table21_.key_id as key_id1_5_1_,
table10_.item_source as item_source3_3_0_,
table10_.sequence_num as sequence_num4_3_0_,
table10_.source_type as source_type5_3_0_,
table21_.field2 as field23_5_1_,
table21_.field3 as field34_5_1_,
table21_.field1 as field15_5_1_
from
table1 table10_
inner join
table2 table21_
on table10_.key_field_2=table21_.key_id
where
table10_.item_source=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
// 500+ more of these
As we can see the first query successfully returns all rows from both tables and it is actually the exact query I am looking for. However there is all those unnecessary extra queries being performed.
Is there any reason why JPA would do such thing and is there a way to prevent it??
I have the impression I am missing something very obvious here.
Thanks in advance for your help
Update 1
If I replace
cQuery.multiselect(t1, t2)
for
cQuery.multiselect(t1.get('id').get('key1'), t1.get('id').get('key2'),
t1.get('fieldX'), t1.get('fieldY'), t1.get('fieldZ'),
t2.get('fieldA'), t2.get('fieldB'), t2.get('fieldC') ...)
It generates the exact same inner join query and DOES NOT re-queries Table2 again.
In other words, looks like (at least for this case) I need to explicitly list all the fields from both tables. Not a nice workaround as it can get very ugly very quickly for tables with a lot of fields. I wonder if there is a way to retrieve all the #Column annotated fields/getters without resourcing to a bunch of reflection stuff?
I think I've got it!
#JoinFormula:
Primary Key in Table2 is INT and the field in Table1 that is used as FK is String (I completely missed that! duh!). So, the solution for that was to apply a #JoinFormula instead of a #JoinColumn in the form of:
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumnsOrFormulas([
#JoinColumnOrFormula(formula=#JoinFormula(value='CAST(key_field_2 AS INT)'))
])
#NotFound(action=NotFoundAction.IGNORE)
Table2 table2
This strangely returns a List<Object[]> each item of the List contains an array of 2 elements: one instance of Table1 and one instance of Table2.
Join Fetch:
Following your suggestion I added "join fetch" to the query, so it looks like:
select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
This causes Hibernate to correctly return a List<Table1>
Either with the #JoinFormula alone or the #JoinFormula + "join fetch" hibernate stopped generating the n+1 queries.
Debugging Hibernate code I've found that it correctly retrieves and stores in Session both entities the first time it queries the DB with the join query, however the difference between PK and FK data types causes Hibernate to re-query the DB again, once per each row retrieved in the first query.
I have the following 2 classes using JTA transaction type with openjpa & a derby embedded db. What I want is to get the parent Organisation & the requested WSpace in one query when I only have the WSpace id. I am quite new to JPA so am learning as I go and have been struggling with 'q2' query. I have been using queries 'q0' & 'q1' to debug and check the items do exist in the db. 'q0' returns 1 object as does 'q1', whereas 'q2' returns 0 objects
I have tried a variety of entity setups and different queries but nothing has worked yet.
Orignally the WSpace class did not have an Organisation field as it didn't seem necessary for persisting or selecting, but I added it (along with the mappedby parameter) incase it was needed for the query to work, but nothing has changed.
back to the original question how can I get this to work so it returns the parent object with the single child being requested
SELECT o FROM Organisation o JOIN FETCH o.spaces w WHERE w.id = :id
Here are my classes
#Entity
public class Organisation implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#OneToMany( mappedBy = "organisation",
cascade = { CascadeType.PERSIST, CascadeType.MERGE } )
private List<WSpace> spaces;
//getters/setter below
}
And
#Entity
public class WSpace implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#ManyToOne
private Organisation organisation;
#OneToMany
private List<Application> apps;
//getters/setter below
}
class DAO
{
...
public void foo( Integer id )
{
....
String q0 = "SELECT o FROM Organisation o WHERE o.id = 49068";
List<Organisation> res0 = em.createQuery( q0, Organisation.class ).getResultList();
String q1 = "SELECT w FROM WSpace w WHERE w.id = " + id;
List<WSpace> res1 = em.createQuery( q1, WSpace.class ).getResultList();
String q2 = "SELECT o FROM Organisation o "
+ "JOIN FETCH o.spaces w WHERE w.id = " + id;
List<Organisation> res2 = em.createQuery( q2, Organisation.class ).getResultList();
...
}
}
Have you tried to look in the logs for output of your q2 query?
I am learning JPA too and was dealing with Criteria and QL queries quite recently.
So after having pretty same problems with joins, I started checking logs and it was pretty clear, what the issues were, since logs showed up translated to SQL queries.
Another thing to look, how are you generating your Entities? I used Netbeans generating it for me. Also, many to many relations mignt have helper class generated too, I saw it in one of the projects.
Hope it helps..
The query you're looking for is probably this:
SELECT w FROM WSpace w LEFT JOIN FETCH w.organisation where w.id=:id
with query.setParameter("id", id); to bind the id parameter to the query. This effectively tells the persistence provider to fetch the WSpace.organisation relation while querying for WSpace entities in the same query. Using the LEFT [OUTER] keword (OUTER being optional) in front of the JOIN FETCH keywords tells your persistence provider to select WSpace objects even if there are no matching Organisation records for your WSpace.organisation relation.
I make a query:
String query = "SELECT DISTINCT a FROM A a FETCH ALL PROPERTIES " +
"JOIN a.Bs AS b " +
"JOIN b.Cs AS c WHERE c = :c";
Query q = DAO.getSession().createQuery(query);
q.setParameter("c", c);
return q.list();
Even though I've said FETCH ALL PROPERTIES on a, when I access all the collections that A has, they still need to be loaded, thus aren't eagerly loaded. They have been defined as lazy loading, and that is the default behaviour I want, but this is the exception: I would like them loaded right now. I've tried swapping JOIN for LEFT OUTER JOIN to provoke Hibernate into loading them, and I've tried setting q.setFetchMode("a", FetchMode.EAGER), but it doesn't exist for Query.
The list of As is quite long, and they have quite a few collections, so making this an n+1 query thing is very slow (about ten seconds, as opposed to doing it in a single query which would be sub-second speed). I'd far prefer one query and loading all that's necessary in that one go. Any suggestions on how I can do that?
PS, Little bonus question: If I replace the "JOIN b.Cs AS c WHERE c = :c";
line with "WHERE :c IN b.Cs";, I get an SQL exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
The double paranthesis it's referring to is "and ('151000000-0000' in (.))" where 151000000-0000 is the primary key of c. Any idea why I get this error when I do it this way compared to not getting it when I do it the with joining b.Cs in?
UPDATE, as requested, here is the way I use for mapping. B and C are very similarly designed:
#Entity
#Table(name = "tblA")
public class A {
#Id
String AId;
#Column(name = "shortName", length = 12, nullable = false)
String shortName;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<B> Bs;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<D> Ds;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<E> Es;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<F> Fs;
}
theA in B, D, E and F is defined like this:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "AId", nullable = true)
#ForeignKey(name="FK_KategoriID")
private A theA;
Cheers
Nik
fetch all properties is not what you want; it's used for telling Hibernate that you want it to fetch single-valued lazy-loaded properties. Details are here.
You need to specify join fetch in your query instead:
SELECT DISTINCT a FROM A a
LEFT JOIN FETCH a.Bs AS b
LEFT JOIN FETCH b.Cs AS c
WHERE c = :c
As far as bonus question goes, WHERE :c IN b.Cs is illegal syntax. Depending on how your C is mapped, you may want to look at elements() function instead.
FETCH ALL PROPERTIES only works for lazy properties (where a property is a String, Integer, ...) not one-to-may associations (i.e. collections)
see A Short Primer On Fetching Strategies