I'm using GAE's datastore and JPA persistence framework to store my entities. Though when attempting to retreive some specific entities I run into the problem mentioned below.
The following exception is thrown when invoking the getResultList() method on my TypedQuery: javax.persistence.PersistenceException: Illegal argument
EntityManager em = Persistence.createEntityManagerFactory("test-persistence")
.createEntityManager();
String q = "SELECT c FROM c TestBord c WHERE c.publiclyAvailible=true
AND c.avarageRating='5'
AND c.user LIKE 'user%'
AND c.nameBord LIKE 'bord%'";
TypedQuery<TestBord> tq = em.createQuery(q, TestBord.class);
List<TestBord> l = tq.getResultList();
As also shown above, here is the query I'm using:
SELECT c FROM c KvCBord c WHERE c.publiclyAvailible=true
AND c.avarageRating='5'
AND c.user LIKE 'user%'
AND c.nameBord LIKE 'bord%'
It seems to break when I use two LIKE clauses, anybody have any ideas on how to work around this problem, or knows how to properly rewrite the query?
NOTE: Works fine with just one LIKE clause though.
AppEngine translates your GQL query into a low level Datastore API query. According to the Restrictions on queries Java docs, "Inequality filters are limited to at most one property". This is usually because of index selection. The LIKE operator becomes an inequality filter and cannot apply to both .user and .nameBord properties in the same query.
It doesn't work because App Engine has restrictions in its queries. Queries results come from indexes, and you can't have indexes that support 2 or more inequality filters.
I recommend using Search API for that searches. It's easy, practical, fast, and you can do more complex searches:
https://developers.google.com/appengine/docs/java/search/
Related
Sorting and paging with Hibernate's Criteria api comes with a big restriction, it needs to retrieve distinct results from the database. Tools provided by the api like DistinctRootTransformer won't work, because it is applied after retrieving the entities from db and thus breaks paging and sorting. The only way to get distinct results of a query with restrictions on an association is by limiting the resultset by a DetachedCriteria:
DetachedCriteria dc = DetachedCriteria.forClass(Household.class, "h")
.createAlias("cats", "c", JoinType.LEFT_OUTER_JOIN)
.add(Restrictions.or(
Restrictions.isEmpty("cats"),
Restrictions.ne("c.name", "Sylvester")
))
.setProjection(Projections.distinct(Projections.property("h.id")));
Criteria criteria = session.createCriteria(Household.class)
.add(Property.forName("id").in(dc));
...apply paging, sorting and filtering to criteria.
Does anybody know a better approach such as omitting subqueries and use joins without breaking pagination? My goal is to find a solution that is reusable, like passing only a criteria to another method that applies paging, sorting and filtering.
Update:
The following code does not work. Because of the join I have to use a Resulttransformer, to get distinct results. However, it is applied after sorting and paging.
Criteria criteria = session.createCriteria(Household.class)
.createAlias("cats", "c", JoinType.LEFT_OUTER_JOIN)
.add(Restrictions.ne("c.name","Sylvester"))
.setFirstResult((page - 1) * pagesize)
.setMaxResults(pagesize)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
E.g. debugging the sql the database would return something like that:
household_id=1,...cat_ids={1,2};household_id=1,...cat_ids={1,2};household_id=2,...cat_ids={1};
In this example, setting pagesize to 1 and viewing page 2 should return the uid 2, because there are only two distinct users. But as you can see in the database output, it returns the wrong uid 1, because Resulttransformers kicks in afterwards.
If you create DetachedCriteria in some method and then work with it, you can try to convert DetachedCriteria to Criteria using something like:
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Your.class);
// then add all your restrictions and convert
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
And then apply you pagination
criteria.setFirstResult(pageable.getPage() * pageable.getSize());
criteria.setMaxResults(pageable.getSize());
And then execute criteria. For example:
List<T> result = criteria.list();
When I need to paginate, I never use DetachedCriteria, but instead normal Criteria, controlling first and max results.
From view I determine which page I need to show and once I have prepared the critera I configure this way:
criteria.setMaxResults(lazyQuery.getPageSize());
criteria.setFirstResult(layQuery.getStart());
lazyQuery is an object of my own model, used for view and business logic. This works perfect.
While building a query using Hibernate, I noticed something rather odd. If I use sequential named parameters for the ORDER BY clause, Hibernate throws a QuerySyntaxException (the colon prefix being an unexpected token):
createQuery("FROM MyEntity ORDER BY :orderProperty :orderDirection");
However, when this is done with a plain SQL query the query is created without a problem:
createSQLQuery("SELECT * FROM my_entity_table ORDER BY :orderProperty :orderDirection");
I know Hibernate is doing more String evaluation for the HQL query, which is probably why the SQL query is created without an error. I am just wondering why Hibernate would care that there are two sequential named parameters.
This isn't a huge issue since it is simple to work around (can just append the asc or desc String value to the HQL instead of using a named paramater for it), but it struck my curiosity why Hibernate is preventing it (perhaps simply because 99% of the time sequential named parameters like this result in invalid SQL/HQL).
I've been testing this in my local, and I can't get your desired outcome to work with HQL.
Here is quote from the post I linked:
You can't bind a column name as a parameter. Only a column value. This name has to be known when the execution plan is computed, before binding parameter values and executing the query. If you really want to have such a dynamic query, use the Criteria API, or some other way of dynamically creating a query.
Criteria API looks to be the more useful tool for your purposes.
Here is an example:
Criteria criteria = session.createCriteria(MyEntity.class);
if (orderDirection.equals("desc")) {
criteria.addOrder(Order.desc(orderProperty));
}
else {
criteria.addOrder(Order.asc(orderProperty));
}
According to the answer accepted in this question, you can only define parameters in WHERE and HAVING clauses.
The same answer also gives you some ways to have a workaround for your problem, however I will add one more way to do this:
Use the CASE - WHEN clause in your ORDER BY, this would work by the following way:
SELECT u FROM User u
ORDER BY
CASE WHEN '**someinputhere**' = :orderProperty
AND '**someotherinput**' = :orderDirection
THEN yourColumn asc
ELSE yourColumn desc END
Please, note that in this approach would required you to write all the possible inputs for ordering. Not really beautiful but really useful, especially because you would not need to write multiple queries with different orderings, plus with this approach you can use NamedQueries, which would be possible by writing the query dinamically using string concats.
Hope this can solve your problem, good luck!
I'm using openJPA as implementation, and i'm facing the following issue.
In one of our services, we use a namedQuery to select value in a range, so something like that:
query = "select xxx from xxx where xxx in (:param)"
This service / query is called by another process which is responsible of building/providing this parameter value. Sometime - for bad reasons - this process give us a list with a length greater than the maximum one authorized by DB2.
So i'm wondering if it's possible for us to implement a mecanism to automatically split this parameter into several list of vlaues, execute for each internal list this namedQuery and then aggregate results before returning.
The main question now is: can i re-use several times my built TypedQuery to re-execute same request but with a different parameter value ?
Code example (For the example, not a real nor existing code):
TypedQuery<MyClass> query = em.createNamedQuery("myQueryName", MyClass.class);
foreach (...) {
query.setParameter(...);
res = query.getResultList();
// Query re-executed and results are different ?
}
Is it a correct way to fix this kind of issue, if not is there any better (in term of performances) way to do this ?
No, you can't do that. You must recreate the TypedQuery for each execution.
I'm trying to convert a simple Play/JPA query to use the criteria API. Below isn't even the query I'm trying to convert; this one's even simpler -- just trying to get something to succeed to begin with.
All the examples I've been finding online expect you to be able to use a class that has _ appended to the class name, much like what I've seen hibernate queries do to table name aliases in the generated SQL. However, I can't get my code to compile this way since there is no class: ExtendedHaulTrain_ (there is however ExtendedHaulTrain)
Is there some kind of annotation I need to add to the ExtendedHaulTrain class? Perhaps I have not been reading deeply enough but the examples I've found so far don't address the issue of the class with the underbar appended.
Here's my code that fails to compile on the last line, specifically on ExtendedHaulTrain_
Query query = JPA.em().createQuery("select DISTINCT(x.trnType) from ExtendedHaulTrain x");
List<String> trainTypes = query.getResultList();
//as criteria query
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery<ExtendedHaulTrain> q = cb.createQuery(ExtendedHaulTrain.class);
Root<ExtendedHaulTrain> xhtRoot = q.from(ExtendedHaulTrain.class);
q.select(xhtRoot.get(ExtendedHaulTrain_.trnType)).distinct(true);
Instead of the MetaModel classes(they end with '_') you can always use the attribute name in form of a string as refrence.
q.select(xhtRoot.get("trynType")).distinct(true);
As noted in my comment there is a notion of a meta-model class I'd rather avoid. So below is how I converted my existing query to use the criteria API. Again, this is just to get a success under my belt; I'm probably not going to replace this query. Rather I have another more complex query, for which I intend to use the Criteria API; this was just to get some familiarity with the Criteria API -- there will probably be more questions to follow!
/*
Query query = JPA.em().createQuery("select DISTINCT(x.trnType) from ExtendedHaulTrain x");
List<String> trainTypes = query.getResultList();
*/
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(ExtendedHaulTrain.class);
Root root = cq.from(ExtendedHaulTrain.class);
cq.select(root.get("trnType")).distinct(true);
List<String> trainTypes = JPA.em().createQuery(cq).getResultList();
I understand that you do not like these meta-models but this is actually a very useful thing, which keeps your code on the safe side of type-safety (believe me, once you begin to write more queries, you will see the advantage). And the advantage is: you can generate them automatically with the so called meta-model generators (which are annotation processing tools). Hibernate has for example something one generator. In Eclipse it is very easy to generate them. Also in Maven it is easy. I recommend to use them.
UPDATE
Type Safety means actually beside not having to write xhtRoot.get("trynType") also that you work with correct join types. Do not forget, that compared to NamedQueries, CriteriaQueries are not checked on deployment. This means, if you remove or use the wrong type in the generic part of a join result (WrongOwner below)
Join<WrongOwner, Address> address = cq.join(Pet_.owners).join(Owner_.addresses);
you will know that on compile time.
I am using JPA 2. For safety reasons, I am working type safe with CriteriaQuery's (and thus, I am not searching for any solutions to typed queries and so on).
I recently came across an issue in which I needed to set a SQL-LIMIT.
After a lot of searching, I was still not successful in finding a solution.
CriteriaQuery<Product> query = getEntityManager().getCriteriaBuilder().createQuery(Product.class);
Root<Product> product = query.from(Product.class);
query.select(product);
return em.createQuery(query).getResultList();
Can anyone help me?
Define limit and offset on the Query:
return em.createQuery(query)
.setFirstResult(offset) // offset
.setMaxResults(limit) // limit
.getResultList();
From the documentation:
TypedQuery setFirstResult(int startPosition)
Set the position of the first result to retrieve. Parameters:
startPosition - position of the first result, numbered from 0
TypedQuery setMaxResults(int maxResult)
Set the maximum number of results to retrieve.
For the sake of completeness, I want to answer the initial question with regards to the JPA Criteria API.
First of all, you might clarify for yourself beforehand when to use JPQL and when to use the Criteria API depending on the use case. There is a nice article on this at the ObjectDB documentation website which states:
A major advantage of using the criteria API is that errors can be detected earlier, during compilation rather than at runtime. On the other hand, for many developers string based JPQL queries, which are very similar to SQL queries, are easier to use and understand.
I recommend this article in general because it describes concisely how to use the JPA Criteria API. There is a similar article about the Query API.
Back to the question:
A CriteriaQuery offers a set of restrictions that are accessible - for instance - by using the where() method. As you might intuitively guess: you cannot limit the query to a particular number of results with such a restriction - except you have a trivial case like limiting on a unique identifier (which would make the usage of the Criteria API obsolete). Simply explained: a limit is not a criterion and therefore not covered by that api. See also the old but gold Java EE docs for more details.
Solution
However, you can of course use your CriteriaQuery object as a foundation for a JPQL query. So first, you create your CriteriaQuery as is:
CriteriaQuery<Product> criteriaQuery =
getEntityManager().getCriteriaBuilder().createQuery(Product.class);
Root<Product> product = criteriaQuery.from(Product.class);
criteriaQuery.select(product);
Then use the JPA Query constructor for CriteriaQuery objects:
Query limitedCriteriaQuery = getEntityManager().createQuery(criteriaQuery)
.setMaxResults(resultLimit); // this is the important part
return limitedCriteriaQuery.getResultList();
And that is basically how you should use both APIs according to the documentation and the provided articles.