JPA CriteriaQuery - access fields from connected table with ManyToOne - jpa

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!

Related

JPQL In clause error - Statement too complex

Following is the code which is blowing up if the list which is being passed in to "IN" clause has several values. In my case the count is 1400 values. Also the customer table has several thousands (arround 100,000) of records in it. The query is executing against DERBY database.
public List<Customer> getCustomersNotIn(String custType, List<Int> customersIDs) {
TypedQuery<Customer> query = em.createQuery("from Customer where type=:custType and customerId not in (:customersIDs)", Customer.class);
query.setParameter("custType", custType);
query.setParameter("customersIDs", customersIDs);
List<Customer> customerList = query.getResultList();
return customerList;
}
The above mentioned method perfectly executes if the list has less values ( probably less than 1000 ), if the list customersIDs has more values since the in clause executes based on it, it throws an error saying "Statement too complex"
Since i am new to JPA can any one please tell me how to write the above mention function in the way described below.. * PLEASE READ COMMENTS IN CODE *
public List<Customer> getCustomersNotIn(String custType, List<Int> customersIDs) {
// CREATE A IN-MEMORY TEMP TABLE HERE...
// INSERT ALL VALUES FROM customerIDs collection into temp table
// Change following query to get all customers EXCEPT THOSE IN TEMP TABLE
TypedQuery<Customer> query = em.createQuery("from Customer where type=:custType and customerId not in (:customersIDs)", Customer.class);
query.setParameter("custType", custType);
query.setParameter("customersIDs", customersIDs);
List<Customer> customerList = query.getResultList();
// REMOVE THE TEMP TABLE FROM MEMORY
return customerList;
}
The Derby IN clause support does have a limit on the number of values that can be supplied in the IN clause.
The limit is related to an underlying limitation in the size of a single function in the Java bytecode format; Derby currently implements IN clause execution by generating Java bytecode to evaluate the IN clause, and if the generated bytecode would exceed the JVM's basic limitations, Derby throws the "statement too complex" error.
There have been discussions about ways to fix this, for example see:
DERBY-6784
DERBY-6301, or
DERBY-216
But for now, your best approach is probably to find a way to express your query without generating such a large and complex IN clause.
Ok here is my solution that worked for me. I could not change the part generating the customerList since it is not possible for me, so the solution has to be from within this method. Bryan your explination was the best one, i am still confuse how "in" clause worked perfectly with table. Please see below solution.
public List<Customer> getCustomersNotIn(String custType, List<Int> customersIDs) {
// INSERT customerIds INTO TEMP TABLE
storeCustomerIdsIntoTempTable(customersIDs)
// I AM NOT SURE HOW BUT, "not in" CLAUSE WORKED INCASE OF TABLE BUT DID'T WORK WHILE PASSING LIST VALUES.
TypedQuery<Customer> query = em.createQuery("select c from Customer c where c.customerType=:custType and c.customerId not in (select customerId from TempCustomer)");
query.setParameter("custType", custType);
List<Customer> customerList = query.getResultList();
// REMOVE THE DATA FROM TEMP TABLE
deleteCustomerIdsFromTempTable()
return customerList;
}
private void storeCustomerIdsIntoTempTable(List<Int> customersIDs){
// I ENDED UP CREATING TEMP PHYSICAL TABLE, INSTEAD OF JUST IN MEMORY TABLE
TempCustomer tempCustomer = null;
try{
tempCustomerDao.deleteAll();
for (Int customerId : customersIDs) {
tempCustomer = new TempCustomer();
tempCustomer.customerId=customerId;
tempCustomerDao.save(tempCustomer);
}
}catch(Exception e){
// Do logging here
}
}
private void deleteCustomerIdsFromTempTable(){
try{
// Delete all data from TempCustomer table to start fresh
int deletedCount= tempCustomerDao.deleteAll();
LOGGER.debug("{} customers deleted from temp table", deletedCount);
}catch(Exception e){
// Do logging here
}
}
JPA and the underlying Hibernate simply translate it into a normal JDBC-understood query. You wouldn't write a query with 1400 elements in the IN clause manually, would you? There is no magic. Whatever doesn't work in normal SQL, wouldn't in JPQL.
I am not sure how you get that list (most likely from another query) before you call that method. Your best option would be joining those tables on the criteria used to get those IDs. Generally you want to execute correlated filters like that in one query/transaction which means one method instead of passing long lists around.
I also noticed your customerId is double - a poor choice for a PK. Typically people use long (autoincremented/sequenced, etc.) And I don't get the "temp table" logic.

TypedQuery<x> returns vector of Object[] instead of list of x-type object

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;

Category tree structure with node count using JPA

I have a category tree structure, using JPA with eclipse link.
Each category in the tree includes items, and I want to select all categories along with their items count.
The code I inherited used to calculate the number of items offline, and update the category, and use the cool JPA trick for selecting the categories like so:
#Entity
#Table(name = "categories")
#NamedNativeQuery(name = "topLevel", query = "SELECT categories.* FROM categories WHERE categories.parent_id IS NULL")
public class Category {
#Id
private Long id;
private String name;
private Integer item_count;
private Long parent_id;
#JoinColumn(name = "parent_id")
private List<Categories> categories;
}
When running the native query - I get all the categories populated in the tree structure.
However, now I have a new filter that doesn't allow the user to see all the items in the category. So I can no longer calculate the items offline.
I tried to use a join so I can calculate it online, like this:
#NamedNativeQuery(name = "topLevel", query = "SELECT categories.*, count(*) as item_count FROM categories, items_to_categories WHERE categories.id = items_to_categories.category_id and some_user_specific_query and categories.parent_id IS NULL GROUP BY categories.id")
but this doesn't populate the lower levels with the right items count.
Since I am using Eclipse Link, I can't use a formula field that is not supported in this JPA implementation.
Any ideas on how to change my model in order to get it to work?
Thanks in advance!
If you want an Item count you can either query for it, such as using JPQL. This will return an Object[] including the Category and its Integer item count. If you want to use a native SQL query, then you will need to use a SqlResultSetMapping.
If you want the item count in Category, then you could define a database VIEW that makes this appear as a column and map to the view.
Another option is to map the items of the Category, then to get the item count, just access the size() of the items in Java (you could use join or batch fetching to load the items efficiently).

JPA criteriabuilder "IN" predicate on foreign key error: Object comparisons can only use the equal() or notEqual() operators

I want to select a list of file references from a table by looking at which users have the rights to retrieve that file. To do this I have 3 tables, a file table, an access control table, and a users table.
I am using JPA and Criteriabuilder (because there are more tables involved and I need dynamicle create the query, I am leaving out the other tables and predicates from this question for the sake of readability).
The following code works
CriteriaBuilder queryBuilder = em.getCriteriaBuilder();
CriteriaQuery<File> queryDefinition = queryBuilder.createQuery(File.class);
Root<File> FileRoot = queryDefinition.from(File.class);
List<Predicate> predicateList = new ArrayList<Predicate>();
Predicate userPredicate = FileRoot .join("FileAccesControlCollection").join("userId").get("usersId").in(loggedInUser.getUsersId());
predicateList.add(userPredicate );
queryDefinition.where(predicateArray).distinct(true);
Query q = em.createQuery(queryDefinition);
List<Files> results = (List<Files>) q.getResultList();
For the userpredicate I want to leave out the last join to the users table because the ID that I want to filter on is already present in the FileAccesControlCollection table, and a join is a computational expensive database operation.
What I tried is to do this:
Predicate userPredicate = FileRoot .join("FileAccesControlCollection").get("usersId").in(loggedInUser.getUsersId());
But I guess because the userId value in the FileAccesControlCollection entity class is a foreignkey reference to the Users class I get the following error:
Exception Description: Object comparisons can only use the equal() or notEqual() operators. Other comparisons must be done through query keys or direct attribute level comparisons.
Is there a way, using the loggedInUser entity or its Id, to filter the files by just joining the File class to the FileAccesControlCollection class and filtering on the userId foreign key? I am kind of new to JPA and using google lead me to a lot of pages but not a clear answer for something which seems to me should be possible.
So "userId" is mapped as a OneToOne? Then you could do,
get("userId").get("id").in(...)
You could also add a QueryKey in EclipseLink using a DescriptorCustomizer for the foreign key field and then use it in the query,
get("userFk").in(...)
try this:
Predicate userPredicate = FileRoot.join(FileAccesControlCollection.class).join(Users.class).get("{id field name in class Users}").in(loggedInUser.getUsersId());
good luck.

Tuple result Criteria API subquery

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.