JPA query with two left join - java

I have three classes: A.java, B.java and C.java. A and B extend C. How do I write a JPA query to works correctly? I tried
Query query = em.createQuery("select c from C c left join A a left join B b where (c.id = a.id or c.id = b.id)");

The short answer is that you don't. In JPA:
The default strategy, InheritanceType.SINGLE_TABLE, is used if the #Inheritance annotation is not specified on the root class of the entity hierarchy
When you select everything from table C, the JPA provider will instantiate classes based on the discriminator column, so
List<C> rl = em.createQuery("select c from C c", C.class).getResultList();
System.out.println(rl);
will give you:
Hibernate: select c0_.id as id2_0_, c0_.DTYPE as DTYPE1_0_ from C c0
[model.C#2cac4385, model.A#6731787b, model.B#16f7b4af]
If you need to you can use the instanceof operator in Java to determine the result types. If you want a specific subclass, then just query it:
List<A> rl = em.createQuery("select a from A a", A.class).getResultList();
System.out.println(rl);
which will give you:
Hibernate: select a0_.id as id2_0_ from C a0_ where a0_.DTYPE='A'
[model.A#3fc9dfc5]
Reference: Entity Inheritance

Related

problems with OneToMany including a filter clause in spring jpa

I currently get unexpected results in my MYSQL8/H2 test-case when using on a #OneToMany relationship in spring jpa. I want to filter in a list of TKBColumn-tables inside my TKBData table using JPQL. I expect to get one TKBData-table with the filtered TKBColumn but I always get the TKBData-table with ALL TKBColumn (unfiltered). When I using a SQL command it works!
I got no Idea whats the problem here, why it always give me the TKBData-table with always ALL TKBColumn-tables inside.
Native Query (This works):
SELECT d.id,c.name FROM TKBDATA d LEFT JOIN TKBDATA_TKBCOLUMN dc ON d.ID = dc.TKBDATA_ID LEFT JOIN TKBCOLUMN c ON c.ID = dc.COLUMNS_ID WHERE c.name = 'column1';
Output
ID NAME
7b6ec910-3e53-40a3-9221-ee60e75c8d67 column1
JPQL Query (Not works):
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
Output:
id: e892bc28-c35f-4fc8-9b09-387f97a758d8, name:column1
id: 069cc76b-3487-4ad8-a4ae-6568694e2287, name:column2
Table 'TKBData'
public class TKBData {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
private Set<TKBColumn> columns = Sets.newHashSet();
...
}
Table 'TKBColumn'
public class TKBColumn {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
}
Spring Data Repository
#Service
public interface KBDataRepository extends CrudRepository<TKBData, String>, KBDataCustomRepository {
#Query("select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
}
Spring JPA Generated H2 Tables (relevant)
CREATE CACHED TABLE "PUBLIC"."TKBCOLUMN"(
"ID" VARCHAR(255) NOT NULL,
"NAME" VARCHAR(255),
...
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA_TKBCOLUMN"(
"TKBDATA_ID" VARCHAR(255) NOT NULL,
"COLUMNS_ID" VARCHAR(255) NOT NULL
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA"(
"ID" VARCHAR(255) NOT NULL,
...
)
Relevant Content of tables which are generated at the start of the test class
Table: TKBDATA
ID
726004cf-5cab-4b1d-bb3f-466ba22622e9
Table: TKBDATA_TKBCOLUMN
TKBDATA_ID COLUMNS_ID
726004cf-5cab-4b1d-bb3f-466ba22622e9 7b4e4ea8-4ff9-4668-8882-67ff93b595ca
726004cf-5cab-4b1d-bb3f-466ba22622e9 d670e813-0466-48a8-be54-ee992cf28462
Table: TKBCOLUMN
ID DATAORDER NAME OWNERID
d670e813-0466-48a8-be54-ee992cf28462 0 column1 16e01046-9a84-4651-98d8-4e3e358212eb
7b4e4ea8-4ff9-4668-8882-67ff93b595ca 1 column2 16e01046-9a84-4651-98d8-4e3e358212eb
For more informations you can find the github repository here: https://github.com/fo0/ScrumTool
Test class: https://github.com/fo0/ScrumTool/blob/master/ScrumTool/src/test/java/com/fo0/vaadin/scrumtool/test/data/TKBDataColumnFilterTest.java
Edit:
The solution for this was to use a native query, because of the design of JPA and how it works with objects, thats why my use-case has exactly this problem.
Meaning of select d from TKBData d JOIN d.columns c WHERE c.name = column1 is
Find a TKBData object where it has an associated column object for which name is column1
Once its decided which TKBData has at least one column object for which name is column1, then it will return all its associated column objects which you don't have control over in JPA. ( see My answer to another question ). Alternative is to write native sql and return custom non entity objects
For example, you have TKBDATA_1 with column1 and column2 associated, you also have TKBDATA_2 with column3 associated.
When you run your query, it will ignore TKBDATA_2 and decides to return TKBDATA_1 as it has atleast one column object with name= column2. But after that you don't have control over which associated column objects to return for TKBDATA_1 and JPA will return all associated column objects
If you are not sure of the reason, read about hibernate session.How it provides unique presentation of any associated entry in memory. It is the foundation for its dirty checking and repeatable read
Update your #OneToMany as follows
#OneToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
#JoinTable(name = "TKBDATA_TKBCOLUMN",
joinColumns = #JoinColumn(name = "TKBDATA_ID"),
inverseJoinColumns = #JoinColumn(name = "COLUMNS_ID"))
private Set<TKBColumn> columns = Sets.newHashSet();
When it comes to JPA query language, I would like to think in terms of query a collection of in-memory objects.
So now try to describe the meaning of the following two queries in terms of objects.
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
vs
select d from TKBData d JOIN d.columns c WHERE c.name = :name
Don't forget unlike in sql where you are select any columns here you have said you want to select TKBData objects and restricting which TKBData objects to return.
So to achieve the same result as of your native sql, use the second JPA query
Note:
Even though you used a left join in your sql query, it is effectively an inner join sql query because you also applied a where condition to the most right table on that join.
Use the DISTINCT JPQL keyword
#Query("select distinct d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
Or use JPA method naming query
public TKBData findByColumnsName(String name);

With JPA Criteria, how would I Fetch a child entity of a Joined entity without Fetching the Joined entity?

On my project I'm using Groovy with Spring Data JPA's Specification's to construct Hibernate queries.
I can't provide my actual queries but to illustrate my problem let's say I have Building entities, and each Building has Floors and each Floor has both Rooms and Windows.
The behavior I'm attempting to simulate is something like this native SQL query:
SELECT b.*, r.*
FROM building b
INNER JOIN floor f ON b.id = f.building_id
INNER JOIN window w ON f.id = w.floor_id
LEFT OUTER JOIN room r ON f.id = r.floor_id
WHERE w.id = 1;
I have a specification similar to the below:
public class MySpec implements Specification<Building> {
#Override
public Predicate toPredicate(final Root<Building> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
final Join floorsJoin = root.join("floors");
final Join windowsJoin = floorsJoin.join("windows");
//I'd like to remove this line
final Fetch floorsFetch = root.fetch("floors"); // <---
floorsFetch.fetch("rooms", JoinType.LEFT);
cb.equal(windowsJoin.get("id"), 1L);
}
}
The line annotated above is my issue. If I leave it, the generated query looks something like this:
SELECT b.*, f2.*, r.*
FROM building b
INNER JOIN floor f ON b.id = f.building_id
INNER JOIN window w ON f.id = w.floor_id
INNER JOIN floor f2 ON b.id = f2.building_id
LEFT OUTER JOIN room r ON f2.id = r.floor_id
WHERE w.id = 1;
(notice the duplicate INNER JOIN of floor and the unneeded f2.* data)
If I remove it, and use the floorsJoin instead to fetch rooms, I get the following Hibernate error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
The unneeded f2.* data would be OK except I can't replace the above floorsJoin with the floorsFetch because I need to join with the windows table (without fetching windows) and the Fetch class doesn't have a .join method.
I'm having a difficult time figuring out how I would accomplish what I need while still generating a single query; surely I must be missing something simple.
Any thoughts or advice you could provide would be much appreciated.
Thanks a lot,
B.J.
Well it's not that simple with the JPA Criteria API. With Hibernate you could simply cast the Fetch to a Join I guess but that's not going to help you that much. I am not sure how you use the specification in this case, but if you could write the query as a whole it could look like the following
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Building> root = cq.from(Building.class);
final Join floorsJoin = root.join("floors");
final Join windowsJoin = floorsJoin.join("windows");
final Join roomsJoin = floorsJoin.join("rooms", JoinType.LEFT);
cb.equal(windowsJoin.get("id"), 1L);
cq.multiselect(
root,
roomsJoin
);

Invalid alias when joining two JPA entities via JPA 2.0 CriteriaBuilder

I have two JPA entities
class A{
#OneToMany
Lis<B> entitiesB;
#Column("STATUS")
String status;// will sort based on this column
}
and
class B{
#ManyToOne
A entityA;
#Column("PROPERTY_ONE")
String propertyOne;
....
#Column("PROPERTY_M")
String propertyM;
....
}
I need to left join A with B and then perform filtering on columns from B. I have the following criteria:
Join<A, B>root=criteriaBuilder
.createQuery(A.class)
.from(A.class)
.join("entitiesB");
CriteriaQuery<A> query = criteriaBuilder.createQuery(A.class);
query.select(query.from(A.class).join("entitiesB"))
.distinct(true)
.where(formWhereClause(filters))
.orderBy(formOrderByClause());
How do I form the filter by the status property from A entity
criteriaBuilder.notEqual(root.get("A").get("status"), "SOME_STATUS_VALUE");
It has generated me the following SQL:
select distinct generatedAlias0 from A as generatedAlias1
inner join generatedAlias1.entitiesB as generatedAlias0
where ( generatedAlias2.A.status<>:param0 ) and ( generatedAlias2.propertyOne like :param1 )
order by generatedAlias2.propertyM desc
I got the following exception:
'org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.ast.QuerySyntaxException:
Invalid path generatedAlias2.A.status '
How can I fix it? I am using Hibernate 4.3.5 as the persistence provider.
CriteriaQuery query = criteriaBuilder.createQuery(A.class);
means that you want to return instances of type A. Therefore, your select clause must specify query root instead of an instance of Join as you did:
Define the root of the query because the join method can only be applied either to an instance of Root or Join types:
Root<A> root = query.from(A.class);
Define Join (I need to left join A with B):
Join<A, B> b = root.join("entitiesB", JoinType.LEFT);
Define the SELECTclause:
query.select(root)
.distinct(true)
.where(formWhereClause(filters))
.orderBy(formOrderByClause());
How do I form the filter by the status property from A entity
Form it as follows:
criteriaBuilder.notEqual(root.get("status"), "SOME_STATUS_VALUE");
and if you want to use attributes of B as a filter define it, for example, as:
criteriaBuilder.equal(b.get("propertyOne"), "SOME_VALUE");

JPA left/right join query

I have this structure from entities Aa, Bb, Cc:
Aa has a list of Bb
Bb has a list of Cc
-
public class Aa{
#OneToMany
List<Bb> listBb;
}
public class Bb{
#OneToMany
List<Bb> listCc;
}
I would like to create a JPA Criteria API query to pool Aa by an id of C:
public A getAaByCcId(long id) {...}
In native sql I would have try left join (twice). How do I do this using JPA?
You also do it with joins in JPQL:
select a from Aa a
inner join a.listBb b
inner join b.listCc c
where c.id = :cId
Note that inner joins can be used here, since you have a restriction on c.id = :cId, which can only be true if B and C exist. But you could use left joins as well.
EDIT:
Using a Criteria query, it would look like the following (not tested):
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Aa> criteria = builder.createQuery(Aa.class);
Root<Aa> a = criteria.from(Aa.class);
CollectionJoin<Aa, Bb> b = a.join(Aa_.listBb);
CollectionJoin<Bb, Cc> c = b.join(Bb_.listCc);
criteria.where(builder.equal(c.get(Cc_.id), cId));
return em.createQuery(criteria).getResultList();
Assuming that each entity has a reference to its parent entity, where Cc's reference to a Bb is called bb and Bb's reference to an Aa is called aa, then in JPQL, you can do:
select cc.bb.aa from Cc cc where cc.id = ?
The criteria version of this should be a simple translation.

Hibernate Query Criteria for mappings involving inheritance

Lets say that class 'X' is mapped to table 'X' class 'A' is mapped to Table 'A' and Class 'B is mapped to table 'B'.
Table X Structure:(X_ID, some other columns
Table A Structure:(A_Id,X_Id, some other columns)
Table B Structure:(A_Id, some other columns)...Table B also has A_Id
Class 'B' extends class 'A'. We have the mapping files for both of them as:
Class 'A' Parent Mapping file:
#Entity
#Table(name = 'A')
#Inheritance(stratergy=InheritanceType.Joined)
public abstract class A {
#Id #Clumns(name = "A_Id)
#GeneratedValue
protected Long aId;
-- some more A specific fields
}
Class 'B' Mapping file:
#Entity
#Table(name= 'B')
Public class B extends A{
---- B specific fields
}
Now, I have a SQL Query as below that I need to write using hibernate criteria API.
select * from X
INNER JOIN A
ON X.id = A.id
INNER JOIN B
ON A.id = B.id
where B.name = 'XYZ'
and B.Sex = 'M'
I have come up with:
Criteria c = session.createCriteria(x.class, "x");
.createAlias("x.a", "a")
.createAlias("a.b", "b")
.add(Restrictions.eq("b.sex", "M"))
.add(Restrictions.eq("b.name", "XYZ"));
But, if we check the mapping file, there is no direct reference of B in A. Hence hibernate throws out "B not related to A" entity.
Is there any way this inheritance can be mapped in query crteria
You shouldn't need to reference A at all in your criteria, or use any aliases.
Criteria c = session.createCriteria(B.class);
.add(Restrictions.eq("sex", "M"))
.add(Restrictions.eq("name", "XYZ"));
will give you the result you need.
Because of the InheritanceType.Joined, this will probably produce SQL that includes a join to the the A table (something close to the sql you show), but it isn't necessary to specify that join in the criteria.
The things that look like columns in the criteria are actually (reflective) references to fields in your Java objects. Hibernate figures out the columns to put in the sql from your annotations, and should the join to the A table if it's needed based on the inheritance annotation.
To be sure of this in your context, and to understand all this a bit better, I'd advise trying it and turning on logging of the generated sql as described in this answer to another SO hibernate question.
Try this way:
Criteria rootCrit = session.createCriteria(A.class);
rootCrit.createAlias("B", "B");
rootCrit.add(Restrictions.eq("B.sex", "M"));
rootCrit.add(Restrictions.eq("B.name", "XYZ"));

Categories

Resources