How to escape the keyword "on" in HQL - java

Here is my Entity which I want to query with Hibernate 4.3.4:
#Entity
#DynamicInsert
#DynamicUpdate
public class Device extends OrmEntity implements Serializable {
#Column(name = "is_on") // was necessary to get Hibernate to create the table at all
private boolean on;
...
}
This is the HQL I want to execute (btw the database is PostgreSQL):
Query query = session.createQuery("from Device where ... and on = :a");
query.setBoolean("a", true);
This gives me org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: on near line 1.
I tried many escape characters like backticks, \", [ ], single-quotes, without success.
What would be the appropriate way to escape the keyword "on" in this HQL-query?
Edit:
I tried "from Device d where d.on = :a" which results in QuerySyntaxException: unexpected token: d near line 1.
I did not try renameAlias as this seems to be overkill for just escaping a keyword...
Edit2:
I am trying out AliasToMapTransformer but I can not seem to get it working for a where clause ... ? It seems to be made for column aliases.
Edit3:
Generally, this code is around for some time already and strangely it worked with Hibernate < 4.3.$
Edit4:
I accepted NimChimpsky's answer, and implemented the following change: Rename the boolean member from on to isOn, but keep the getter and setter as they were: setOn() and isOn().
No further change in my code necessary, and Hibernate is happy now.

#Column
private boolean deviceIsOn;
annotate the field with a renamed variable, and then my method would be isOn.
Bit hacky ... but you keep your nice friendly OO api, with out too much hassle.
Or even simpler, just call it off which isn't reserved

Related

Criteria JPA - Call Postgres CAST function

I'm trying to call a Postgres function with Criteria but it's not working. I need to use the LIKE clause in a UUID field, so I need to convert into VARCHAR first.
The result I need:
SELECT * FROM my_table WHERE cast(uuid as varchar(36)) like '%1234%';
What I'm doing in Criteria:
final Path<UUID> uuidField = from.get("uuid");
var cast = cb.function("cast", String.class, uuidField, cb.literal("as varchar(36)"));
cb.like(cast, String.format("%%%s%%", stringValue));
The query which is being generated:
HQL: select generatedAlias0 from com.MyTable as generatedAlias0 where function('cast', generatedAlias0.uuid, 'as varchar(36)') like '%1234%' order by generatedAlias0.name asc
Error:
2022-08-08 18:38:48,549 WARN [io.ver.cor.imp.BlockedThreadChecker] (vertx-blocked-thread-checker) Thread Thread[vert.x-eventloop-thread-9,5,main] has been blocked for 2393 ms, time limit is 2000 ms: io.vertx.core.VertxException: Thread blocked
at antlr.ASTFactory.make(ASTFactory.java:342)
at antlr.ASTFactory.make(ASTFactory.java:352)
at org.hibernate.hql.internal.antlr.HqlBaseParser.jpaFunctionSyntax(HqlBaseParser.java:4633)
at org.hibernate.hql.internal.antlr.HqlBaseParser.primaryExpression(HqlBaseParser.java:1075)
The log is not so clear (I'm using Quarkus + Hibernate Reactive), but I suspect it crashed in database because the function('cast', generatedAlias0.uuid, 'as varchar(36)').
I think it should be something like: function('cast', generatedAlias0.uuid, as varchar(36)) (without quotes). But I don't know how to achieve this result to test my theory.
How can I call this CAST function?
After investigating some possible solutions (I'm avoiding to create custom database routines) I found something interesting in a answer from another question:
Currently JPA does not have APIs for replace() and cast(string as numeric). But you can use CriteriaBuilder.function(...) to create database native functions if database portability is not critical.
Source: JPA criteria builder: how to replace and cast a string to numeric in order-by?
I don't know if this is documented is some place, but assuming that there is no way to call CAST(x AS y) using Criteria, I tried a workaround to force the UUID to VARCHAR cast without using the probably unsupported CAST function.
I tested this direct SQL query to database:
SELECT * FROM my_table WHERE concat(uuid, '') like '%123%';
And it works. This CONCAT forces the cast to VARCHAR and the LIKE function does his job. Knowing this, I did:
final Path<UUID> uuidField = from.get("uuid");
var cast = cb.function("concat", String.class, uuidField, cb.literal(""));
cb.like(cast, String.format("%%%s%%", stringValue));
Worked perfectly. I hope this help someone else.
As #HaroldH said, it's a weird requirement, but happened in my project.

Convert Integer to String in Spring Data JPA Query

I need to optimize a query that iterates over several objects and I wanted Spring Data to let the database handle it. I want to end up with a HashMap<String,String> that looks like
2134_9877, 9877
2134_2344, 2344
3298_9437, 9437
The SQL would be select convert(varchar,b.id)+'_'+convert(varchar,a.id)',a.id from t1 a join t2 b on a.jc = b.jc
So far, I've got Whatever-QL in the repository that looks like:
#Query("SELECT new map (a.bkey, a.akey) FROM mergeTable a WHERE a.discr= ?1")
The problem is, bkey is not unique, it is only unique when paired with akey and the monstrosity that I have to feed it to wants them combined with an underscore: 2345_2177.
I have tried a.bkey.toString and ''+a.bkey and new String(a.bkey) and just string(a.bkey) (that last gives a new exception but still doesn't work) but Spring doesn't like any of these. I can find no questions asking this and it seems I cannot use SQLServer's convert() function as this ain't SQL.
How can I concatenate the Integers as Strings with an underscore in this #Query?
PS: Using the native query that's been debugged in SQLServer throws some weird alias exception in Hibernate so I think 'going native' is predetermined to be a dead end.
If I have understood it right, the 'Whatever-QL' is called JPQL, and the operator CONCAT can be used. Only the use of it, as it accepts two or more parameters depends on the JPA version you are running.
Here is the answer.
JPA concat operator
You could add a getter to your entity like this:
public String getCombinedKey(){
return a.akey + "_" + a.bkey;
}
The advantage is you could handle here null's and other things if you want and it's more reusable in case you need this in another place. If you do it just in the repository you will have to copy it everytime.
Your query would then be:
#Query("SELECT new map (a.combinedKey, a.akey) FROM mergeTable a WHERE a.discr= ?1")

Selecting fields to join on when joining nested queries

I've recently been migrating my database and have been moving our remaining SQL queries over to jooq. Having some fun with this one:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments).on(COMMENT.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(numLikes).on(USER_LIKES_IDEA.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}
The returned query is then used to append additional modifiers (using addConditions(), addOrderBy(), addLimit() etc.) depending on the context, and then executed.
The issue I'm having is that the two sub-select queries are not named as their original names for the joins. This is correct behaviour! However, within jOOQ I'm having a hard time finding how I can specify the sub-query's fields to join on. I've tried going down the route of renaming the sub-queries as described in this StackOverflow answer, but the types don't line up for me.
Any ideas?
So I've found what I think is a solution for now. However, it seems like a workaround. I've given the sub-queries names when I do the join, and using that name to point to the field. If my schema changes in future, I don't think this will flag up a compile time error.
Here it is for anyone interested:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments.asTable("com")).on(DSL.field("com.idea_id").eq(IDEA.ID))
.leftOuterJoin(numLikes.asTable("like")).on(DSL.field("like.idea_id").eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}

org.hibernate.NonUniqueResultException: query did not return a unique result: 2?

I have below code in my DAO:
String sql = "SELECT COUNT(*) FROM CustomerData " +
"WHERE custId = :custId AND deptId = :deptId";
Query query = session.createQuery(sql);
query.setParameter("custId", custId);
query.setParameter("deptId", deptId);
long count = (long) query.uniqueResult(); // ERROR THROWN HERE
Hibernate throws below exception at the marked line:
org.hibernate.NonUniqueResultException: query did not return a unique result:
I am not sure whats happening as count(*) will always return only one row.
Also when i run this query on db directly, it return result as 1. So whats the issue?
It seems like your query returns more than one result check the database. In documentation of query.uniqueResult() you can read:
Throws: org.hibernate.NonUniqueResultException - if there is more
than one matching result
If you want to avoid this error and still use unique result request, you can use this kind of workaround query.setMaxResults(1).uniqueResult();
Hibernate
Optional findTopByClientIdAndStatusOrderByCreateTimeDesc(Integer clientId, Integer status);
"findTop"!! The only one result!
I don't think other answers explained the key part: why "COUNT(*)" returns more than one result?
I just encountered the same issue today, and what I found out is that if you have another class extending the target mapped class (here "CustomerData"), Hibernate will do this magic.
Hope this will save some time for other unfortunate guys.
Generally This exception is thrown from Oracle when query result (which is stored in an Object in your case), can not be cast to the desired object.
for example when result is a
List<T>
and you're putting the result into a single T object.
In case of casting to long error, besides it is recommended to use wrapper classes so that all of your columns act the same, I guess a problem in transaction or query itself would cause this issue.
It means that the query you wrote returns more than one element(result) while your code expects a single result.
Received this error while doing otherwise correct hibernate queries. The issue was that when having a class extend another hibernate was counting both. This error can be "fixed" by adding a method to your repository class.
By overriding the class count you can manually determine the way this is counted.
#Override
public Integer count(Page<MyObject> page) {
// manual counting method here
}
I was using JPQL and wanted to return Map. In my case, the reason was that I wanted to get Map<String, String>, but had to expect List<Map<String, String>> :)
Check your table, where one entity occurring multiple time's.
I had the same error, with this data :
id
amount
clientid
createdate
expiredate
428
100
427
19/11/2021
19/12/2021
464
100
459
22/11/2021
22/12/2021
464
100
459
22/11/2021
22/12/2021
You see here clientid occurring two times with 464.
I solved it by deleting one row :
id
amount
clientid
createdate
expiredate
428
100
427
19/11/2021
19/12/2021
464
100
459
22/11/2021
22/12/2021
I have found the core of the problem:
result of SELECT COUNT(*) can be a list, if there is a GROUP BY in the query,
and sometimes Hibernate rewrite your HQL and put a GROUP BY into it, just for fun.
Basically your query returns more than one result set.
In API Docs uniqueResult() method says that
Convenience method to return a single instance that matches
the query, or null if the query returns no results
uniqueResult() method yield only single resultset
Could this exception be thrown during an unfinished transaction, where your application is attempting to create an entity with a duplicate field to the identifier you are using to try find a single entity?
In this case the new (duplicate) entity will not be visible in the database as the transaction won't have, and will never be committed to the db. The exception will still be thrown however.
Thought this might help to someone, it happens because "When the number of data queries is greater than 1".reference
As what Ian Wang said, I suspect you are using a repository from spring. And a few days ago you just copy past a class and forgot to delete it when it is finally unused. Check that repository, and see if there is multiple same class of table you use. The count is not the count of rows, but the count of the table problem.
This means that orm technology is not preprogrammed to give you which results you are looking for because there are too many of the same results in the database. for example If there is more than one same value in my database and I want to get it back, you will encounter the error you get with the select query.
For me the error is caused by
spring.jpa.hibernate.ddl-auto=update
in application.properties file replacing it with
spring.jpa.hibernate.ddl-auto=create solved the issue, but it still depends on your needs to decide which configuration you need in your project, for more insights on the topic check this.
First you must test the query list size; here a example:
long count;
if (query.list().size() > 0)
count=(long) criteria.list().get(0);
else
count=0;
return count;

JAVA: NamedQuery String problem

Hello guys I am having some problems with exact matches while doing a NamedQuery.
I am currently using something like this:
#NamedQuery(name = MyClass.GET_ENTRY_BY_NAME, query = "select e from Entry e where e.name =:"+ Entry.NAME )
...
Query query = em.createNamedQuery(MyClass.GET_ENTRY_BY_NAME);
query.setParameter(Entry.NAME, myEntry.getName());
It works for most cases, however I noticed that in case the user pass the file name with an space at the end, the namedQuery ignores that character. For example:
Query query = em.createNamedQuery(MyClass.GET_ENTRY_BY_NAME);
query.setParameter(Entry.NAME, myEntry.getName()+ " ");
Will return the same result as the query before. Bypassing my 'valid entry' validation. In other words I'd like the query to return no entry at all and treat the error later on.
One workaround I could think of, is to put single quotes surrounding my parameter in the namedQuery, like this:
#NamedQuery(name = MyClass.GET_ENTRY_BY_NAME, query = "select e from entry e where e.name =':"+ Entry.NAME "'")
However it will trash my code in case the String contains single quotes in it...
Any ideas guys?
I guess this happens because your database field is declared as CHAR(...), and therefore stored values are padded with whitespaces which are not taken into account by = operation.
So, you may either declare your database field as VARCHAR(...) or use a built-in trim function:
query = "select e from Entry e where trim(trailing from e.name) =:"+ Entry.NAME
I did some research in JPA and found out that it does some automatic trimming for CHARs, I am not sure if this behaves the same with Strings, but since it is happening to me... I believe so. The only way to bypass it is by setting some attribute within the session DatabaseLogin object (see http://www.eclipse.org/eclipselink/api/1.1/org/eclipse/persistence/sessions/DatabaseLogin.html#setShouldTrimStrings) .
Well I didn't want to be messing up with the session properties so I decided to make some sort of check and throwing the same exception as the NoResultException catch does in my code.
I basically took the result from the database and compared the field with the String I used:
query.setParameter(Entry.NAME, myEntry.getName());
...
if(!StringUtils.equals(result.getName(), myEntry.getName()){
do a cool throw just like NoResultException Catch
}
I also had to include the Trim function axtavt! This is just to make sure that if the database has a column with trailing spaces and it matches the parameter given by the user, it will be included as a valid answer. For example:
Database entry: Name = "Flavio " - Trimmed with Function = "Flavio".
Parameter passed: Name = "Flavio " - Trimmed by JPA automatic function = "Flavio".
If it isnt trimmed at all it will just Compare "Flavio " with "Flavio", returning NoResult when it was supposed to return that Entry.
Nasty workaround, but as long as there is no other way to stop the auto-trimming we will have to just make use of this sort of things.
Thanks for all the other answers!!

Categories

Resources