Hibernate - JPA - oneToMany - count as subquery and use as a Predicate - java

I have the following relation and I need to get consumers which have at least one purchase (as a subquery, because this is a part of a bigger query).
#Entity
#Table(name = "consumers")
public class Consumer extends User {
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "consumer_id")
private List<Purchase> purchases;
}
and the query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Consumer> criteriaQuery = criteriaBuilder.createQuery(Consumer.class);
Root<Consumer> root = criteriaQuery.from(Consumer.class);`
Join<Consumer, Purchase> purchases = root.join(Consumer_.purchases, JoinType.LEFT);
sub.select(criteriaBuilder.count(purchases.get(Purchase_.id)));
sub.where(criteriaBuilder.equal(root.get(Consumer_.id), purchases.get(Purchase_.consumer).get(Consumer_.id)));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(sub, 0L));

Related

JPA Criteria filter collections based on condition

Why Criteria query not filtering records based on Services (collection entities) condition services.get("status"), "pending") as below?
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> customer = query.from(Customer.class);
Join<Customer, Service> services = customer.join("services", JoinType.INNER);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.equal(customer.get("customerId"), 1));
predicates.add(cb.equal(services.get("status"), "pending"));
query.select(customer).distinct(true)
.where(predicates.toArray(new Predicate[]{}));
List<Customer> customers = em.createQuery(query).getResultList();
where as the SQL does filter records properly
select * from customers c
INNER JOIN SERVICES s on s.COID = c.COID
where c.ID=1 and
s.status='pending';
Records were not qualified in resultset based on status condition(for collection), in fact, all services of a customer were returned.
I tried to use fetch Join (because there were 2 queries executed 1st for customer and 2nd for services of this customer, thought condition might be not evaluated in 2nd query) using
customer.fetch("services", JoinType.INNER);
but no luck.
I'm surprised by this behavior. I'm using OpenJPA JPA provider
Entities are Customer and Service.
public class Customer{
#Id
#Column(name = "ID")
private Integer customerId;
#OneToMany
#MappedBy(name = "customer")
private List<Service> services;
}
public class Service {
#EmbeddedId
private ServicesPK servicePK;
#ManyToOne
#JoinColumn(name = "COID")
private Customer customer;
}
#Embeddable
#EqualsAndHashCode
public class ServicesPK implements Serializable {
#Column(name = "COID")
private Integer coId;
#Column(name = "VERSION")
private Integer version;
}
Try this code, change I did is I added type safety to the query.Know about type safety typesafe
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> customer = query.from(Customer.class);
Join<Customer, Service> services = customer.join(Customer_.services);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.equal(customer.get("customerId"), 1));
predicates.add(cb.equal(services.get(Service_.status), "pending"));
query.select(customer).distinct(true)
.where(predicates.toArray(new Predicate[]{}));
List<Customer> customers = em.createQuery(query).getResultList();

JPA Criteria multiselect with fetch

I have following model:
#Entity
#Table(name = "SAMPLE_TABLE")
#Audited
public class SampleModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "SHORT_NAME", nullable = true)
private String shortName;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
Now I would like to query only columns: id, name, shortName and mentor which referes to User entity (not complete entity, because it has many other properties and I would like to have best performance).
When I write query:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
I have following exception:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
Query before exception:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
I know that I can replace fetch with join but then I will have N+1 problem. Also I do not have back reference from User to SampleModel and I do not want to have..
I ran into this same issue, and found that I was able to work around it by using:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
instead of
CriteriaQuery<X> crit = builder.createQuery(X.class);
A little extra work has to be done to produce the end result, e.g. in your case:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
It's been a long time since the question was asked. But I wish some other guys would benefit from my solution:
The trick is to use subquery.
Let's assume you have Applicant in your Application entity (one-to-one):
#Entity
public class Application {
private long id;
private Date date;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
This code will generate a select statement for Application, and select statements for Applicant per each Application.
Note that you have to define an appropriate constructor corresponding to your multiselect.
I got the same problem using EclipseLink as the JPA provider : I just wanted to return the id of a mapped entity («User» in Gazeciarz's example).
This can be achieved quite simply by replacing (in the query.multiselect clause)
root.get(SampleModel_.mentor)
with something like
root.get(SampleModel_.mentor).get(User_.id)
Then, instead of returning all the fields of User, the request will only return the its id.
I also used a tuple query but, in my case, it was because my query was returning fileds from more than one entity.

Hibernate criteria implementation for this entity model (subquery, self-join)

Given the following entity one-to-many model:
One Repository can be linked to many AuditRecords.
Many AuditRecords can all link to the same Repository
#Entity
class AuditRecordEntity {
private AuditRepositoryEntity auditRepository;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
public AuditRepositoryEntity getAuditRepository() {
return auditRepository;
}
...
}
#Entity
class AuditRepositoryEntity {
private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();
#OneToMany(mappedBy = "auditRepository")
public List<AuditRecordEntity> getAuditRecords() {
return auditRecords;
}
...
}
Minor correction, in ERD diagram below, for 'repositoryId', read 'auditRepository'
I am trying to get the Criteria API implementation to:
Get the latest (by accessTime) AuditRecord for each distinct Repository? I.e. a list of AuditRecords, one for each Repository, where the AuditRecord is the last AuditRecord for that Repository (in the case where a Repository has many AuditRecords).
I have the HQL query to do this:
select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)
But need to use the Criteria APi instead:
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Object> query = builder.createQuery();
Root<AuditRecordEntity> root = query.from(AuditRecordEntity.class);
// what next?
I have got this to work(around) by using the output from the HQL query as input to the criteria API:
final List<UUID> auditRecordIds = execute("select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)")
Root<AuditRecordEntity> root = criteriaQuery.from(AuditRecordEntity.class);
criteriaQuery.select(root);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(root.get("id").in(auditRecordIds.toArray()));
entitySearchCriteria.addPredicates(predicates);
...

JPA: querying an entity with a restriction on inverse one-to-one relation

I have a bi-directional one-to-one relation on an entity. I want to query for the inverse side entities where the restriction concerns the said relation. More specifically, I want all of the entities where the one-to-one doesn't map to any relation (i.e. is null).
My entities:
The inverse:
#Entity
#Table(name="direct_debit")
#Audited
public class DirectDebit implements Serializable{
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#OneToOne(mappedBy="debit", targetEntity = Account.class)
private Account account;
...
Simple getters and setters for all fields here
...
}
The owner:
#Entity
#Table(name="account")
#Inheritance(strategy = InheritanceType.JOINED)
#Audited
public abstract class Account implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#Column(name = "account_id")
private String accountId;
#OneToOne
#JoinColumn(name="debit_id")
private DirectDebit debit;
...
Simple getters and setters for all fields here
...
}
My query is an attempt to retrieve all DirectDebit instances whose accounts are null. My attempted criteria query:
#Override
#Transactional
public List<DirectDebit> getUnassignedDirectDebits(){
EntityManager entityManager = entityManagerProvider.get();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<DirectDebit> query = criteriaBuilder.createQuery(DirectDebit.class);
Root<DirectDebit> directDebitRoot = query.from(DirectDebit.class);
query.select(directDebitRoot);
query.where(criteriaBuilder.isNull(directDebitRoot.get("account")));
List<DirectDebit> accounts = entityManager.createQuery(query).getResultList();
for (DirectDebit account : accounts){
entityManager.detach(account);
}
return accounts;
}
However, the query that this generates is not at all what I've expected:
select directdebi0_.id as id1_16_ from direct_debit directdebi0_ where directdebi0_.id is null
Naturally, this query returns no results.
Am I expecting hibernate/jpa to do something that it cannot (seeing as the query is somewhat odd)? Is there a different way I can do what I'm trying to achieve here?
I'm using Hibernate as the JPA provider, and the database I'm connecting to is MySQL.
The CriteriaBuilder.isNull only work as spected with the owner's entity.
On your case, you can do:
#Override
#Transactional
public List<DirectDebit> getUnassignedDirectDebits(){
EntityManager entityManager = entityManagerProvider.get();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<DirectDebit> query = criteriaBuilder.createQuery(DirectDebit.class);
Root<DirectDebit> directDebitRoot = query.from(DirectDebit.class);
query.select(directDebitRoot);
//Changes ini
CriteriaQuery<Integer> ids = criteriaBuilder.createQuery(Integer.class);
Root<Account> accountRoot = ids.from(Account.class);
Path<Integer> path = accountRoot.get("debit").<Integer>get("id");
TypedQuery<Integer> idsQuery = entityManager.createQuery(ids.select(path));
query.where(criteriaBuilder.not(directDebitRoot.in(idsQuery.getResultList())));
//query.where(criteriaBuilder.isNull(directDebitRoot.get("account")));
//Changes end
List<DirectDebit> accounts = entityManager.createQuery(query).getResultList();
for (DirectDebit account : accounts){
entityManager.detach(account);
}
return accounts;
}
Since what you want can be accomplished by a simple query, I suggest you not use the hibernate criteria API and user HQL/JPQL.
Try this.
EntityManager entityManager = entityManagerProvider.get();
List<DirectDebit> accounts = entityManager.createQuery("SELECT directDebit FROM DirectDebit directDebit WHERE directDebit.account IS NULL").getResultList();

JPA query a collection using between clause

My entity has a collection of another entity on which I need to do a BETWEEN criteria.
I do not want to use the native query.
I am trying to achieve this using the criteria API.
Below is a short snippet of my entity.
#Entity
#Table(name = "ref_dates")
public class Dates{
#Id
#Column(name = "ID")
private int id;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(
name="ref_dates_prg",
joinColumns = #JoinColumn( name="DATE_PRG_ID"),
inverseJoinColumns = #JoinColumn( name="DATE_ID")
)
private Set<DateInfo> dates;
}
It has several other properties, geter/setters, etc which I have not mentioned here.
I need to do a query on this Set for the id's in DateInfo object using between clause.
I tried using Expression<Set<DateInfo>> but haven't reached anywhere.
Thanks for all the help.
Here is my criteria build up.
final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
final CriteriaQuery<NetPrgTimePeriod> criteriaQuery = criteriaBuilder.createQuery(Dates.class);
List<Predicate> criteriaList = new ArrayList<Predicate>();
final Root<Dates> root = criteriaQuery.from(Dates.class);
Join<Dates, DateInfo> dateJoin = root.join("dates", JoinType.LEFT);
Predicate runDatesRange = criteriaBuilder.between(
dateJoin.<Integer> get("id"), startDate.getId(), endDate.getId());
criteriaList.add(runDatesRange);
Join<Dates, TimeInfo> timeJoin = root.join("times", JoinType.LEFT);
Predicate timeBlocksRange = criteriaBuilder.between(
timeJoin.<Integer> get("id"), startTime.getId(), endTime.getId());
criteriaList.add(timeBlocksRange);
criteriaQuery.where(criteriaBuilder.and(criteriaList.toArray(new Predicate[0])));
TypedQuery<NetPrgTimePeriod> query = em.createQuery(criteriaQuery);
List<Dates> results = query.getResultList();
Assuming you actually mapped your collection correctly, the main part you seem to be missing is the Join:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Dates> query = cb.createQuery(Dates.class);
Root<Dates> root = query.from(Dates.class);
Join<Dates, DateInfo> infos = root.join("dates", JoinType.LEFT);
query.distinct(true);
em.createQuery(query.where(cb.between(infos.<Integer>get("id"), 1, 10))).getResultList();
Of course you can substitute metamodel fields where I used strings (which will also obsolete the need for this ugly <Integer> selector - assuming your id is an integer).

Categories