I want to write this query using Hibernate Criteria language. I am pretty new to Hibernate and not able to convert this query into Criteria form. I referred lots of answers available on SO but in my case I am using inner join on different columns rather than primary key/ foreign key column. I referred this but still can't make it right.
select TableA.columnA1, TableA.columnA2, TableA.columnA3, TableB.columnB1, TableC.columnC2 from TableA inner join TableB
on
cast(TableA.columnA3 as Integer) = TableB.columnB2
inner join
TableC
on
TableB.columnB3 = TableC.columnC1
To handle the joining logic, you are going to want to use from for each of the tables and include all of your conditions from the on-clauses in the where predicate.
Here is a JPA example that handles a parent-child relationship without having a foreign-key relationship:
EntityManager em = getDb().getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Child> criteria = cb.createQuery(Child.class);
Root<Parent> p = criteria.from(Parent.class);
Root<Child> c = criteria.from(Child.class);
Predicate condition = cb.and(
cb.equal(c.get(Child_.parentId), p.get(Parent_.id)),
...
);
criteria.where(condition);
criteria.select(c);
criteria.orderBy(cb.asc(c.get(Child_.createDate)));
TypedQuery<Child> q = em.createQuery(criteria).setMaxResults(limit);
A JPA example is provided here, because the Hibernate criteria API is deprecated in favor of the JPA criteria API (see Legacy Hibernate Criteria Queries).
Related
I am using JPA 2.2.0 in my project. I have below requirement to write a hibernate query using "CriteriaBuilder". Below is sample code snippet
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> criteriaQuery = criteriaBuilder.createQuery(A.class);
Root<A> aRoot= criteriaQuery.from(A.class);
Join<A,B> abJoin= aRoot.join("aliasB", JoinType.LEFT);
....
I want to join two tables A and B. However I want to join A with B with 2 columns c1 and c2. Is it possible in JPA 2.2.0? If so, how we can do this?
for Eg.,
I need below query,
SELECT * FROM A LEFT JOIN
B ON A.c1 = B.c1 AND **B.c2 = 'Yes'**
We are moving from Hibernate native criteria to JPA criteria queries in scope of upgrading a hibernate from 4.3.11 to 5.2.12 and found out different behavior. Previously hibernate criteria use a single query with joins to eager fetch one-to-many associated entities, but JPA use separate queries to fetch the associated entities for each root entity.
I know I can explicitly set fetch mode like entityRoot.fetch("attributes", JoinType.INNER); but we need to do it in some AbstractDao implementation that should work for any eager one-to-many association so can't explicitly set this.
So can I somehow tell JPA criteria to eager fetch associated entities in a single query using joins by default instead of separate queries for each root entity?
The code example:
CriteriaBuilder builder = createCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(getEntityClass());
Root<T> entityRoot = criteriaQuery.from(getEntityClass());
criteriaQuery.select(entityRoot);
criteriaQuery.where(builder.equal(entityRoot.get("param1"), "value"));
return getEntityManager().createQuery(criteriaQuery).getResultList();
Short answer
You can't configure it in such a way, but you may implement the necessary behavior.
Long answer
As you may read in Hibernate 5.2 User Guide, there are several ways to apply a fetching strategy:
#Fetch annotation
JPQL/HQL query - fetch join
JPA Criteria query - FetchParent::fetch
JPA entity graph - attributeNodes
Hibernate profile - fetchOverrides
#Fetch annotation is a static way to apply fetching strategy, and the FetchMode.JOIN works exactly as you've described:
Inherently an EAGER style of fetching. The data to be fetched is
obtained through the use of an SQL outer join.
The problem is, even if you would mark your attributes collection with the #Fetch(FetchMode.JOIN) annotation, it would be overridden:
The reason why we are not using a JPQL query to fetch multiple
Department entities is because the FetchMode.JOIN strategy would be
overridden by the query fetching directive.
To fetch multiple relationships with a JPQL query, the JOIN FETCH
directive must be used instead.
Therefore, FetchMode.JOIN is useful for when entities are fetched
directly, via their identifier or natural-id.
JPA Criteria query without FetchParent::fetch would do the same.
Since you need a universal solution for an abstract DAO, the possible way is to process all eager one-to-many associations with reflection:
Arrays.stream(getEntityClass().getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.forEach(field ->
entityRoot.fetch(field.getName(), JoinType.INNER));
Of course, calling reflection for every query would be inefficient. You may obtain all loaded #Entity classes from Metamodel, process them, and store results for further use:
Metamodel metamodel = getEntityManager().getMetamodel();
List<Class> entityClasses = metamodel.getEntities().stream()
.map(Type::getJavaType)
.collect(Collectors.toList());
Map<Class, List<String>> fetchingAssociations = entityClasses.stream()
.collect(Collectors.toMap(
Function.identity(),
aClass -> Arrays.stream(aClass.getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.map(Field::getName)
.collect(Collectors.toList())
));
I have following query which is working fine:
public ContractorContractor findContractorByName(String contractorName) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ContractorContractor> query = builder.createQuery(ContractorContractor.class);
Root<ContractorContractor> root = query.from(ContractorContractor.class);
query.select(root).distinct(true);
Predicate namePredicate = builder.like(root.get(ContractorContractor_.name), contractorName);
query.where(builder.and(namePredicate));
return em.createQuery(query).getSingleResult();
}
Above query gives me single contractor by name or throws exception.
Now I would like to do same thing but get more informations about contractor (add the fetch to another child of contractor) but with following query I do not get result (org.springframework.dao.EmptyResultDataAccessException: No result found for query is thrown). Query with fetch:
public ContractorContractor findContractorByName(String contractorName) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ContractorContractor> query = builder.createQuery(ContractorContractor.class);
Root<ContractorContractor> root = query.from(ContractorContractor.class);
root.fetch(ContractorContractor_.countries);
query.select(root).distinct(true);
Predicate namePredicate = builder.like(root.get(ContractorContractor_.name), contractorName);
query.where(builder.and(namePredicate));
return em.createQuery(query).getSingleResult();
}
Can anybody tell me what I am doing wrong and why in above query?
It appears that you have accidentally limited the domain of your query by adding a inner fetch join with the addition of the following statement:
root.fetch(ContractorContractor_.countries);
As per JPA 2.1, JSR 338, section 6.5.4
[...] A fetch join has the same join semantics as the corresponding inner or outer join [...]
Thus changing the implicit inner fetch join (JoinType.INNER) to an (outer) left fetch join should solve your problem:
root.fetch(ContractorContractor_.countries, JoinType.LEFT);
This side effect might seem a little unexpected which is probably why the authors of the specification added the following comment (although in context with standard joins, section 4.4.7):
Application developers should use caution in defining identification variables because the domain of the query can depend on whether there are any values of the declared type.
I am trying to build this query with JPA Criteria API
SELECT s FROM snapshot s
INNER JOIN (
SELECT collector_id, entity_id, MAX(timestamp) AS "timestamp"
FROM snapshot GROUP BY collector_id, entity_id
) AS j ON s.TIMESTAMP = j.TIMESTAMP AND s.collector_id = j.collector_id AND s.entity_id = j.entity_id;
The inner select should get 3 properties to identify a snapshot, and then the outer select will get all the other properties of a snapshot based on 3 that inner select returned.
I have success with building the inner select, but how to combine the outer select with the inner using a join?
Or, maybe, there is a different way to construct the query itself in a way, that doesn't include a sub query...
EDIT:
Similar quertion: jpa criteria-api: join with subselect
JPA does not support sub-selects in the FROM clause. Some JPA providers may support this.
For example EclipseLink does:
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Sub-selects_in_FROM_clause
This is a sample entity:
public class Account{
#Id
Long id
Double remaining;
#ManyToOne
AccountType type
}
public class AccountType{
#Id
Long id;
String name;
}
Now i create a criteria query with Join as follwing :
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createquery();
Root<Account> accountRoot = criteriaQuery.from(Account.class);
Join<Account, AccountType> typeJoin = accountRoot.join(Account_.type);
criteriaQuery.multiSelect(
typeJoin,
criteriaBuilder.sum(accountRoot.get(Account_.remaining))
);
criteriaQuery.groupBy(typeJoin);
Query query = getEntityManager().createQuery(criteriaQuery);
query.getResultList();
The above code generate Sql command like following:
select accType.id, accType.name, sum(acc.remaining)
from account acc join accType on acc.accounttype_id = accType.id
group by accType.id
Above code work in PosgreSQL but can't run in Oracle, because in it select accType.name that doesn't appear in the group by clause.
update :
I think my question isn't clear for you. My question isn't about PostgreSQL or Oracle behavior in group by. My question is this :
I use typeJoin in group by clause(this means I expect hibernate use all field of AccountType in group by), but why hibernate just use identity field on group by? if I will use just identity field in group by then I can use the following statement :
criteriaQuery.groupBy(typeJoin.get(AccountType_.id)) ;
JPA/Hibernate doesn't automatically include all entity properties in a group by clause, so you have to manually specify them:
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root<Account> accountRoot = criteriaQuery.from(Account.class);
Join<Account, AccountType> typeJoin = accountRoot.join(Account_.type);
criteriaQuery.multiSelect(
typeJoin.get("id"),
typeJoin.get("name"),
criteriaBuilder.sum(accountRoot.get(Account_.remaining))
);
criteriaQuery.groupBy(typeJoin.get("id"), typeJoin.get("name"));
Query query = getEntityManager().createQuery(criteriaQuery);
query.getResultList();
If using GROUP BY, Oracle requires every column in select list to be in the GROUP BY.PostgreSQL is the same, except when grouping by the primary key, then it allows you to select any column.
From Oracle docs
In a query containing a GROUP BY clause, the elements of the select
list can be aggregate functions, GROUP BY expressions, constants, or
expressions involving one of these.
From PostgreSQL docs
When GROUP BY is present, or any aggregate functions are present, it
is not valid for the SELECT list expressions to refer to ungrouped
columns except within aggregate functions or when the ungrouped column
is functionally dependent on the grouped columns, since there would
otherwise be more than one possible value to return for an ungrouped
column. A functional dependency exists if the grouped columns (or a
subset thereof) are the primary key of the table containing the
ungrouped column.