Collection Query Issues with QueryDSL JPA and PostgreSQL - java

I'm currently using QueryDSL 3.7.2 with Spring-Data-JPA. I'm building a predicate, then passing it to a repository that extends QueryDslPredicateExecutor and queries a PostgreSQL 9.3 database.
I had everything working correctly, but have run into two scenarios that are causing me issues. I'm not sure if I'm approaching this correctly, and I can't seem to find any examples that quite match the scenarios.
Scenario 1:
For this, I have an entity with a list of children. I wanted an "AND" on two of the child properties, so I used a JPASubQuery:
final QChild any = QParent.parent.children.any();
final JPASubQuery subQuery = new JPASubQuery();
final QChild qChild = QChild.child;
subQuery.from(qChild)
.where(qChild.code.eq(codeValue)
.and(qChild.date.isNull()));
predicateBuilder.and(any.in(subQuery.list(qChild)));
So basically I want to fetch any Parent objects where the Child has a code of codeValue and a null date. This worked perfectly when the Child had a surrogate key in the database (an ID column). This generated the following query when passed to the repository:
select
count(parent0_.parent_id) as col_0_0_
from parent_tab parent0_
where exists (
select 1
from child_tab child1_
where parent0_.parent_id=child1_.parent_id
and (
child1_.child_id
in (
select child2_.child_id
from child_tab child2_
where child2_.status=? and (child2_.date is null)
)
)
)
The problem arises when we change the surrogate key to a natural key with serveral fields (code, status, parent_id and name). The following query is then generated:
select
count(parent0_.parent_id) as col_0_0_
from parent_tab parent0_
where exists (
select 1
from child_tab child1_
where parent0_.parent_id=child1_.parent_id
and (
(child1_.code, child1_.status, child1_.parent_id, child1_.name)
in (
select (child2_.code, child2_.status, child2_.parent_id, child2_.name)
from child_tab child2_
where child2_.status=? and (child2_.date is null)
)
)
)
This isn't valid, and throws the following exception:
ERROR: subquery has too few columns
From what I can gather, any().in(subQuery.list(qChild)) is the part that's causing the problem. From all the examples I've found, this is what's being done - but it's also with a surrogate key. Is there something that I'm missing here?
Scenario 2:
The second scenario is dealing with a PostgreSQL only feature - the pg_trgm extension. This extension is used for fuzzy searching, and allows us to use two specific commands - "similarity(x, y)" and "x % y". The former will return a real number representing how close a match the parameters are, and the second will return a boolean if the values are above 0.3 (by default). Originally I was using the former, like so:
final NumberExpression<Double> nameExpression = NumberTemplate.create(Double.class, "similarity({0}, {1})", QParent.parent.name ConstantImpl.create(name));
predicateBuilder.or(nameExpression.goe(0.3));
This worked perfectly, but unfortunately "similarity(x, y)" doesn't use trigram indexes, so I wanted to change to the "%" operator, which does. I thought it should be as easy as the following:
final BooleanExpression nameExpression = BooleanTemplate.create("{0} % {1}", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());
Unfortunately this doesn't work, and throws the following exception:
java.lang.IllegalArgumentException: Parameter value [true] did not match expected type [java.lang.String (n/a)]
at com.mysema.query.jpa.impl.JPAUtil.setConstants(JPAUtil.java:55) [querydsl-jpa-3.7.2.jar:]
at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:130) [querydsl-jpa-3.7.2.jar:]
at com.mysema.query.jpa.impl.AbstractJPAQuery.count(AbstractJPAQuery.java:81) [querydsl-jpa-3.7.2.jar:]
at org.springframework.data.jpa.repository.support.QueryDslJpaRepository.findAll(QueryDslJpaRepository.java:141) [spring-data-jpa-1.9.2.RELEASE.jar:]
The problem seems to be that it's expecting a String instead of a Boolean for the JavaType in the AbstractJPAQuery. Excluding isTrue() results in the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: % near line 3, column 19
The query is the following:
select count(parent)
from com.test.Parent parent
where parent.name % ?1
I solved this by using a custom dialect, and registering the following function:
registerFunction("sim", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "?1 % ?2"));
Then I can use the following:
final BooleanExpression nameExpression = BooleanTemplate.create("sim({0}, {1})", QParent.parent.name, ConstantImpl.create(name));
predicateBuilder.or(nameExpression.isTrue());
When expanding on this, I wanted to also check the name of the child entities. This led to the following:
final BooleanExpression childExpression = BooleanTemplate.create("sim({0}, {1})", QParent.parent.children.any().name, ConstantImpl.create(name));
predicateBuilder.or(childExpression.isTrue());
This, however, doesn't work, and throws the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: exists near line 3, column 7
The query is:
select count(parent)
from com.test.Parent parent
where exists (select 1
from parent.children as parent_children_4652c
where sim(parent_children_4652c.name, ?1)) = ?2
The exception seems to point to the "exists", but I'm not sure why. As a result, I tried to create a sub-query (like scenario 1):
final BooleanExpression childExpression = BooleanTemplate.create("sim({0}, {1})", QChild.child.name, ConstantImpl.create(name));
final QChild child = QChild.child;
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
.where(childExpression.isTrue());
predicateBuilder.or(QParent.parent.children.any().in(subQuery.list(child)));
This, however, runs into the same problem as scenario 1, where the child entity has a composite key, and the any().in() doesn't seem correct.
So there's a few questions here:
Is there a way to use the % operator without registering a custom function in the dialect?
Am I querying the children correctly with the sim() function?
What's the correct way to craft a sub-query with a composite key?
Any additional pointers or help would be greatly appreciated too.

Figured out how to do both (though I haven't answered all questions from Scenario 2). My problem was thinking that I wanted the entity returned. A good night's sleep made me see it clearly.
Instead of returning qChild from the subquery list, I should have been returning its parent ID. Then I simply needed to check if the parent ID was in that list:
final String code = "code";
final String name = "name";
final JPASubQuery subQuery = new JPASubQuery();
final QParent parent = QParent.parent;
final QChild child = QChild.child;
subQuery.from(child)
.where(child.id.code.eq(code)
.and(child.id.name.eq(name)));
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));
For the second scenario, I kept the custom Dialect with the registerFunction for my trigram operator, and used the following subquery:
final String name = "name";
final QParent parent = QParent.parent;
final QChild child = QChild.child;
final BooleanExpression newExpression = BooleanTemplate.create("sim({0}, {1})",
child.id.name, ConstantImpl.create(name));
final JPASubQuery subQuery = new JPASubQuery();
subQuery.from(child)
.where(newExpression.isTrue());
predicateBuilder.or(parent.id.in(subQuery.list(child.id.parentId)));
Everything is working correctly, though I still wonder if there's a way to simply use the trigram operator without registering a custom function.

Related

How to I remove L from Long field's values in java spring

I put query param for my list services for example:
tablename/list?query=id:10
it is running but I added other param
'personTNo'
tablename/list?query=id:10&personTNo=101035678
id is Integer but personTNo is Long
when I try to this sql returns select * from TABLENAME WHERE personTNo=10L
but this I want to return without 'L' for Long value. It is my code's a bit section in RepositoryCustom class
public List<TABLENAME> getTable(Specification aTablenameSpec) {
CriteriaBuilder builder = mEntityManager.getCriteriaBuilder();
CriteriaQuery<Object> query = builder.createQuery();
Root<TABLENAME> root = query.from(TABLENAME.class);
String queryWhere = null;
org.hibernate.query.Query hibernateQuery = null;
Predicate predicate = aTablenameSpec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
query.select(root);
TypedQuery<Object> typedQuery = mEntityManager.createQuery(query);
hibernateQuery = typedQuery.unwrap(org.hibernate.query.Query.class);
String queryString = hibernateQuery.getQueryString();
This row returns with L result, How to remove 'L' value in sql
Use INTEGER() function in the sql query. You can also try CAST() or CONVERT() functions in the query
Based on the problem description and code, it seems safe to assume the tech stack includes: JPA and Spring Data JPA.
And I understand that you want to remove the Long value L suffixes, but it's not clear if that's because the suffixes are causing a problem or exactly why you want the suffixes removed.
I only say that because the example query string appears to be a valid JPA query:
select * from TABLENAME WHERE personTNo = 10L
JPA support for the use of literal values in queries includes support for standard Java numeric (integer/long/float/double) literal value syntax.
Which means the L suffix on the literal Long value of personTNo, as defined in your query (10L), is legitimate, valid, and should not cause a problem.
Please let me know if I've missed the point, made an incorrect assumption, or overlooked something, and I will follow up.

Constructing DTO:s with Querydsl and ConstructorExpression.create()

I have a problem involving querydsl and DTO:s
I have some query object:
QPerson person = QPerson.person;
QExamCode examCode = QExamCode.examCode;
QExamGrade examGrade = QExamGrade.examGrade;
QProgram gradeProgram = examGrade.program;
From them I try to query and list instances of a DTO class (that is not an entity) that is called CompletedCreditsSummary.
CompletedCreditsSummary has a constructor which takes: Long,Long,Float.
JPQLQuery query = new JPAQuery(manager);
query = query.from(person, examCode, examGrade);
query = query.where(person.studies.examGrades.contains(examGrade).and(examGrade.examCode.eq(examCode)).and(examGrade.passed.isTrue()));
I am able to do this (Without group by and with CompletedCreditsSummary requiering all the parameters it needs to be able to create person and program objects, in this case simplified to person.id and program.id)
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,program.id,examCode.credits);
return query.list(completedCreditsSummaryExpression);
This works. But when I want to add this to the query:
query.groupBy(person, examGrade.program);
and create CompletedCreditssummary with examCode.credits.sum() i.e.
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,gradeProgram.id,examCode.credits.sum());
instead of
ConstructorExpression.create(CompletedCreditsSummary.class,person.id,gradeProgram.id,examCode.credits);
I get a: java.lang.IllegalArgumentException: argument type mismatch.
The question here is what the difference between examCode.credits (NumberPath) and examcode.credits.sum() (NumberExpression) and what I could do to solve my problem.
As I am learning querydsl by trial and error there is probably something fundamental that I have overlooked. Would really appreciate any help!
Regards Rasmus
EDIT: Preferably I would something like this to work (with group by and CompletedCreditsSummary constructor taking Person,Program,Float.):
ConstructorExpression<Person> personExpression = ConstructorExpression.create(Person.class,person.id);
ConstructorExpression<Program> programExpression = ConstructorExpression.create(Program.class,gradeProgram.id);
ConstructorExpression<CompletedCreditsSummary> completedCreditsSummaryExpression = ConstructorExpression.create(CompletedCreditsSummary.class,personExpression,programExpression,examCode.credits.sum());
EDIT: Got it to work by having the CompletedCreditsSummary Constructor accepting: Long,Long,Number. That is I changed Float to Number. This is not an ideal solution but at least it works.
Try something like this
JPAQuery query = new JPAQuery(manager);
query.from(person, examCode, examGrade)
.where(
person.studies.examGrades.contains(examGrade),
examGrade.examCode.eq(examCode),
examGrade.passed.isTrue())
.groupBy(person, examGrade.program)
.list(ConstructorExpression.create(
CompletedCreditsSummary.class,
person, examGrade.program, examCode.credits.sum()));
You need to make sure that the argument for ConstructorExpression after the class are compatible with the arguments to the constructor you want to invoke. Replacing entities with ids caused your problems.

Writing a Hibernate Criteria API Query with restrictions for multiple sub-elements

I have a data model that looks like this (simplified example):
public class Address { private List<AddressLine> addressLines; }
public class AddressLine { private String value; private String type; }
I am trying to use the Criteria API to search for Addresses in the database that contain specific combinations of AddressLines. For example, to retrieve all addresses that contain the address lines {(type="CITY", value="London"), (type="COUNTRY", value="GB")}. I haven't been able to find any examples of such a query.
As far as I have been able to get is to query for an Address based on a single AddressLine.
session.createCriteria(Address.class)
.createCriteria("addressLines")
.add(Restrictions.and(Restrictions.eq("type", type), Restrictions.eq("value", value))).list()
If I add a restriction for a second address lines the SQL that hibernate generates is basically asking SELECT x WHERE x.y = 'a' AND x.y = 'b' so will never return any results.
I have found similar questions being asked before but none of them have an accepted or voted for answer.
You need to write the Criteria equivalent of
select a from Address a where
exists (select line1.id from AddressLine line1 where line1.address.id = a.id
and line1.type = 'CITY'
and line1.value = 'London')
and exists (select line2.id from AddressLine line where line2.address.id = a.id
and line2.type = 'COUNTRY'
and line2.value = 'GB')
This means writing a DetachedCriteria for each subquery, with an id projection, and using these detached criterias as argument of two Subqueries.exists() calls. The alias of the address entity in the main criteria can be used in the detached criterias to implement the line1.address.id = a.id restriction.

Assign rows of a result set with same id into a Java list using Hibernate

I'm using Hibernate to retrieve some data from a database. The result set returned by the SQL query I'm using, is something like the following:
SourceId | TargetId
1 | 10
1 | 11
1 | 12
2 | 13
2 | 14
Right now I'm having something like the following class:
#NamedNativeQuery(name = "myQuery", resultSetMapping = "myMapping",
query = "select source.id id1, target.id id2
+ "from someTable source "
+ "left join associationTable association on source.id=association.sourceId "
+ "left join someTable target on association.targetId=target.id "
+ "where source.id in(?1)")
#SqlResultSetMapping(name = "myMapping",
entities = #EntityResult(entityClass = DBCalculationElement.class, fields = {
#FieldResult(name = "targetId", column = "targetId"),
#FieldResult(name = "sourceId", column = "sourceId") }))
public class MyClass {
private String sourceId;
private String targetId;
public String getSourceId() {
return sourceId;
}
public String getTargetId() {
return targetId;
}
}
Even though everything is working fine and I'm getting the results I need, in some cases the result set is really huge (thousands of rows) so it takes several minutes to actually get the data. I'm aware that Hibernate is not the best solution performance-wise when it comes to really big result sets, but I'm trying to avoid using raw JDBC.
One workaround would be to have a list of strings as targetId. This way, and using the above resultSet as an example, I would get 2 objects, one with
sourceId = "1"
which also has a list of targetIds containing the following:
targetId = <"10", "11", "12">
and something similar for the second object with sourceId="2".
Does anyone know how can I do this using Hibernate? How can I map several rows to a list in Java?
Once, i solved this problem by writing a stored procedure.
u can write stored proc to retun comma separated values of target id for each source id and in your main query use distinct to get unique sourceId.
Hope, will work for u also.
I'm aware that Hibernate is not the best solution performance-wise when it comes to really big result sets
First of all that is not true.
...in some cases the result set is really huge (thousands of rows)...
This is not huge for a DBMS like postgres, mysql and so on...
You should consider using setMaxResults and setFirstResult for paginating your query. (it is like offset and limit).
And you should take a look at your machine and JVM configurations, if you are getting performance lacks with such a simple query (yes, there is joins. but still, simple).

Hibernate: Parse/Translate HQL FROM part to get pairs class alias, class name

Can anyone point me out, how can I parse/evaluate HQL and get map where key is table alias and value - full qualified class name.
E.g. for HQL
SELECT a.id from Foo a INNER JOIN a.test b
I wish to have pairs:
a, package1.Foo
b. package2.TestClassName
It's relatively easy to do for result set
HQLQueryPlan hqlPlan = ((SessionFactoryImpl)sf).getQueryPlanCache().getHQLQueryPlan( getQueryString(), false, ((SessionImpl)session).getEnabledFilters() );
String[] aliases = hqlPlan.getReturnMetadata().getReturnAliases();
Type[] types = hqlPlan.getReturnMetadata().getReturnTypes();
See details here.
Hardly a good way of doing it, but it seems you can get the AST through some internal interfaces and traverse this:
QueryTranslator[] translators = hqlPlan.getTranslators();
AST ast = (AST)((QueryTranslatorImpl)translators[0]).getSqlAST();
new NodeTraverser(new NodeTraverser.VisitationStrategy() {
public void visit(AST node) {
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
}
}).traverseDepthFirst(ast);
So this seems to retrieve the alias-mappings from the compiled query, but I would be very careful using this solution: it typecasts objects to subclasses not usually visible to a hibernate-client and interprets the AST based on guessing the semantics of the different nodes. This might not work on all HQL-statements, and might not work, or have different behaviour, on a future hibernate-version.
I found right solution for my question. Your original post was almost correct except that part:
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
Please correct your answer answer and I accept it.

Categories