I am trying to use subqueries in an application I am writing using JPA 2.0 type-safe criteria API, with Hibernate 3.6.1.Final as my provider. I have no problem selecting primitive types (Long, MyEntity, etc.), but I want to select multiple columns.
Here's an example of something completely reasonable. Ignore the needless use of subquery -- it is simply meant as illustrative.
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Subquery<Tuple> subQ = cq.subquery(Tuple.class);
Expression<Long> subqCount;
{
Root<MyEntity> root = subQ.from(MyEntity.class);
Path<MyEntity> filter = root.get(MyEntity.challenge);
subqCount = cb.count(root);
// How to select tuple?
Selection<Tuple> tuple = cb.tuple(filter, subqCount);
// !! Run-time exception here
Expression<Tuple> tupleExpr = (Expression<Tuple>) tuple;
// Not sure why I can't use multiSelect on a subQuery
// #select only accepts Expression<Tuple>
createSubQ.select(tupleExpr);
createSubQ.groupBy(filter);
}
cq.multiselect(subqCount);
Although the compiler doesn't complain, I still get a run-time exception.
java.lang.ClassCastException: org.hibernate.ejb.criteria.expression.CompoundSelectionImpl cannot be cast to javax.persistence.criteria.Expression
Is this a bug in hibernate, or am I doing something wrong?
If you can't use multiselect on a subquery, then how can you perform a groupBy?
If you can't use groupBy on a subquery, why is it in the API?
I have the same problem.
I can only attempt to answer your last question by saying you can only really use sub queries to perform very simple queries like:
SELECT name FROM Pets WHERE Pets.ownerID in (
SELECT ID FROM owners WHERE owners.Country = "SOUTH AFRICA"
)
The other thing I wanted to say was how much this incident reminds me of xkcd #979.
I had similar problem.
I had specification, and I wanted to get ids of objects matching this specification.
My solution:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleCriteriaQuery = criteriaBuilder.createTupleQuery();
Root<Issue> root = tupleCriteriaQuery.from(Issue.class);
tupleCriteriaQuery = tupleCriteriaQuery.multiselect(root.get(IssueTable.COLUMN_ID));//select did not work.
tupleCriteriaQuery = tupleCriteriaQuery.where(issueFilter.toPredicate(root, tupleCriteriaQuery, criteriaBuilder));
List<Tuple> tupleResult = em.createQuery(tupleCriteriaQuery).getResultList();
First I select columns (In my case I need only one column), and then I call where method to merge with my given specification.
Related
I want to rewrite this SQL query in JPA.
String hql = "SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction "
+ "FROM payment_transactions WHERE date(created_at)>=date(now()- interval 10 DAY) "
+ "AND date(created_at)<date(now()) GROUP BY date(created_at)";
TypedQuery<Merchants> query = entityManager.createQuery(hql, Merchants.class);
List<Merchants> merchants = query.getResultList();
Is there a way to rewrite the queries into JPA or I should use it as it is?
In situations like these, more often than not the best approach is to write a plain SQL view:
CREATE OR REPLACE VIEW payment_transactions_stats AS
SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction
FROM payment_transactions
WHERE date(created_at)>=date(now()- interval 10 DAY)
AND date(created_at)<date(now()) GROUP BY date(created_at);
And map it to an #Immutable entity. This approach works well when:
you have read only data
the view does not need parameters (in this case there are solutions as well which span from hacky to nice)
You provide no details about the classes and entities but it could be something like:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
From<PaymentTransaction> tx = query.from(PaymentTransaction.class);
Expression<Long> sumAmount = builder.sum(tx.get("amount"));
Expression<Long> count = builder.count(tx.get("id"));
Expression<Date> createdAt = tx.get("created_at");
query.multiselect(createdAt, sumAmount, count);
query.where(builder.greaterThanOrEqualTo(createdAt, builder.function("DATEADD", "DAY", new Date(), builder.literal(-10))),
builder.lessThan(createdAt, new Date()));
query.groupBy(createdAt);
entityManager.createQuery(query).getResultList().stream()
.map(t -> new Merchants(t.get(0, Date.class), t.get(1, Long.class), t.get(2, Long.class)))
.collect(Collectors.toList());
It is better not to use JPA for complex queries like this. JPA are usually used for simple queries.
Since the question is tagged with spring-data-jpa, you could try using a Spring CRUDRepository on top of your table. In the CRUDRepository, write a custom method with the #Query annotation.
It's hard for me to formulate the entire query because I don't know the members of your Merchants class.
Alternatively you can set the nativeQuery = true for the #Query annotation and use actual DB query to solve your problem.
You can use below code
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = qb.createQuery();
Root paymentInstructionsRoot = cq.from(PaymentInstructions.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(qb.greaterThanOrEqualTo(path, fromDateRange));
predicates.add(qb.lessThanOrEqualTo(path, toDateRange));
Selection cdate = paymentInstructionsRoot.get(PaymentInstructions_.createdAt).alias("cdate");
Selection amount = qb.sum(paymentInstructionsRoot.get(PaymentInstructions_.amount))).alias("amount");
Selection nooftransaction = qb.count(paymentInstructionsRoot.get(PaymentInstructions_.id))).alias("nooftransaction");
Selection[] selectionExpression = {cdate, amount, nooftransaction};
Expression[] groupByExpression = {paymentInstructionsRoot.get(PaymentInstructions_.createdAt)};
cq.multiselect(selectionExpression).where(predicates.toArray(new Predicate[]{})).groupBy(groupByExpression).where(predicates.toArray(new Predicate[]{}));
List<PaymentInstructions> paymentInstructions = entityManager.createQuery(cq).getResultList();
In your Entity class that represents the 'payment_transactions' table, add the following:
#SqlResultSetMapping(
name = "PaymentTransaction.summaryMapping",
classes = {
#ConstructorResult(targetClass = PaymentTransactionSummary.class,
columns = {
#ColumnResult(name = "cdate")
, #ColumnResult(name = "amount")
, #ColumnResult(name = "nooftransaction")
})
}
)
Create a new pojo class named PaymentTransactionSummary (must match the name used above, or whatever name you choose, with member fields cdate, amount, and nooftransaction. Include a constructor that includes those three fields in the order listed above.
Then in your dao class, write this:
Query q = entityManager.createNativeQuery("your query string from above"
, "PaymentTransaction.summaryMapping");
List<PaymentTransactionSummary> results = q.getResultList();
My objective is to replace an old JPQL query with a generic type-safe helper method using javax.persistence.metamodel and javax.persitence.criteria.
The query is essentially
select * from table
where field1 = arg1
and field2 = arg2
and field3 = (select max (field3)
from table
where field1 = arg1
and field2 = arg2
and field3 <= arg3
)
Admittedly this is maybe too specialized a query to generalize but I see the need for 2 or 3 other more generic helpers which I can model on this solution.
I have been googling the Criteria documentation (one problem is it's easy to surf a google search result list and mistakenly move from a javax.persitence page to a JBoss Hibernate page... and they are NOT the same).
I have obviously not found a one-stop shop that tells me all I need to know:
how to select a single field in a CriteriaQuery
how to structure a subquery in a CriteriaQuery Expression
how to write a max aggregate function call using CriteriaBuilder
how to properly use Static Metamodel attributes to specify generic classes in a CriteriaBuilder query, when the table being queried has a composite key which is mapped by composite key class (using #EmbeddedId)
OK. I already had the answer before I posted the question but I thought it might be useful to publish what I found.
The use case is a CHARGE table that provides CHG_NU values for ranges of product-option-level values. The appropriate
CHG_NU from the table is the one that matches a PROD_CD and OPTION_TYPE and does not exceed the OPTION_LEVEL.
Here's the method I ended up writing (the comments are specific to the above use-case but the code is generic):
public static <X, KT, PT, BT, NT extends Number> X findWithUpperLimit (Class<X> rootClass, Class<NT> numericClass,
SingularAttribute<X, KT> keyAttr,
SingularAttribute<KT, PT> arg1Attr, PT arg1Val,
SingularAttribute<KT, BT> arg2Attr, BT arg2Val,
SingularAttribute<KT, NT> numericAttr, NT number,
EntityManager em)
{
List<X> results;
CriteriaBuilder cb = em.getCriteriaBuilder ();
// set up the query (returns a full record of the CHARGE table)...
CriteriaQuery<X> cq = cb.createQuery (rootClass);
// ... and the subquery (returns only the BigDecimal OPT_LEVEL)
Subquery<NT> sq = cq.subquery (numericClass);
// set up the root objects for the CHARGE table. Both the query and the subquery are on the same table
Root<X> root = cq.from (rootClass);
Root<X> sqRoot = sq.from (rootClass);
// the query objects and the criteria builder are used to structure the query,
// the root objects are used to get metadata from the table to assign table elements to the criteria
// the subquery gets the closest optLevel to the passed-in number...
sq.select (cb.max (sqRoot.get (keyAttr).get (numericAttr)))
.where (cb.and
(cb.equal (sqRoot.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (sqRoot.get (keyAttr).get (arg2Attr), arg2Val),
cb.le (sqRoot.get (keyAttr).get (numericAttr), number)
));
// ...and the main query matches the passed-in prodCd, optType and the optLevel found by the subquery.
cq.select (root).where (cb.and (cb.equal (root.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (root.get (keyAttr).get (arg2Attr), arg2Val),
cb.equal (root.get (keyAttr).get (numericAttr), sq)
));
results = em.createQuery (cq).getResultList ();
return results.size() == 0 ? null : results.get (0);
}
This is a code snippet that calls it:
Charge charge = DAOHelper.findWithUpperLimit (Charge.class, BigDecimal.class,
Charge_.key,
ChargeKey_.prodCd, invoice.getCharge().getChargeKey().getProdCd(),
ChargeKey_.optType, invoice.getCharge().getChargeKey().getOptType(),
ChargeKey_.optLevel, invoice.getCharge().getChargeKey().getOptType(),
em);
and here's the SQL that it generates:
select charge0_.OPTION_TYPE_CD as OPTION_1_50_,
charge0_.OPTION_LEVEL as OPTION_LEV2_50_,
charge0_.PROD_CD as PROD_CD3_50_,
charge0_.CHG_NU as CHG_NU4_50_
from CHARGE charge0_
where charge0_.PROD_CD=?
and charge0_.OPTION_TYPE_CD=?
and charge0_.OPTION_LEVEL=(select max(charge1_.OPTION_LEVEL)
from CHARGE charge1_
where charge1_.PROD_CD=?
and charge1_.OPTION_TYPE_CD=?
and charge1_.OPTION_LEVEL<=1358.00
)
How can I use the "order by" clause with "asc nulls first"?
This is my code:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<MyClassModel> query = builder.createQuery(MyClassModel.class);
//esprToOrder is a Expression<?> istance that containing the sort field...
query.orderBy(builder.asc(esprToOrder));
TypedQuery<MyClassModel> myQ = em.createQuery(query);
List<MyClassModel> myList = myQ.getResultList();
myList doesn't contain any records with null field sort...
JPA doesn't support specification of "NULLS FIRST|LAST". Some implementations may allow it (by casting to allow extra methods), but it's not part of the JPA spec.
I have a method:
public List<Timetable> getTimetableTableForRegion(String id) {
List<Timetable> timetables;
TypedQuery<Timetable> query = em_read.createQuery("SELECT ..stuff.. where R.id = :id", Timetable.class).setParameter("id", Long.parseLong(id));
timetables = query.getResultList();
return timetables;
}
which returns this:
so, what am I missing in order to return a list of Timetable's?
ok, so, ..stuff.. part of my JPQL contained an inner join to other table. Even through in SELECT there were selected fields just from one table, which was used as type - Timetable, Eclipslink was unable to determine if this fields are part of that entity and instead of returning list of defined entity returned list of Object[].
So in conclusion: Use #OneToMany/#ManyToOne mappings (or flat table design) and query just for ONE table in your JPQL to be able to typize returned entities.
Not sure it might be something is looking for, but I had similar problem and converted Vector to ArrayList like this:
final ArrayList<YourClazz> results = new ArrayList<YourClazz>();;
for ( YourClazzkey : (Vector<YourClazz>) query.getResultList() )
{
results.add(key);
}
i have faced the same problem. and my entity has no one to one or one to many relationship. then also jpql was giving me queryresult as vector of objects. i changed my solution to query to criteria builder. and that worked for me.
code snippet is as below:
CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Timetable> criteria = builder.createQuery(Timetable.class);
Root<Enumeration> root = criteria.from(Timetable.class);
criteria.where(builder.equal(root.get("id"), id));
List<Timetable> topics = this.entityManager.createQuery(criteria) .getResultList();
return topics;
My situation: A have a datatable called AuditDetail. This table is in ManyToOne connection with another table AuditHead. I display data in Primefaces datatable. My problem is with sorting and filtering. If I want to sort a field witch is in AuditHead table, I got an error: no such field exception.
The related code:
public List<AuditDetail> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String,String> filters) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<AuditDetail> cq = cb.createQuery(AuditDetail.class);
Root<AuditDetail> from = cq.from(AuditDetail.class);
CriteriaQuery<AuditDetail> select = cq.select(from);
It works perfectly with AuditDetail fields, but I don't know how can I reach fields from AuditHead. If I use NamedQueries, I can simply say auditDetail.headId, but how can I get AuditHead fields, when I use CriteriaQuery?
I used NamedQueries before, then this code works perfectly:
filterBy="#{log.headId.tableName}" sortBy="#{log.headId.tableName}"
I'm here, because I have no idea anymore, I think tried everything. For example:
Fetch<AuditDetail, AuditHead> fetch = from.fetch(AuditDetail_.headId, JoinType.LEFT);
But I'm absolutely not sure how do I have to do this.
Every help appreciated!
Regards!