I am having trouble writing a JPAQuery that will execute a simple less than or equal to comparison on an integer column. I have QCommand generated querydsl object which I am trying to use with a JPAquery to execute this very simple query. The query I want to execute would look like this using SQL:
select * from command where retry_count <= 10;
The generated QCommand object defines the retryCount parameter as:
public final SimplePath<Integer> retryCount = createSimple("retryCount", Integer.class);
I have noticed that because it is a SimplePath, when I try to write the JPAQuery, the retryCount variable does not have comparison operators like loe or goe. That column/variable only allows me to do a isNull, isNotNull, eq, and other very simple operators, but no comparison operators. This would look like below, however, obviously not execute the comparison I want, but it works without error:
JPAQuery query = new JPAQuery(entityManager);
query.from(command).where(command.retryCount.isNotNull()).list(command);
In looking around I saw there is a way to create a NumberPath, which seems to be what I want, however, when I attempt the below code I get an exception "java.lang.IllegalArgumentException: Undeclared path 'retryCount'. Add this path as a source to the query to be able to reference it."
JPAQuery query = new JPAQuery(entityManager);
NumberPath<Integer> retryCount = new NumberPath<Integer>(Integer.class, "retryCount");
return query.from(command).where(retryCount.loe(10)).list(command);
So, how can I "add" this path to the source. I have looked around and I cannot find an example that does this nor an explanation as to how this is supposed to be done. The javadocs are not very helpful in this situation. I had been moving along nicely with querydsl, but this is the first roadblock and I'm stumped on writing one of the most simple queries. Any assistance or advice would be greatly appreciated.
The query with manual paths works like this
JPAQuery query = new JPAQuery(entityManager);
NumberPath<Integer> retryCount = new NumberPath<Integer>(Integer.class, command, "retryCount");
return query.from(command).where(retryCount.loe(10)).list(command);
The difference is that you create the retryCount path as a top level path, where as it should be created as a property path with a parent.
How does the Command class look like? There might be some issues in the code generation.
Related
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.
I need to copy the records of one table to another table based on some condition.
String query = "insert into public.ticket_booking_archive select * from public.ticket_booking where ticketId in (:ticketIds)";
So here the :ticketIds are dynamic, where i need to pass ticketIds to make sure whether it satisfies the condition. So it may be the matching and non matching ticket id's here at runtime.
The values of ticketIds are something like this
('f1fa3a42-5837-11ec-bf63-0242ac130002','516fd14d-3c9d-4b4b-91a0-b684d8592dfe','c9652f86-734c-4df5-8ef9-d407cb3eaf7a','df7f2812-b445-45b4-b731-da23c36d7738','f1fa3a42-5837-11ec-bf63-0242ac130002'). And this is just an example. And the list might goes on.
Since it is of type UUID, I'm storing it into a Set<UUID>
Set<UUID> tktIds = new HashSet<UUID>();
for(int i=0 ; i<ticketIds.size(); i++) {
String ticketId = ticketIds[i];
tktIds.add(UUID.fromString(ticketId));
}
Map<String, Object> params = new HashMap<>();
params.put("ticketIds", tktIds);
SqlParameterSource namedParameters =
new MapSqlParameterSource().addValue("ticketIds",params.get("ticketIds"));
Since I'm using NamedParameterJdbcTemplate, so I'm using like below
int res = writeNamedJdbcTemplate.update(query, namedParameters);
res = 3 when executed programmatically.
Here the problem is, as soon as it finds the first matching value in the IN clause it executes. And it is not considering the other matching values (ticketIds here)
But if I execute the same query in pgadmin it works fine
insert into public.ticket_booking_archive select * from public.ticket_booking where ticketId in ('f1fa3a42-5837-11ec-bf63-0242ac130002','516fd14d-3c9d-4b4b-91a0-b684d8592dfe','c9652f86-734c-4df5-8ef9-d407cb3eaf7a','df7f2812-b445-45b4-b731-da23c36d7738','f1fa3a42-5837-11ec-bf63-0242ac130002');
result is 6. Working as expected.
writeNamedJdbcTemplate.queryForObject(query, namedParameters, Integer.class); //. throws an error
Can anyone please assist? I'm really not sure where I'm making a mistake
I am not quite sure whether you are using the appropriate JDBC template for the named parameters, but you can do the following:
you can consult this article to use the right template and employ proper SQL query composition,
for string passing you can wrap the parameter mapping as shown here
after all your named parameter should work
I have a query that returns a Postgres array of UUIDs:
SELECT e.id, e.date,
ARRAY
(
SELECT cs.api_id FROM event_condition_set ecs
JOIN condition_set cs on cs.id = ecs.condition_set_id
WHERE ecs.event_id = e.id
) AS condition_set_ids,
...
And then create and run this query as a native query: Query query = entityManager.createNativeQuery(queryString);
Since Hibernate can normally not deal with these Postgres arrays, I use Vlad's Hibernate Types library.
However, currently I need to register this UUIDArrayType globally in my application:
public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
super();
this.registerHibernateType(Types.ARRAY, UUIDArrayType.class.getName());
}
}
Aside from the fact this is a bit ugly, it also leaves no room for other types of arrays.
(Note I also tried registering a generic ListArrayType but this throws a NullPointerException during execution of the query.)
I have also tried registering it as a scalar type:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
But this makes the entire query only return a single UUID, which is very strange and seems bugged.
Is there a way to ONLY use this UUIDArrayType specifically in this query?
(Please don't suggest using array_agg because the performance is terrible for this case)
you can call native queries using custom Hibernate types as follows:
String myJsonbData = ...;
String[] myStringArr = ...;
final String queryStr = "select your_function(?, ?, ...)"; // ? for each param
entityManager
.createNativeQuery(queryStr)
.setParameter(1, new TypedParameterValue(JsonBinaryType.INSTANCE, myJsonbData))
.setParameter(2, new TypedParameterValue(StringArrayType.INSTANCE, myStringArr));
This is just an example, but as a rule of thumb, you need to instantiate a new TypedParameterValue.
Answering my own question here. After waiting for a while and updating to the most recent library version (I'm on 2.19.2 right now) I don't have any issues anymore with the scalar types registration as I mentioned in my question, i.e.:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
So it appears to just have been a bug and I can now avoid the global registration in favor of using scalars.
To query a PostgreSQL 10.11 database, I am using jOOQ 3.12.4, which comes bundled with Spring Boot 2.2.
Let's assume I have built a query using jOOQ like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
final Map<String, List<MyTable>> changeDomains = query.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
This code runs fine and produces the expected results. But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code), like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
I suddenly start to get the following error:
org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar ...; nested exception is org.postgresql.util.PSQLException: ERROR: operator does not exist: text = character varying[]
Hinweis: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Edit: I get the same error when I use
MY_TABLE.ID.in(param("ids", String[].class))
instead.
How can I solve or work around this problem?
A better solution to your code reuse approach
But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code)
While you could use jOOQ this way (be careful, when mutating and reusing jOOQ queries in a non-threadsafe way!), it is generally recommended to use jOOQ in a more functional way, see e.g.:
https://blog.jooq.org/2017/01/16/a-functional-programming-approach-to-dynamic-sql-with-jooq/
https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql/
You don't gain much by re-using a jOOQ query, specifically, there's hardly any performance gain.
So, instead of this:
final var query = dslContext.selectFrom(MY_TABLE)
.where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query
.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
Write this:
public ResultQuery<MyTableRecord> query(String[] ids) {
return dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
}
// And then:
final Map<String, List<MyTable>> changeDomains = query(ids)
.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
The actual problem you ran into:
jOOQ, JDBC, and SQL don't support single bind value IN lists. While it seems useful to write this:
SELECT * FROM t WHERE c IN (:bind_value)
And passing an array or list as a single bind value, this is not supported in SQL. Some APIs might pretend that this is supported (but behind the scenes replace the single bind value by multiple ?, ?, ..., ?
PostgreSQL supports the = ANY (:bind_value) operator with arrays
SELECT * FROM t WHERE c = ANY (:bind_value)
You could use it in jOOQ using
dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.eq(any(ids)));
That way, you could call the bind() method to replace the array prior to execution. However, I still recommend you write functions returning queries dynamically.
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")